Найти сертификат на смарт-карте в настоящее время на читателе

Я использую Visual Studio 2013 (С#) для цифровой подписи документа с использованием сертификата с смарт-карты. Я не могу определить сертификат, который в настоящее время вставлен в устройство чтения карт: (

Windows копирует сертификаты со всех вставленных в считыватель карт и сохраняет их в магазине. Я хочу использовать в кармане только карточку.

Используемый код I

public static byte[] Sign(Stream inData, string certSubject)
{

    // Access Personal (MY) certificate store of current user
    X509Store my = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    my.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

    // Find the certificate we'll use to sign            
    RSACryptoServiceProvider csp = null;
    foreach (X509Certificate2 cert in my.Certificates)
    {
        if (cert.Subject.Contains(certSubject))
        {
            // We found it. 
            // Get its associated CSP and private key
            if (cert.HasPrivateKey) {
                csp = (RSACryptoServiceProvider)cert.PrivateKey;
                if (csp.CspKeyContainerInfo.HardwareDevice)
                    Console.WriteLine("hardware");                              
                    Console.WriteLine(cert.ToString());
            }
        }
    }
    if (csp == null)
    {
        throw new Exception("No valid cert was found");
    }

    // Hash the data
    SHA1Managed sha1 = new SHA1Managed();
    byte[] hash = sha1.ComputeHash(inData);

    // Sign the hash
    return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
}

Но когда пользователь доступа cert.PrivateKey получает запрос на вставку карты в считывающее устройство. Как обнаружить и пропустить эту подсказку для карты или определить, что в настоящее время у читателя есть соответствующая карта HAS?

Я просто хочу использовать сертификат из смарт-карты, который в настоящее время находится в читателе.

Ответ 1

Я боюсь, что невозможно обнаружить, присутствует ли в читателе карта, содержащая определенный объект X509Certificate2, с использованием стандартных .NET API. Самое лучшее (очень хакерское), которое я мог бы придумать, это:

public static X509Certificate2 GetDefaultCertificateStoredOnTheCard() 
{ 
    // Acquire public key stored in the default container of the currently inserted card
    CspParameters cspParameters = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider"); 
    RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParameters); 
    string pubKeyXml = rsaProvider.ToXmlString(false); 

    // Find the certficate in the CurrentUser\My store that matches the public key
    X509Store x509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser); 
    x509Store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); 
    foreach (X509Certificate2 cert in x509Store.Certificates) 
    { 
        if ((cert.PublicKey.Key.ToXmlString(false) == pubKeyXml) && cert.HasPrivateKey)
            return cert; 
    } 

    return null; 
}

Однако этот метод является надежным только при выполнении следующих условий:

  • Вы можете получить доступ к карте через минидрайвер и Microsoft Base Smart Card Crypto Provider.
  • К вашему компьютеру подключен только один считыватель с имеющейся смарт-картой.
  • На карте, которая в настоящее время вставлена ​​в считыватель, присутствует только один сертификат.

Когда есть несколько считывателей с подключенными смарт-картами или несколько сертификатов, присутствующих на карте, вы не можете быть уверены, какой из них будет возвращен этим методом.

Обратите внимание, что есть также другие доступные API, которые могут получить доступ к смарт-карте. Одним из примеров такого API является PKCS # 11. Это может быть излишним для простых операций, но это может дать вам полный контроль над вашей картой и хранимыми на ней объектами. Если вы заинтересованы, и ваша смарт-карта поставляется с библиотекой PKCS # 11, вы можете взглянуть на мой проект Pkcs11Interop, который приносит полную мощность PKCS # 11 для среды .NET.

Надеюсь, что это поможет:)

Отредактировано для удаления ограничения "единого сертификата":

