Signing with CryptoAPI using Certificate and Detached Private Key

Use of separated certificate and private key might be confusing without understanding how parts or CryptoAPI are related one to another. Apparently, CryptSignMessage and friends require private key in order to create a digital signature.

It is not a problem when private key resides right in the signing certificate (such as, for example, imported with PFXImportCertStore):

ZeroMemory(&Parameters, sizeof Parameters);
Parameters.cbSize = sizeof Parameters;
Parameters.dwMsgEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
Parameters.pSigningCert = CertificateContext; // <<--- Certificate with Private Key
// ...

Things get more complicated when the certificate does not contain the private key. API’s error is CRYPT_E_NO_KEY_PROPERTY which is described as follows:

“The pSigningCert in *pSignPara does not have a CERT_KEY_PROV_INFO_PROP_ID or CERT_KEY_CONTEXT_PROP_ID property.”

CERT_KEY_PROV_INFO_PROP_ID is a property to be defined with certificate context and it expects a structure of type CRYPT_KEY_PROV_INFO. The other one, CERT_KEY_CONTEXT_PROP_ID takes a CERT_KEY_CONTEXT structure.

Even though described as “The CRYPT_KEY_PROV_INFO structure contains information about a key container within a cryptographic service provider (CSP).” and “The CERT_KEY_CONTEXT structure contains data associated with a CERT_KEY_CONTEXT_PROP_ID property.” respectively, it is not well clear to see where the key is attached exactly.

The missing part is that the private key is not something to be provided explicitly. Instead, the key is to be pre-loaded into provider and then this provider is associated with CERT_KEY_CONTEXT structure, which goes as a property value, which in turn is added to signing certificate context, whcih finally gets used for creating a digital signature.

__E(CryptStringToBinary(sPrivateKeyBlob, (DWORD) wcslen(sPrivateKeyBlob), CRYPT_STRING_BASE64_ANY, pnData, &nDataSize, NULL, NULL));
// ...
CCryptProvider Provider;
__E(Provider.AcquireContext(NULL, NULL, PROV_RSA_FULL, 0));
CCryptKey Key;
Key.Attach(Provider.ImportKey(pnData, nDataSize));
// ...
ZeroMemory(&KeyContext, sizeof KeyContext);
KeyContext.cbSize = sizeof KeyContext;
KeyContext.hCryptProv = Provider; // <<--- Here we go, ooh
KeyContext.dwKeySpec = AT_SIGNATURE;
CertificateContext.SetProperty(CERT_KEY_CONTEXT_PROP_ID, &KeyContext);

Leave a Reply