2
votes

I'm using .NET and OpenSSL to generate CA and client certificates.

The following piece of code generates a client certificate and signs it with the CA certificate:

//Generating public/private key pair
var userRsa = new RSA();
userRsa.GenerateKeys(4096, 3, null, null);

var userCryptoKey = new CryptoKey(userRsa);


//Creating certificate signing request
var userSubjectName = new X509Name();
userSubjectName.Common = "Some Value";
userSubjectName.OrganizationUnit = "Some Value";
userSubjectName.Organization = "Some Value";
userSubjectName.StateOrProvince = "Some Value";
userSubjectName.StateOrProvince = "Some Value";
userSubjectName.Country = "US";

var userRequest = new X509Request(3, userSubjectName, userCryptoKey);
userRequest.Sign(userCryptoKey, MessageDigest.SHA1);


//Signing Certificate by authority (this part of code will be located in the separate CA Authority service)
X509Certificate userX509Certificate = signingX509CertificateAuthority.ProcessRequest(userRequest, DateTime.Now, DateTime.Now.AddYears(1), MessageDigest.SHA1);
userX509Certificate.AddExtension(new X509Extension(signingX509Certificate, userX509Certificate, "basicConstraints", true, "CA:false"));
userX509Certificate.AddExtension(new X509Extension(signingX509Certificate, userX509Certificate, "keyUsage", true, "nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, encipherOnly, decipherOnly, keyAgreement"));
userX509Certificate.AddExtension(new X509Extension(signingX509Certificate, userX509Certificate, "extendedKeyUsage", true, "clientAuth"));
userX509Certificate.AddExtension(new X509Extension(signingX509Certificate, userX509Certificate, "nsComment", true, "OpenSSL Generated Certificate"));
userX509Certificate.AddExtension(new X509Extension(signingX509Certificate, userX509Certificate, "subjectKeyIdentifier", true, "hash"));
userX509Certificate.AddExtension(new X509Extension(signingX509Certificate, userX509Certificate, "authorityKeyIdentifier", true, "keyid,issuer:always"));
userX509Certificate.AddExtension(new X509Extension(signingX509Certificate, userX509Certificate, "issuerAltName", true, "issuer:copy"));
userX509Certificate.AddExtension(new X509Extension(signingX509Certificate, userX509Certificate, "crlDistributionPoints", true, "URI:http://ok/certEnroll/ok-ca.crl"));
userX509Certificate.Sign(signingCryptoKey, MessageDigest.SHA1);


//CA chain
var userX509Chain = new X509Chain();
userX509Chain.Add(rootX509Certificate);
userX509Chain.Add(signingX509Certificate);


//Creating Personal Information Exchange (PFX) Certificate
var userPkcs12 = new PKCS12("1", "Olexiy Kubliy", userCryptoKey, userX509Certificate, new Stack<X509Certificate>(), PKCS12.PBE.SHA1_3DES, PKCS12.PBE.SHA1_3DES, 1);

This code works fine, but there is one issue. I want to generate a key and PFX on smartcard, so the certificate generation steps should be the next:

1.) Generate public/private key pair on smart card;

2.) Create certificate signign request over OpenSLL and sign it with the private key on smartcard;

3.) Send certificate signing request to our CA Authority service and sign it with the CA certificate;

4.) Retrieve certificate (signed by CA Authority), sign it with the private key stored on smartcard and generate pfx on smartcard;

To work with smartcards I used the Microsoft CryptoAPI and NCryptoki. NET library, but the most I could do is to generate the public/private key pair on the smartcard and import pfx certificate with CA chain on the smartcard. I can not sign a certificate request and certificate retrieved from own CA authority.

Using Microsoft CryptoAPI:

const string provider = "etoken Base Cryptographic Provider";
const Int32 AT_KEYEXCHANGE = 1;
const Int32 PRIVATEKEYBLOB = 0x7;
const int KP_CERTIFICATE = 26;

const uint type = PROV_RSA_FULL;
IntPtr cardCryptoProvider = IntPtr.Zero;
if (!Win32.CryptAcquireContext(ref cardCryptoProvider, null, provider, type, Win32.CRYPT_NEWKEYSET))
    showWin32Error(Marshal.GetLastWin32Error());

const string fileName = @"D:\test.pfx";
var fileinfo = new FileInfo(fileName);
var br = new BinaryReader(fileinfo.OpenRead());
byte[] Bytes = new byte[fileinfo.Length];
br.Read(Bytes, 0, (int)fileinfo.Length);
br.Close();

IntPtr buffer = Marshal.AllocHGlobal(Bytes.Length);
Marshal.Copy(Bytes, 0, buffer, Bytes.Length);

var cryptDataBlob = new Win32.CRYPT_DATA_BLOB();
cryptDataBlob.pbData = buffer;
cryptDataBlob.cbData = Bytes.Length;

IntPtr store = Win32.PFXImportCertStore(ref cryptDataBlob, "1", Win32.CRYPT_EXPORTABLE | Win32.CRYPT_USER_KEYSET);

