InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

Setting the PIN of a smartcard programmatically

There are certain scenarios where it’s not feasible to require user interaction when accessing the private key of a smartcard. For example, a service does have the ability to provide a user interface. To use a key from a smartcard where a PIN is required, it must be provided to the smartcard using an alternative way. For some operations in .NET, one can use the CspParameters class to provide the PIN. But not all APIs which require smartcard access provide a way to use that class. The SSLStream is such a case. Here is a small extension method which sets the PIN for an X509Certificate2 instance:

static class X509Certificate2Extension
    public static void SetPinForPrivateKey(this X509Certificate2 certificate, string pin)
        if (certificate == null) throw new ArgumentNullException("certificate");
        var key = (RSACryptoServiceProvider)certificate.PrivateKey;

        var providerHandle = IntPtr.Zero;
        var pinBuffer = Encoding.ASCII.GetBytes(pin);

        // provider handle is implicitly released when the certificate handle is released.
        SafeNativeMethods.Execute(() => SafeNativeMethods.CryptAcquireContext(ref providerHandle, 
SafeNativeMethods.CryptContextFlags.Silent)); SafeNativeMethods.Execute(() => SafeNativeMethods.CryptSetProvParam(providerHandle,
pinBuffer, 0)); SafeNativeMethods.Execute(() => SafeNativeMethods.CertSetCertificateContextProperty(
0, providerHandle)); } } internal static class SafeNativeMethods { internal enum CryptContextFlags { None = 0, Silent = 0x40 } internal enum CertificateProperty { None = 0, CryptoProviderHandle = 0x1 } internal enum CryptParameter { None = 0, KeyExchangePin = 0x20 } [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool CryptAcquireContext( ref IntPtr hProv, string containerName, string providerName, int providerType, CryptContextFlags flags ); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern bool CryptSetProvParam( IntPtr hProv, CryptParameter dwParam, [In] byte[] pbData, uint dwFlags); [DllImport("CRYPT32.DLL", SetLastError = true)] internal static extern bool CertSetCertificateContextProperty( IntPtr pCertContext, CertificateProperty propertyId, uint dwFlags, IntPtr pvData ); public static void Execute(Func<bool> action) { if (!action()) { throw new Win32Exception(Marshal.GetLastWin32Error()); } } }

The interesting piece here is, of course, the SetPinForPrivateKey method. It acquires a Win32 cryptographic context, sets the PIN and associates that pin with the certificate. Once you have an X509Certificate2 instance, you can set the PIN with just one line of code:

var certificate = GetCertificate();

The PIN is remembered until the X509Certificate2 instance is open.


This solution is taken from an stackoverflow article. I’ve just made an extension method out of it, converted all those magic numbers to enums and added a little bit of error handling.

Posted by Henning Krause on Monday, November 22, 2010 4:17 PM, last modified on Thursday, November 25, 2010 11:26 PM
Permalink | Post RSSRSS comment feed