How to Use Certificate from Disk with Microsoft CryptoAPI
How to use certificate from disk with Microsoft CryptoAPI
This article describes how to digitally 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.
The 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. A list of commands used for generation follows, many special articles with details on how to generate certificate can be found on the internet, so I don't mention any details about it here. On the projects website, you can also find links to binaries for the 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
The 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 allows 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 hash 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);