Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to sign with the current token certificate #158

Closed
arsu-leo opened this issue Jul 6, 2020 · 2 comments
Closed

How to sign with the current token certificate #158

arsu-leo opened this issue Jul 6, 2020 · 2 comments

Comments

@arsu-leo
Copy link

arsu-leo commented Jul 6, 2020

The current sample for signing creates it's own key pair:

// Generate key pair
IObjectHandle publicKey = null;
IObjectHandle privateKey = null;
Helpers.GenerateKeyPair(session, out publicKey, out privateKey);`

Of course, as I open the session for read, the last line fails.

Supposedly I already have certificate on my eToken, how can I use it to sign data?

The root cause that took me here:

Some long story that maybe allows me to change the implementation I'm doing, this may be offtopic on the issue but may direct me to the right solution instead of the workaround I'm implementing.
I have a working sample code in Java that "verifies" the eToken certificate. Using the same dll I'm using, it finds the right KeyStore by checking every slot and using the pin. Then it lists all the certificate aliases and for each one it retrieves the certificate and does the following check:

private static void checkCertificates(X509Certificate cert) throws Exception
{
  //Setup path
  List<Certificate> list = Arrays.asList(new Certificate[] { cert });
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  CertPath path = cf.generateCertPath(list);
  
  //Setup params
  //Use the issuer cert
  TrustAnchor anchor = new TrustAnchor(getIssuerCertificate(), null);
  Set<TrustAnchor> anchors = Collections.singleton(anchor);
  PKIXParameters params = new PKIXParameters(anchors);
  params.setRevocationEnabled(false);

  CertPathValidator validator = CertPathValidator.getInstance("PKIX");
  //If not valid it throws an Exception
  validator.validate(path, params);
}

//HardCoded
private static final String CERTIFICATE = "-----BEGIN CERTIFICATE-----\nMIIE9[...VERY LONG STRING ~1800 chars...]jCCA96gAwIVERYk/crWij9o=\n-----END CERTIFICATE-----";
private X509Certificate IssuerCert;

private X509Certificate getIssuerCertificate() throws Exception
{
  if(IssuerCert == null)
  {
    X509Certificate cert = null;
    ByteArrayInputStream bais = new ByteArrayInputStream(CERTIFICATE.getBytes());
    CertificateFactory cf = CertificateFactory.getInstance("X509");
    IssuerCert = (X509Certificate)cf.generateCertificate(bais);
  }
  return IssuerCert;
}

I was able to create this code using different resources that I would use to check all the Windows KeyStore certificates:

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; 

    bool isValid = chain.Build(testCert);

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

    return isValid;
}

I would call that function using the certs obtained from the WindowsKeystore. Note that here I would not use the PIN as the sample Java code does.
It worked good until I discovered that if I find the hardware cert on the windows certificates list and installing it, the function would give true, but you could not use this newly installed certificate as It doesn't includes keys.

So I did some workaround. Sign some random data using .Net CryptoServiceProvider:

private const string ETOKEN_PROVIDER_NAME = @"eToken Base Cryptographic Provider";
private static RSACryptoServiceProvider GetEtokenProvider(string password)
{
    //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_PROVIDER_NAME)
    {
        Flags = CspProviderFlags.UseDefaultKeyContainer
    };

    var pwd = new SecureString();
    for (var i = 0; i < password.Length; i++)
        pwd.AppendChar(password[i]);
    csp.KeyPassword = pwd;
    csp.KeyNumber = (int)KeyNumber.Signature;

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

I then could sign data using that RSACryptoServiceProvider and I would get the public key from there and hard code it into my code to verify different tokens but this code prompts the user with the underliing software (Safenet Authentification Client) if the hardware is not present. Also if I would not provide the password, it will prompt the user as well. I want to avoid any kind of user interaction. "Hardware is present && can sign && can verify the signature with my hardcoded pub key" is success, else fail. Possibly, special message for case "Hardware is not present".

Pkcs11Interop allows me to test if token present and use the password right away but don't find in the samples how to use the existing certificate in the hardware to do the really intended verification or to just sign data, the samples generates new pairs into the device and I think this operation should be read only.

Thanks in advance. This an awesome and very complete lib

@arsu-leo
Copy link
Author

arsu-leo commented Jul 6, 2020

Following the "I sign some data and verify the signature" to verify the cert solution, I was actually able to create multiple signatures for each key found through this code (I create multiple I can't know in advance which one will work):

public static List<IObjectHandle> FindPriv(ISession session)
{
    List<IObjectAttribute> privKeySearchTemplate = new List<IObjectAttribute>();
    privKeySearchTemplate.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PRIVATE_KEY));
    privKeySearchTemplate.Add(new ObjectAttribute(CKA.CKA_KEY_TYPE, CKK.CKK_RSA));
            
    List<IObjectHandle> foundObjects = session.FindAllObjects(privKeySearchTemplate);

    return foundObjects;
}

Later on, verifying the data against a hard-coded public key I extracted from testing the GetEtokenProvider method on the post befor, It did actually work.

I wanted to verify the signature before returning it, but I get zero results with the following search:

public static List<IObjectHandle> FindPub(ISession session)
{
    List<IObjectAttribute> privKeySearchTemplate = new List<IObjectAttribute>();
    privKeySearchTemplate.Add(new ObjectAttribute(CKA.CKA_CLASS, CKO.CKO_PUBLIC_KEY));

    List<IObjectHandle> foundObjects = session.FindAllObjects(privKeySearchTemplate);
    return foundObjects; // returns 0 list
}

Is there any way I can verify the signed data then?

@jariq
Copy link
Member

jariq commented Jul 25, 2020

If the key already exists on your device, then you can acquire its object handle with Session::Find* methods.

It is highly recommended that before you start using Pkcs11Interop you get familiar at least with "Chapter 2 - Scope", "Chapter 6 - General overview" and "Chapter 10 - Objects" of PKCS#11 v2.20 specification (or equivalent chapters of any previous or subsequent specification version).

@jariq jariq closed this as completed Jul 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants