CryptographicException при доступе к сертификату смарт-карты - PullRequest
1 голос
/ 27 сентября 2019

Я разрабатываю класс C # под названием SmartCardService для компании, в которой я работаю.Настольные приложения используют виртуальные и физические смарт-карты для связи со встроенным ПЛК, также разработанным нашей компанией.Сертификаты для этой цели выдаются нашими ИТ-специалистами специально для каждого пользователя.

SmartCardService предоставляет базовые функции для шифрования, дешифрования и подписи данных и проверки подписей.

public sealed class SmartCardService : ISmartCardService
{
    private RSACryptoServiceProvider CryptoServiceProvider { get; }

    public SmartCardService(X509Certificate2 certificate)
    {
        if(certificate == null) throw new ArgumentNullException("missing certificate");
        CryptoServiceProvider = (RSACryptoServiceProvider)Certificate.PrivateKey;
    }

    public byte[] EncryptData(byte[] data)
    {
        if (data == null) throw new ArgumentNullException("data");
        return CryptoServiceProvider.Encrypt(data, false);
    }

    public byte[] DecryptData(byte[] cipher)
    {
        if (cipher == null) throw new ArgumentNullException("cipher");
        return CryptoServiceProvider.Decrypt(cipher, false);
    }

    public byte[] SignData(byte[] data)
    {
        if (data == null) throw new ArgumentNullException("data");

        using (var sha256 = SHA256.Create())
        {
            return CryptoServiceProvider.SignData(data, sha256);
        }
    }

    public bool VerifySignature(byte[] data, byte[] signature)
    {
        if (data == null) throw new ArgumentNullException("data");
        if (signature == null) throw new ArgumentNullException("signature");

        using (var sha256 = SHA256.Create())
        {
            return CryptoServiceProvider.VerifyData(data, sha256, signature);
        }
    }
}

Сам сертификатпредоставляется другим компонентом CertificateProvider в корневом каталоге приложения (который позже также удаляет объект сертификата).Он просто выбирает первый сертификат, выданный нашей компанией для пользователя.

public sealed class CertificateProvider
{
    public X509Certificate2 GetCertificateFromStore(string subjectName)
    {
        using (var store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
        {
            store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
            return store.Certificates
                    .Find(X509FindType.FindByIssuerName, "<COMPANY NAME>", true)
                    .Find(X509FindType.FindBySubjectName, subjectName, true)
                    .Cast<X509Certificate2>()
                    .First();

        }
    }
}

Проблема возникает в следующем примере:

// example data encryption and decryption
var service = new SmartCardService(certificate);
var data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };
var encryptedData = service.EncryptData(data);              // prints a 256 byte long array of hex values
var decryptedData = service.DecryptData(encryptedData);     // prints the original data: 00-01-02-03-04-05-06-07

В Windows 10 все работает нормально.Результаты ожидаемые.

Однако в Windows 7 тот же код приводит к CryptographicException, как только эта строка попадет в конструктор класса SmartCardService:

CryptoServiceProvider = (RSACryptoServiceProvider)Certificate.PrivateKey;

Исключение имеет следующую трассировку стека:

Unhandled Exception System.Security.Cryptography.CryptographicException: System cannot find the specified file.

   at System.Security.Cryptography.Utils.CreateProvHandle(CspParameters parameters, Boolean randomKeyContainer)
   at System.Security.Cryptography.Utils.GetKeyPairHelper(CspAlgorithmType keyType, CspParameters parameters, Boolean randomKeyContainer, Int32 dwKeySize, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
   at System.Security.Cryptography.RSACryptoServiceProvider.GetKeyPair()
   at System.Security.Cryptography.RSACryptoServiceProvider..ctor(Int32 dwKeySize, CspParameters parameters, Boolean useDefaultKeySize)
   at System.Security.Cryptography.X509Certificates.X509Certificate2.get_PrivateKey()

Я до сих пор не нашел причину этой проблемы.Если бы кто-нибудь мог помочь мне с этой проблемой, я был бы очень очень признателен.

PS: Я нашел старую статью с тем же исключением, в котором указывались проблемы с хранением ключей в профиле пользователя.Я не уверен, относится ли это к проблеме, с которой я сталкиваюсь.

1 Ответ

0 голосов
/ 30 сентября 2019

Я следовал совету bartonjs и использовал метод GetRSAPrivateKey() RSACertificateExtensions .Это решило проблему.

Если у кого-то возникла такая же или похожая проблема, ниже приведен класс с рефакторингом, который теперь работает как в Windows 10, так и в Windows 7.

public sealed class SmartCardService : ISmartCardService
{
    private X509Certificate2 Certificate { get; }
    private RSA Rsa { get; }

    public SmartCardService(X509Certificate2 certificate)
    {
        Certificate = certificate ?? throw new ArgumentNullException("missing certificate");
        Rsa = certificate.GetRSAPrivateKey() ?? throw new CryptographicException("certificate has no private key");
    }

    public byte[] EncryptData(byte[] data)
    {
        if (data == null) throw new ArgumentNullException("data");

        return Rsa.Encrypt(data, RSAEncryptionPadding.Pkcs1);
    }

    public byte[] DecryptData(byte[] cipher)
    {
        if (cipher == null) throw new ArgumentNullException("cipher");

        return Rsa.Decrypt(cipher, RSAEncryptionPadding.Pkcs1);
    }

    public byte[] SignData(byte[] data)
    {
        if (data == null) throw new ArgumentNullException("data");
        return Rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    }

    public bool VerifySignature(byte[] data, byte[] signature)
    {
        if (data == null) throw new ArgumentNullException("data");
        if (signature == null) throw new ArgumentNullException("signature");
        return Rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
    }
}
...