Search code examples
c#digital-signaturex509certificatesmartcard-reader

How to check for matching Certificate in SmartCard reader?


Say I selected a certificate from Windows' keystore, and at the time of signing, I need to make sure whether the SmartCard inserted in the reader is the right one or not...

Here is some sample code:

    // finding the certificate
    X509Store store = new X509Store("MY", StoreLocation.CurrentUser);
    store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
    X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, true);

    // by thumbprint, there is only one
    certs = certs.Find(X509FindType.FindByThumbprint, "123456BLAHBLAHAE3C", true); 
    X509Certificate2 cert = certs[0];

    RSACryptoServiceProvider key;
    if (cert.HasPrivateKey)
    {
        // software cert
        key = cert.PrivateKey as RSACryptoServiceProvider;
    }
    else
    {
        // certificate from smartcard
        CspParameters csp = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider");
        csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
        key = new RSACryptoServiceProvider(csp);
    }

Now when I want to sign data how can I know whether the smartcard that is currently inserted is actually the one I'm looking for (matching the one from Windows' keystore)?

I am asking this question, because right now when I sign the data, I get prompts for entering the PIN for the smartcard, but the card I inserted isn't even for the credential that I've selected... And it just signed the data anyway...


Solution

  • After a whole day of troubleshooting and digging through the internet. It turns out I don't need to do that at all. In fact, it was a mistake I made on my part.

    When you select a certificate from the Store, the X509Certificate object you get actually has everything it needs, such as HasPrivateKey and PrivateKey (a RSACryptoServiceProvider object, not the actual PrivateKey), and Windows knows where to get the PrivateKey using those information... including verifying whether the smartcard in your reader has the right smartcard or not.

    My problem was that at some point I used cert.GetRawCertData() to grab the raw bytes to perform my own certificate validation, and I ended up recreating a X509Certificate object out of the raw bytes... I thought it would give me back the original X509Certificate object, but oh I was so wrong. Both HasPrivateKey and the PrivateKey are gone.

    Now, I am not sure if this is new in Windows 10. But as you can see below, I actually don't need to check what's in the card reader at all...

    enter image description here

    To close this issue, my original intent of double checking the smartcard in the reader was to ensure that the signing operation was indeed done with the correct smartcard (I had issue in Java where you can pop in a different card and enter the PIN for the wrong card and it would still sign it for you). However, the better approach is actually to use the PublicKey and verify the data instead.

    // so after you finished signing the data...
    // make a new RSACryptoServiceProvider and verify it like this:
    RSACryptoServiceProvider rsa = cert.PublicKey.Key as RSACryptoServiceProvider;
    Debug.WriteLine("     Verify data >> " + (rsa.VerifyData(data, alg, signature) ? "OK" : "FAILED"));
    Debug.WriteLine("Verify signature >> " + (rsa.VerifyHash(hash, alg, signature) ? "OK" : "FAILED"));
    

    Note: Don't use using for the verification on the RSACryptoServiceProvider, if you do you will close the static handle on the PublicKey.Key, and if you try to validate again (for whatever reason), you will get a Safe Handle has been closed exception.