Sebastiano
Sebastiano

Reputation: 1792

X509Certificate2: determine if certificate private key belongs to an hardware device, and if it needs a PIN

I'm writing a program to do encryption things, using certificates stored on hardware devices (smart cards).

Preamble

As far as I understood, when a smart card, containing one or more certificates, is connected to the computer, the certificates appears on the Windows certificates store (CurrentUser/My).

This is managed by the smartcard software, that must be installed.

Since my software must work with any hardware device, the easiest solution is to popup a window, and making the user to choose the certificate to use.

First problem

I don't want to show all the certificates that are on CurrentUser/My, but only the ones that have an associated hardware connected to the computer.

This is, more or less, the code that I'm using:

X509Store storeObject = new X509Store(StoreName.My, StoreLocation.CurrentUser);

storeObject.Open(OpenFlags.ReadOnly);

foreach (var certificate in storeObject.Certificates)
{
    bool isValid = false;
    if (certificate.HasPrivateKey)
    {
        try
        {
            var rsaKey = certificate.PrivateKey as RSACryptoServiceProvider;
            
            if (rsaKey.CspKeyContainerInfo.HardwareDevice)
            {
                isValid = true;
            }
        }
        catch (Exception ex)
        {
            // just log
        }
    }
}

Despite this solution is very ugly, it's the best I came up.

The certificate belongs to a smart card if:

The problem is on the PrivateKey cast: it seems that lot of things are performed by the CryptoApi system during this cast (that includes calls - of course - to the smart card software).

This is very slow, and if the smartcard software has some kind of failures, uncontrollable error messages are shown, ...

Question

Is there any other way to determine if the certificate belongs to an hardware device? And to determine if the hardware is currently connected?

Second problem

The certificate may require a PIN.

I must (it's a prerequisite) avoid the Windows input box asking for the PIN.

It's not a problem for me to collect the PIN, and to use it during my crypto operations.

My problem is to understand if the PIN is required or not.

ANY example I found online assume that if rsaKey.CspKeyContainerInfo.HardwareDevice == true then a PIN is required.

But I'm not sure about that.

Question

Is there any way I can determine if a certificate requires a PIN code?

Conclusion

I also like external libraries, as long as they are free (preferably open source) and/or come from Microsoft.

Thank you very much!

Upvotes: 2

Views: 1718

Answers (1)

Crypt32
Crypt32

Reputation: 13924

In comments I offered Win32 interop solution which may be faster than delay OP experiences. Proposed solution will extended certificate properties to read provider name from there (I believe, .NET does the same somewhere internally?). Then open specified provider (CSP or KSP) and query hardware capabilites from provider. This solution doesn't touch private key, nor prompt for PIN.

Call sequence:

  1. Call CertGetCertificateContextProperty function and query CERT_KEY_PROV_INFO_PROP_ID property.
  2. Function returns you a pointer to a CRYPT_KEY_PROV_INFO structure.
  3. Structure contains pwszProvName field that stores provider name.
  4. Call NCryptOpenStorageProvider function and pass provider name as a parameter.
  5. Call NCryptGetProperty function and pass provider handle obtained in previous step and property name to retrieve. You need NCRYPT_IMPL_TYPE_PROPERTY property.
  6. Received value will store a bitwise flag combination of implementation types. Check if returned value (pbOutput parameter will store 4 bytes integer value) has NCRYPT_IMPL_HARDWARE_FLAG flag enabled.

Release all unmanaged resources when calls are done. Although this looks a bit complicated, but from my experience this is the fastest way to determine where key is stored for a given certificate.

Important remarks

steps 4 and 5 may not work for all smart card providers (given that you use legacy CSP), though, works for mine. In this case, replace steps 4 and 5 as follows:

  1. Call CryptAcquireContext function to get a handle to your provider (retrieved in step 3)
  2. call CryptGetProvParam function to retrieve provider properties. Query for PP_IMPTYPE property. pbData parameter will store a 4-byte long byte array that represents a bitwise combination of implementation types. Check if this value has CRYPT_IMPL_HARDWARE1 flag enabled.

Upvotes: 1

Related Questions