How verify eToken X509Certificate in C#

旧街凉风 提交于 2020-07-23 06:17:26

问题


I'm trying to retrieve a X509Certificate from an eToken Pro 72K and verify It's authenticity.

I have been able to see the certificate is present the X509Keystore.Certificates with the eToken inserted using the Keystore as new X509Keystore(StoreName.My, StoreLocation.CurrentUser).

I have a hard-coded certificate that I turn into a X509Certificate2 as:

//C# code
//HardCoded
private const string CERTIFICATE = "-----BEGIN CERTIFICATE-----\nA1UEChMHR2lybyBHSDEMMAoGA1UECxMDSStEMREwDwYDVQQDEwhHaXJMYWJlbDEe\nAround 1800 characters with new lines\n-----END CERTIFICATE-----";
private static X509Certificate2 GetIssuerCertificate()
{
    return new X509Certificate2(Encoding.UTF8.GetBytes(CERTIFICATE));
}

So to check for the certificate the code is:

//C# code
public static bool HasValidCertificate()
{
    try
    {
        // This is our valid hardcoded cert
        X509Certificate2 issuerCertificate = GetIssuerCertificate(); // The sample code above
        using (X509Store keyStore = new X509Store(StoreName.My, StoreLocation.CurrentUser))
        {
            keyStore.Open(OpenFlags.ReadOnly);
            foreach (X509Certificate2 cert in keyStore.Certificates)
            {
                try
                {
                    if (IsValidCertificate(issuerCertificate, cert))
                    {
                        return true;
                    }
                }
                catch (Exception)
                {
                    //En an error occurred with this test
                }
            }
        }
    }
    catch(Exception ex)
    {
        //An error occurred
    }
    return false;
}

I have been searching and the following close to work code I was able to assembly using different sources is the following:

//C# code
private static bool IsValidCertificate(X509Certificate2 expectedCert, X509Certificate2 testCert)
{
    X509Chain chain = new X509Chain();
       
    chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
    chain.ChainPolicy.ExtraStore.Add(expectedCert);
    chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority; //X509VerificationFlags.AllowUnknownCertificateAuthority | X509VerificationFlags.;
        
    var isValid = chain.Build(testCert);

    var chainRoot = chain.ChainElements[chain.ChainElements.Count-1].Certificate;
    isValid = isValid && chainRoot.RawData.SequenceEqual(expectedCert.RawData);
    return isValid;
}

This function returns true when checking against the eToken X509Certificate, but if I would go to the Windows Certificates, find it, and install it, it will be listed and working as well, but the eToken one contains a private key and the installed doesn't. I assume the eToken certificate is non-exportable, which is ok, the eToken allows to verify someone has the hardware device inserted, so this function should not pass the test for both certificates (the etoken one and the "installed" copy).

This sample retrieves eToken certify but doesn't validate against another one as I'm trying.

The PkCs11Interop samples doesn't explain to extract an X509 certificate if that would be the right tool.

This project allows to extract an private key from a Cert through the PkCs Interop, would I be able to use the RSA private key from the Pkcs11X509Certificate to create a X509Certificate?

This project is able to get certificates from a Card Reader but I'm completly unsure how would turn their own Certificate class implementation to the .Net one

Having the PIN allows me to obtain the private key and create a X509Certificate to test against? I though you can't get the private key as it's supposedly non exportable from the hardware but that's what the PIN is used for then?

This is a very extensive manual for PKCS11 that gets into how to use the CAPI to obtain data but I assume the PkCs11Interop wraps that over.

Is it OK to try to sign some data with one key and verify it with the other one to validate the X509Certify? I would not need to access to the private key this way. But signing random data sounds strange to me, there should be a way to just test one against the other as the IsValidCertificate is trying to do.

I have a working Java code sample that does work but it uses some functionalities I'm unable to translate and also somehow looks that I don't need as I'm able to retrieve the eToken Certify without using the PIN or any specific eToken provider.

The java code uses an specific KeyStore that looks like comes directly from the eToken. The KeyStore is obtained from the "Provider". The Provider is obtained by generating a config file with different values on the slot attribute, the content is the following:

name=eToken
library=C:\WINDOWS\system32\eTPkcs11.dll
slot=4

The generated provider is tested until one "valid" is found and then it's opened using a Pin

// Java code
/**
  * Gets Keystore
  *
  * @params null
  * @returns KeyStore
  * @throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException
  */
private KeyStore getKeyStore() 
{
    try 
    {
        OutputManager.appendInfoMessage("Getting key store");

        KeyStore keyStore = KeyStore.getInstance("PKCS11",getProvider());
        char [] pin = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', '.'}; //Some chars i replaced due security concerns
        keyStore.load(null, pin);
        OutputManager.appendInfoMessage("Returning keystore" + keyStore);

        return keyStore;
    } 
    catch (Exception e) 
    {
        OutputManager.appendErrorMessage("Keystore retrieval error",e);
    } 
    return null;
}

