Tuesday, November 2, 2010

How to use certificate from disk with MS CryptoAPI

This article describes, how to digitaly sign data using a certificate which is stored outside the Windows Certificate Store. So it may be distributed as external file or is hardcoded into your application.

Provided code samples are for demonstration purposes only and are not intended to be used in a production system - you should at least add proper error handling and release allocated memory.

Preparing key-pair with self-signed certificate

A key-pair can be generated using the OpenSSL. List of commands used for generation follows, many special articles with details how to generate certificate can be found on internet, so I don't mention any details about it here. On the projects website you can find also links to binaries for appropriate platform.
  • Generate private key of desired length (1024 bits)
    openssl genrsa -des3 -out server.key 1024
  • Create request for self-signed certificate
    openssl req -new -key server.key -out server.csr
  • Remove password from the private key
    openssl rsa -in server.key -out server2.key
  • Self-sign the certificate request (-days is expiration in days)
    openssl x509 -req -days 365 -in server.csr -signkey server2.key -out server.crt
  • Convert output to PKCS#12 format which we can use in code (-keysig parameter allows us to use key-pair for signing)
    openssl pkcs12 -export -in server.crt -inkey server2.key –keysig -out c:\mykey.p12

Signing data

Final solution is quite easy as you can see in the code below. The only important thing is, that your key-pair must be saved in PKCS#12 form (.p12 extension).
CRYPT_DATA_BLOB data;

// load key-pair from disk to memory. This can be quite
// easily changed to have key-pair hardcoded
FILE *fIn = fopen("C:\\mykey.p12", "rb");
fseek(fIn, 0, SEEK_END);
data.cbData = ftell(fIn);
fseek(fIn, 0, SEEK_SET);
data.pbData = (BYTE *)malloc(pData.cbData);
fread(data.pbData, 1, pData.cbData, fIn);
fclose(fIn);

// Convert key-pair data to the in-memory certificate store
// If the P12 file is protected with a password, pass it to
// the function    
HCERTSTORE hCertStore = PFXImportCertStore(&data, L"P12ExportPassword", 0);

// If in-memory certificate store wasn't initialized, try
// empty password (NULL has different meaning than empty string)
if (!hCertStore)
  hCertStore = PFXImportCertStore(&data, L"", 0);
if (!hCertStore)
  hCertStore = PFXImportCertStore(&data, NULL, 0);

// Find the certificate in P12 file (we expect there is only one)    
PCCERT_CONTEXT hContext = CertFindCertificateInStore(hCertStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL);

BOOL bFreeHandle;
HCRYPTPROV hProv;
DWORD dwKeySpec;
// acquire private key to this certificate, returned hProv handle must be closed
// if bFreeHandle is TRUE    
BOOL bResult = CryptAcquireCertificatePrivateKey(hContext, 0, NULL, &hProv, &dwKeySpec, &bFreeHandle);

const char szBuffToSign[] = "Hello world!";    
DWORD dwSize = 0;
And now, based on our needs sign data as a message, which allow us to include selected certificates:
CRYPT_SIGN_MESSAGE_PARA p;
memset(&p, 0, sizeof(CRYPT_SIGN_MESSAGE_PARA));
p.cbSize = sizeof(CRYPT_SIGN_MESSAGE_PARA);
p.dwMsgEncodingType = PKCS_7_ASN_ENCODING | X509_ASN_ENCODING;
p.pSigningCert = hContext;
p.HashAlgorithm.pszObjId = szOID_OIWSEC_sha1;
p.cMsgCert = 1;
p.rgpMsgCert = &hContext;
    
DWORD dwSizes[] = {
                    strlen(szBuffToSign)
                  };

const BYTE *byMessageArray[] = { 
                    (const BYTE *) szBuffToSign
};

bResult = CryptSignMessage(&p, FALSE, 1, byMessageArray,  dwSizes, NULL, &dwSize);
    
BYTE *pBuff = (BYTE *) malloc(dwSize);
memset(pBuff, 0, dwSize);
bResult = CryptSignMessage(&p, FALSE, 1, byMessageArray, dwSizes, pBuff, &dwSize);
Or generate a has from data and sign this hash:
HCRYPTHASH hHash;
CryptCreateHash(hProv, CALG_SHA1, NULL, 0, &hHash);
CryptHashData(hHash, (const BYTE *)szBuffToSign, strlen(szBuffToSign), 0);    
CryptSignHash(hHash, AT_SIGNATURE, "", 0, NULL, &dwSize);

BYTE *pBuff = (BYTE *) malloc(dwSize);
memset(pBuff, 0, dwSize);
CryptSignHash(hHash, AT_SIGNATURE, "", 0, pBuff, &dwSize);

Links