IntPtr currentCertContext = IntPtr.Zero;
X509Certificate2 c = null;

do
{
    currentCertContext = Win32.CertEnumCertificatesInStore(store, currentCertContext);
    if (currentCertContext == IntPtr.Zero)
        break;

    uint sizeOfData = 0;

    if (!Win32.CertGetCertificateContextProperty(currentCertContext, 2, IntPtr.Zero, ref sizeOfData))
    {
        showWin32Error(Marshal.GetLastWin32Error());
        continue;
    }

    IntPtr pData = Marshal.AllocHGlobal((int)sizeOfData);

    if (!Win32.CertGetCertificateContextProperty(currentCertContext, 2, pData, ref sizeOfData))
        showWin32Error(Marshal.GetLastWin32Error());

    var pKeyProvInfo = (Win32.CRYPT_KEY_PROV_INFO)Marshal.PtrToStructure(pData, typeof(Win32.CRYPT_KEY_PROV_INFO));

    int cryptAcquireCertificatePrivateKeyFlags = AT_KEYEXCHANGE;
    IntPtr cryptoProvider = IntPtr.Zero;
    bool callerFree = false;

    if (!Win32.CryptAcquireCertificatePrivateKey(currentCertContext, 0, IntPtr.Zero,
        ref cryptoProvider, ref cryptAcquireCertificatePrivateKeyFlags, ref callerFree))
        showWin32Error(Marshal.GetLastWin32Error());

    IntPtr cryptKeyHandle = IntPtr.Zero;
    if (!Win32.CryptGetUserKey(cryptoProvider, AT_KEYEXCHANGE, ref cryptKeyHandle))
        showWin32Error(Marshal.GetLastWin32Error());


    uint pkSize = 0;
    if (!Win32.CryptExportKey(cryptKeyHandle, IntPtr.Zero, PRIVATEKEYBLOB, 0, null, ref pkSize))
        showWin32Error(Marshal.GetLastWin32Error());

    byte[] pPk = new byte[pkSize];

    if (!Win32.CryptExportKey(cryptKeyHandle, IntPtr.Zero, PRIVATEKEYBLOB, 0, pPk, ref pkSize))
        showWin32Error(Marshal.GetLastWin32Error());

    IntPtr importedKey = IntPtr.Zero;
    if (!Win32.CryptImportKey(cardCryptoProvider, pPk, (uint)pPk.Length, IntPtr.Zero, 0, ref importedKey))
        showWin32Error(Marshal.GetLastWin32Error());

    var cert = new X509Certificate(currentCertContext);

    if (!Win32.CryptSetKeyParam(importedKey, KP_CERTIFICATE, cert.GetRawCertData(), 0))
        showWin32Error(Marshal.GetLastWin32Error());

} while (currentCertContext != IntPtr.Zero);
if (cardCryptoProvider != IntPtr.Zero)
    Win32.CryptReleaseContext(cardCryptoProvider, 0);

using NCryptoki. NET library:

Cryptoki cryptoki = new Cryptoki(@"C:\Windows\System32\eTPKCS11.dll");
cryptoki.Initialize();

Slot slot = null;

foreach (Slot slot1 in slots)
{
    if (slot1.IsTokenPresent)
    {
        slot = slot1;
        break;
    }
}

Token token = slot.Token;
Session session = token.OpenSession(Session.CKF_SERIAL_SESSION | Session.CKF_RW_SESSION, null, null);

if (session.Login(Session.CKU_USER, "12345678") != 0)
{
    Console.WriteLine("Wrong PIN");
    return;
}

var certificate = new X509Certificate2(@"C:\certificate.pfx", "1", X509KeyStorageFlags.Exportable);            
var keyPair = certificate.PrivateKey as RSA;

var collection = new X509Certificate2Collection();
collection.Import(@"C:\certificate.pfx", "1", X509KeyStorageFlags.PersistKeySet);            

RSAParameters keyParams = keyPair.ExportParameters(true);

var template = new CryptokiCollection();
template.Add(new ObjectAttribute(ObjectAttribute.CKA_CLASS, CryptokiObject.CKO_PRIVATE_KEY));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_KEY_TYPE, Key.CKK_RSA));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_EXTRACTABLE, true));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_MODULUS, keyParams.Modulus));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_PUBLIC_EXPONENT, keyParams.Exponent));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_PRIVATE_EXPONENT, keyParams.D));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_PRIME_1, keyParams.P));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_PRIME_2, keyParams.Q));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_EXPONENT_1, keyParams.DP));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_EXPONENT_2, keyParams.DQ));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_COEFFICIENT, keyParams.InverseQ));            
template.Add(new ObjectAttribute(ObjectAttribute.CKA_LABEL, "dddd"));
template.Add(new ObjectAttribute(ObjectAttribute.CKA_TOKEN, true));            
session.Objects.Create(template);