Я немного изменил код. Теперь он использует неуправляемый Crypto API для перечисления имен всех контейнеров, управляемых провайдером Crypto Provider Microsoft Base Smart Card, а затем ищет соответствующие объекты X509Certificate2 в хранилище CurrentUser\My. Обратите внимание, что этот подход также очень хакерский, и при условии, что код может не работать надежно со всеми картами/мини-контроллерами, доступными на рынке. Обычно лучше и проще позволить пользователю выбрать правильный сертификат из встроенного диалогового окна выбора сертификатов.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace CSP
{
    public static class BaseSmartCardCryptoProvider
    {
        private const string _providerName = "Microsoft Base Smart Card Crypto Provider";

        private static class NativeMethods
        {
            public const uint PROV_RSA_FULL = 0x00000001;
            public const uint CRYPT_VERIFYCONTEXT = 0xF0000000;
            public const uint CRYPT_FIRST = 0x00000001;
            public const uint CRYPT_NEXT = 0x00000002;
            public const uint ERROR_NO_MORE_ITEMS = 0x00000103;
            public const uint PP_ENUMCONTAINERS = 0x00000002;

            [DllImport("advapi32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
            public static extern bool CryptAcquireContext(
            ref IntPtr phProv,
            [MarshalAs(UnmanagedType.LPStr)] string pszContainer,
            [MarshalAs(UnmanagedType.LPStr)] string pszProvider,
            uint dwProvType,
            uint dwFlags);

            [DllImport("advapi32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
            public static extern bool CryptGetProvParam(
            IntPtr hProv,
            uint dwParam,
            [MarshalAs(UnmanagedType.LPStr)] StringBuilder pbData,
            ref uint pdwDataLen,
            uint dwFlags);

            [DllImport("advapi32.dll", SetLastError = true)]
            public static extern bool CryptReleaseContext(
            IntPtr hProv,
            uint dwFlags);
        }

        public static List<X509Certificate2> GetCertificates()
        {
            List<X509Certificate2> certs = new List<X509Certificate2>();

            X509Store x509Store = null;

            try
            {
                x509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
                x509Store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

                List<string> containers = GetKeyContainers();

                foreach (string container in containers)
                {
                    CspParameters cspParameters = new CspParameters((int)NativeMethods.PROV_RSA_FULL, _providerName, container);
                    cspParameters.Flags = CspProviderFlags.UseExistingKey;
                    string pubKeyXml = null;
                    using (RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParameters))
                        pubKeyXml = rsaProvider.ToXmlString(false);

                    foreach (X509Certificate2 cert in x509Store.Certificates)
                    {
                        if ((cert.PublicKey.Key.ToXmlString(false) == pubKeyXml) && cert.HasPrivateKey)
                            certs.Add(cert);
                    }
                }
            }
            finally
            {
                if (x509Store != null)
                {
                    x509Store.Close();
                    x509Store = null;
                }
            }

            return certs;
        }

        private static List<string> GetKeyContainers()
        {
            List<string> containers = new List<string>();

            IntPtr hProv = IntPtr.Zero;

            try
            {
                if (!NativeMethods.CryptAcquireContext(ref hProv, null, _providerName, NativeMethods.PROV_RSA_FULL, NativeMethods.CRYPT_VERIFYCONTEXT))
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                uint pcbData = 0;
                uint dwFlags = NativeMethods.CRYPT_FIRST;
                if (!NativeMethods.CryptGetProvParam(hProv, NativeMethods.PP_ENUMCONTAINERS, null, ref pcbData, dwFlags))
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                StringBuilder sb = new StringBuilder((int)pcbData + 1);
                while (NativeMethods.CryptGetProvParam(hProv, NativeMethods.PP_ENUMCONTAINERS, sb, ref pcbData, dwFlags))
                {
                    containers.Add(sb.ToString());
                    dwFlags = NativeMethods.CRYPT_NEXT;
                }

                int err = Marshal.GetLastWin32Error();
                if (err != NativeMethods.ERROR_NO_MORE_ITEMS)
                    throw new Win32Exception(err);

                if (hProv != IntPtr.Zero)
                {
                    if (!NativeMethods.CryptReleaseContext(hProv, 0))
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    hProv = IntPtr.Zero;
                }
            }
            catch
            {
                if (hProv != IntPtr.Zero)
                {
                    if (!NativeMethods.CryptReleaseContext(hProv, 0))
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    hProv = IntPtr.Zero;
                }

                throw;
            }

            return containers;
        }
    }
}

Просто вызовите метод GetCertificates() предоставленного класса, чтобы проверить, работает ли этот код с вашей картой:

List<X509Certificate2> certs = CSP.BaseSmartCardCryptoProvider.GetCertificates();

Ответ 2

Мне было интересно, почему вы делаете foreach через все сертификаты в магазине, когда вы знаете тему сертификата. Мое предложение было бы:

public static byte[] Sign(Stream inData, string certSubject)
{

    // Access Personal (MY) certificate store of current user
    X509Store my = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    my.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

    var foundCerts = my.Certificates.Find(X509FindType.FindBySubjectName, certSubject, true);
    if (foundCerts.Count == 0)
        throw new Exception("No valid cert was found");

    var cert = foundCerts[0];
    RSACryptoServiceProvider csp = null;
    // let us assume that certSubject is unique
    if (cert.HasPrivateKey)
    {
        csp = (RSACryptoServiceProvider)cert.PrivateKey;
        if (csp.CspKeyContainerInfo.HardwareDevice)
            Console.WriteLine("hardware");
        Console.WriteLine(cert.ToString());
    }
    else
    {
        throw new Exception("No private key assigned to this certificate");
    }

    // Hash the data
    SHA1Managed sha1 = new SHA1Managed();
    byte[] hash = sha1.ComputeHash(inData);

    // Sign the hash
    return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
}

Если вы не знаете точного предмета или ожидаете найти другой сертификат с этим предметом, это, вероятно, не сработает для вас.