在当前阅读器上的智能卡上查找证书

Posted

技术标签:

【中文标题】在当前阅读器上的智能卡上查找证书【英文标题】:find certificate on smartcard currently on reader 【发布时间】:2014-03-06 21:13:38 【问题描述】:

我正在使用 Visual Studio 2013 (C#) 使用智能卡中的证书对文档进行数字签名。 我无法识别当前插入读卡器的证书:(

Windows 从读卡器中插入的所有卡中复制证书并将其保存在商店中。我只想在读卡器中使用卡片。

我使用的代码是

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 时,系统会提示用户将卡插入读卡器。如何检测并跳过此卡提示或检测该证书当前在读卡器中是否有对应的卡?

我只想使用当前在阅读器中的智能卡证书。

【问题讨论】:

【参考方案1】:

恐怕无法通过使用标准 .NET API 来检测读卡器中是否存在包含特定 X509Certificate2 对象的卡。我能想到的最好的东西(非常老套)是这样的:

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,它为 .NET 环境带来了 PKCS#11 API 的全部功能。

希望这会有所帮助:)

编辑删除“单一证书”限制:

我稍微修改了代码。它现在使用非托管 Crypto API 来枚举由 Microsoft Base Smart Card Crypto Provider 管理的所有容器的名称,然后在 CurrentUser\My 存储中搜索相应的 X509Certificate2 对象。请注意,这种方法也非常hackish,并且提供的代码可能无法可靠地与市场上可用的所有卡/微型驱动程序一起使用。让用户从内置的证书选择对话框中选择正确的证书通常会更好、更容易。

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();

【讨论】:

这将解决我的问题。我正在考虑 RSACryptoServiceProvider 和 x509Store 的组合,但我希望有更合乎逻辑的解决方案,如 cert.CardInTheReader :) 谢谢。 我太快了:(我需要使用的一些卡有两个证书。我可以在 RSACryptoServiceProvider 中选择/过滤/一些证书吗? 对我来说,您的初始代码正是我所需要的,非常感谢。我以前只在浏览器中创建过智能卡签名,需要从 csp --> X509 开始,不知道我会在常规商店中找到它。谢谢! 上述代码适用于 GetKeyContainers() 并枚举密钥容器,对于这些密钥容器,GetCertificates() 无法使用 RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParameters) 创建 csp。没有获得 csp 的原因可能是什么? 我在尝试从卡中枚举容器时收到Access denied【参考方案2】:

我想知道,当您知道证书主题时,为什么要遍历商店中的所有证书。我的建议是:

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"));

如果您不知道确切的主题或希望找到具有此主题的另一个证书,这可能不适合您。

【讨论】:

不幸的是,我不知道证书的确切主题。还是谢谢。

以上是关于在当前阅读器上的智能卡上查找证书的主要内容,如果未能解决你的问题,请参考以下文章

Cordova InAppBrowser 访问虚拟智能卡上的证书

列出系统上的所有智能卡读卡器(Alcor Micro 读卡器问题)

如何修复智能手机中的证书错误[关闭]

枚举仅在智能卡上可用的证书

智能卡如何用于客户端证书身份验证?

来自 O2Micro 阅读器上的 WINSCARD.DLL 方法 SCardTransmit 的未知响应以及响应长度信息