很遗憾,使用标准的 .NET API 无法检测读卡器中是否存在包含特定 X509Certificate2 对象的卡。我能想到的最好方法(非常不正规)是:
public static X509Certificate2 GetDefaultCertificateStoredOnTheCard()
{
CspParameters cspParameters = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider");
RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParameters);
string pubKeyXml = rsaProvider.ToXmlString(false);
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;
}
然而,此方法仅在满足以下条件时可靠:
- 您的卡可通过minidriver和Microsoft Base Smart Card Crypto Provider访问。
- 只有一个读卡器连接到计算机,智能卡已插入其中。
- 当前插入读卡器的卡上只有一个证书。
当使用多个读卡器与智能卡或卡上存在多个证书时,无法确定此方法将返回哪个证书。
请注意还有其他可用于访问智能卡的API。其中一个例子是PKCS#11。它可能对于简单操作来说过于复杂,但可以让您完全控制卡和存储在其上的对象。如果您感兴趣并且您的智能卡带有PKCS#11库,您可以查看我的项目Pkcs11Interop,为.NET环境带来了PKCS#11 API的全部功能。
希望这会有所帮助 :)
编辑以删除“单证书”限制:
我稍微修改了代码。现在它使用非托管的Crypto API列举由Microsoft Base Smart Card Crypto Provider管理的所有容器的名称,然后在CurrentUser\My存储区域中搜索相应的X509Certificate2对象。请注意,这种方法也非常hackish,提供的代码可能无法可靠地使用市场上所有可用的卡/ minidriver。通常最好并且更容易让用户从内置证书选择对话框中选择正确的证书。
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();