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にアクセスすると、ユーザーはリーダーにカードを挿入するように求められます。このカードのプロンプトを検出してスキップする方法、または証明書に現在リーダーに対応するカードがあることを検出する方法は?
現在リーダーにあるスマートカードの証明書を使用したいだけです。
標準の.NETAPIを使用して、特定の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;
}
ただし、この方法は、次の条件が満たされた場合にのみ信頼できます。
スマートカードが接続されているリーダーが複数ある場合、またはカードに複数の証明書が存在する場合、この方法でどれが返されるかはわかりません。
スマートカードにアクセスできる他のAPIも利用できることに注意してください。このようなAPIの一例はPKCS#11です。単純な操作ではやり過ぎかもしれませんが、カードとそれに保存されているオブジェクトを完全に制御できます。興味があり、スマートカードにPKCS#11ライブラリが付属している場合は、私のプロジェクトをご覧ください Pkcs11Interop PKCS#11APIのフルパワーを.NET環境にもたらします。
お役に立てれば :)
「単一の証明書」の制限を削除するように編集:
コードを少し変更しました。アンマネージドCryptoAPIを使用して、Microsoft Base Smart Card Crypto Providerによって管理されているすべてのコンテナーの名前を列挙し、CurrentUser\Myストアで対応するX509Certificate2オブジェクトを検索するようになりました。このアプローチも非常にハックであり、提供されたコードは、市場で入手可能なすべてのカード/ミニドライバーで確実に機能しない可能性があることに注意してください。通常、ユーザーが組み込みの証明書選択ダイアログから正しい証明書を選択できるようにする方が適切で簡単です。
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();
証明書の件名を知っているのに、なぜストア内のすべての証明書を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"));
}
正確な件名がわからない場合、またはこの件名の別の証明書を見つけることを期待している場合、これはおそらく機能しません。