private Provider getProvider() throws Exception 
{
    String osName = System.getProperty("os.name" );
    if (osName.contains("Windows"))
    {
        //The first time we execute this, search for the correct slot.
        if(lastSlot == -1)
        {
            for(int i = 0 ; i < 5 ; i++)
            {
                initConfigWindows(i);
                Class<?> lclass = ClassLoader.getSystemClassLoader().getParent().loadClass("sun.security.pkcs11.SunPKCS11");
                @SuppressWarnings("rawtypes")
                Constructor constructor = lclass.getConstructor(String.class);
                Provider p = (Provider) constructor.newInstance(configName);
                if(isACorrectProvider(p))
                {
                    lastSlot = i;
                    return p;
                }
            }
            return null;
        }
        else
        {
            //Once it has already been found, just use the stored slot.
            initConfigWindows(lastSlot);
            Class<?> lclass = ClassLoader.getSystemClassLoader().getParent().loadClass("sun.security.pkcs11.SunPKCS11");
            @SuppressWarnings("rawtypes")
            Constructor constructor = lclass.getConstructor(String.class);
            return (Provider) constructor.newInstance(configName);
        }
    }
    else
    {
        throw new Exception("Unsupported architecture");
    }
}

private void initConfigWindows(int slot) throws IOException
{
    OutputManager.appendInfoMessage("Token init windows config");
    StringBuffer sb = new StringBuffer();
    sb.append("name=eToken\r\n");
    String osArch = "system32";
    String system32LibPath = System.getenv("SystemRoot") + "\\" + osArch + "\\eTPkcs11.dll";
    sb.append("library="+system32LibPath+"\r\n");
    sb.append("slot=" + slot + "\r\n");
    String dataConf = sb.toString();
    OutputManager.appendInfoMessage("Configuration used:\n" + dataConf + "\n");
    String path = System.getProperty("java.io.tmpdir") + "pkcs11Conf";
    File directorio = new File(path);
    directorio.mkdirs();
    //we generate dynamically the config file (with name, library and slot attributes, necesarry on windows)
    Writer log = new FileWriter(path + "\\pkcs11.cfg", false);
    log.append(dataConf);
    log.close();
    //we set the config path
    configName = path + "\\pkcs11.cfg";
}

private boolean isACorrectProvider(Provider p) {
    try {
        KeyStore.getInstance("PKCS11",p);
        return true;
    } 
    catch (Exception e) 
    {
        return false;
    }
}

Later on the Certificates on the KeyStore are iterated and checked using the following method:

// Java code
private static void checkCertificates(X509Certificate issuerCert, X509Certificate c1) throws Exception
{
    TrustAnchor anchor = new TrustAnchor(issuerCert, null);
    Set<TrustAnchor> anchors = Collections.singleton(anchor);
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    List<Certificate> list = Arrays.asList(new Certificate[] { c1 });
    CertPath path = cf.generateCertPath(list);
    PKIXParameters params = new PKIXParameters(anchors);
    params.setRevocationEnabled(false);
    CertPathValidator validator = CertPathValidator.getInstance("PKIX");
    validator.validate(path, params);//If not valid will throw Exception
    OutputManager.appendMessage("VALID");
}

As stated above, the code I made doesn't behave the same way as this one when checking the certificate.

Furthermore, I don't understand why I don't need the Pin as this Java code does.

On one side, i have been able to sign data using the eToken and pin without leaving the .Net environment:

//C# code
public static void TestTrustedCert()
{
    https://groups.google.com/forum/#!topic/dotnetdevelopment/AjUNkpVY3GY
    // Create a new CspParameters object that identifies a
    // Smart Card CryptoGraphic Provider.
    // The 1st parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider Types.
    // The 2nd pGetEtokenCertarameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
    CspParameters csp = new CspParameters(1, "eToken Base Cryptographic Provider");
    csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
    
    //password do token
    string pin = "myPW";
    var pwd = new SecureString();
    for (var i = 0; i < pin.Length; i++)
        pwd.AppendChar(pin[i]);
    csp.KeyPassword = pwd;
    csp.KeyNumber = (int)KeyNumber.Signature;

    // Initialize an RSACryptoServiceProvider object using
    // the CspParameters object.
    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);

    // Create some data to sign.
    byte[] data = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 };

    Console.WriteLine("Data         : " + BitConverter.ToString(data));

    // Sign the data using the Smart Card CryptoGraphic Provider.
    byte[] sig = rsa.SignData(data, "SHA1");

    Console.WriteLine("Signature    : " + BitConverter.ToString(sig));
    
    // Verify the data using the Smart Card CryptoGraphic Provider.
    bool verified = rsa.VerifyData(data, "SHA1", sig);

    Console.WriteLine("Verified     : " + verified);
}

Also I have been able to do the same though the PKCS11Interop:

//C# code
using (ISession session = slot.OpenSession(SessionType.ReadOnly))
{
    session.Login(CKU.CKU_USER, Password);

    List<IObjectAttribute> objectAttributes = new List<IObjectAttribute>();
    objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_CLASS, CKO.CKO_DATA));
    objectAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true));

    // Find all objects that match provided attributes
    List<IObjectHandle> foundObjects = session.FindAllObjects(objectAttributes);
    byte[] sourceData = ConvertUtils.Utf8StringToBytes("Hello world");
    // Generate key pair
    IObjectHandle publicKey = null;
    IObjectHandle privateKey = null;
    IMechanism mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_SHA1_RSA_PKCS);
    
    byte[] signature = session.Sign(mechanism, privateKey, sourceData);
    // Do something interesting with signature
    // Verify signature
    bool isValid = false;
    session.Verify(mechanism, publicKey, sourceData, signature, out isValid);

    session.Logout();

}

But i have no idea how the hardcoded certificate I have been provided matches here

来源:https://stackoverflow.com/questions/62713161/how-verify-etoken-x509certificate-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!