foreach (X509Certificate2 x509Certificate2 in collection)
{
    var template1 = new CryptokiCollection();
    template1.Add(new ObjectAttribute(ObjectAttribute.CKA_CLASS, CryptokiObject.CKO_CERTIFICATE));
    template1.Add(new ObjectAttribute(ObjectAttribute.CKA_PRIVATE, false));
    template1.Add(new ObjectAttribute(ObjectAttribute.CKA_CERTIFICATE_CATEGORY, x509Certificate2.HasPrivateKey ? 0 : 2));
    template1.Add(new ObjectAttribute(ObjectAttribute.CKA_CERTIFICATE_TYPE, Certificate.CKC_X_509));
    template1.Add(new ObjectAttribute(ObjectAttribute.CKA_VALUE, x509Certificate2.RawData));
    template1.Add(new ObjectAttribute(ObjectAttribute.CKA_SUBJECT, x509Certificate2.SubjectName.RawData));
    template1.Add(new ObjectAttribute(ObjectAttribute.CKA_TOKEN, true));

    session.Objects.Create(template1);
}

Can anybody help me with signing a certificate request(generated over Open SLL) and signing certificate retrieved from own CA Authority with the private key stored on smartcard?

Using NCryptoki Sign method for signing some text:

Cryptoki cryptoki = new Cryptoki(@"C:\Windows\System32\eTPKCS11.dll");
cryptoki.Initialize();
Slot slot = _cryptoki.ActiveSlots.FirstOrDefault();
Session session = slot.Token.OpenSession(Session.CKF_SERIAL_SESSION | Session.CKF_RW_SESSION, null, null);
session.Login(Session.CKU_USER, "12345678")

CryptokiCollection templatePub = new CryptokiCollection();
templatePub.Add(new ObjectAttribute(ObjectAttribute.CKA_CLASS, CryptokiObject.CKO_PUBLIC_KEY));
templatePub.Add(new ObjectAttribute(ObjectAttribute.CKA_TOKEN, true));
templatePub.Add(new ObjectAttribute(ObjectAttribute.CKA_PRIVATE, true));
templatePub.Add(new ObjectAttribute(ObjectAttribute.CKA_LABEL, "My Key"));
templatePub.Add(new ObjectAttribute(ObjectAttribute.CKA_ID, "1"));
templatePub.Add(new ObjectAttribute(ObjectAttribute.CKA_MODULUS_BITS, 1024));

CryptokiCollection templatePri = new CryptokiCollection();
templatePri.Add(new ObjectAttribute(ObjectAttribute.CKA_CLASS, CryptokiObject.CKO_PRIVATE_KEY));
templatePri.Add(new ObjectAttribute(ObjectAttribute.CKA_TOKEN, true));
templatePri.Add(new ObjectAttribute(ObjectAttribute.CKA_PRIVATE, true));
templatePri.Add(new ObjectAttribute(ObjectAttribute.CKA_LABEL, "My Key"));
templatePri.Add(new ObjectAttribute(ObjectAttribute.CKA_ID, "1"));

Key[] keys = session.GenerateKeyPair(Mechanism.RSA_PKCS_KEY_PAIR_GEN, templatePub, templatePri);
var privateKey = (RSAPrivateKey)keys[1];

const string text = "Some Value";
byte[] textBytes = Encoding.ASCII.GetBytes(text);
int result = session.SignInit(Mechanism.SHA1_RSA_PKCS, privateKey);
byte[] signature = session.Sign(textBytes);
1

1 Answers

2
votes

You are misunderstanding certain aspects of your task.

First, you do not need to re-sign the certificate received from a CA with your private key (step 4). It will be already signed by the CA's private key.

Second, PFX is just a format in which certificate can be stored. PFX is used to keep password-encrypted certificates and private keys in files; it is not used when storing certificates on smart cards.

Third, hardware tokens usually do not allow exporting private keys, that is, you won't be able to create a certificate+key PFX file if the key pair has been generated on the token.

Now to your question.

As far as I'm aware, OpenSSL doesn't support accessing keys stored on HSM's, i.e. you just won't be able to get the private key in order to sign the request. Yet, such keys can be accessed with CryptoAPI just like usual system-based keys if the vendor of the token ships an appropriate CSP (which is actually a layer between CryptoAPI and low-level token driver). However, as such keys are non-exportable, you will only be able to use them with CryptoAPI functions, referencing them by CryptoAPI key handles. At the same time, CryptoAPI's certificate enrollment control does not support generation of certificate requests from existing private keys (brand new keypairs are always generated).

That is, you have the following three options:

  1. CryptoAPI-based. Form certificate request BLOB (PKCS#10) yourself and sign it with the use of CryptSignHash() method; then append the created signature to the BLOB. The keypair can be generated prior to forming the request either by CryptGenKey() method or via other means (e.g. some PKCS#11 component or tool).

  2. OpenSSL-based. Generate a brand new keypair (you will be forced to store it in files) and certificate request. Import the keypair to the token. Destroy the private key stored in file.

  3. The third. Use some PKCS#11 library to generate a keypair and sign the request. As with option 1, you will need to form the request yourself.