My goal is to populate the Subject Key Identifier Extension (2.5.29.14) for a certificate using Microsoft CNG. I did it previously with Microsoft CAPI but the function I used:
CryptHashPublicKeyInfo
https://msdn.microsoft.com/en-us/library/windows/desktop/aa380204(v=vs.85).aspx
Is now depreciated. CNG has no such method. However, the descripion for CryptHashPublicKeyInfo in the link above says that they do a SHA1 hash of the public key information. So I did a SHA1 hash of the public key bytes in CNG on the same data in CryptHashPublicKeyInfo (CAPI) and the two hashes are different. I need to resolve this difference. To be clear, my logic is running on the same public key from the same CSR.
Details in RFC 5280 seem to confirm what Microsoft says: https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.2
(1) The keyIdentifier is composed of the 160-bit SHA-1 hash of the value of the BIT STRING subjectPublicKey (excluding the tag, length, and number of unused bits).
Cooper, et al. Standards Track [Page 28] RFC 5280 PKIX Certificate and CRL Profile
May 2008(2) The keyIdentifier is composed of a four-bit type field with the value 0100 followed by the least significant 60 bits of the SHA-1 hash of the value of the BIT STRING subjectPublicKey (excluding the tag, length, and number of unused bits).
^I'm guessing Microsoft is doing case #1.
Here is my CAPI code:
//depreciated (CAPI)
//get data for subject key identifier
//get length
HCRYPTPROV hHashProv = NULL;
if (!CryptHashPublicKeyInfo(
hHashProv,
CALG_SHA1, //sha1
0,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
&serverCertInfo.SubjectPublicKeyInfo,
NULL,
&dwSubjectKeyIdentifier
))
{
throw std::runtime_error("Unable to get length of public key info hash");
}
//alocate data buffer
pbSubjectKeyIdentifier = (LPBYTE)LocalAlloc(0, dwSubjectKeyIdentifier);
//fill data buffer with subject key identifier
if (!CryptHashPublicKeyInfo(
hHashProv,
CALG_SHA1, //sha1
0,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
&serverCertInfo.SubjectPublicKeyInfo,
pbSubjectKeyIdentifier,
&dwSubjectKeyIdentifier
))
{
throw std::runtime_error("Unable to fill public key info hash");
}
CRYPT_DATA_BLOB skiBlob;
skiBlob.cbData = dwSubjectKeyIdentifier;
skiBlob.pbData = pbSubjectKeyIdentifier;
//encode subject key identifier extension
LPBYTE pbEncodeSKI = NULL;
DWORD dwEncodedSKI;
if (!CryptEncodeObject(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
szOID_SUBJECT_KEY_IDENTIFIER,
(void*)&skiBlob,
NULL,
&dwEncodedSKI
))
{
throw std::runtime_error("Unable to get length to encode extension: subject key identifier");
}
pbEncodeSKI = (LPBYTE)LocalAlloc(0, dwEncodedSKI);
if (!CryptEncodeObject(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
szOID_SUBJECT_KEY_IDENTIFIER,
(void*)&skiBlob,
pbEncodeSKI,
&dwEncodedSKI
))
{
throw std::runtime_error("Unable to encode extension: subject key identifier");
}
This produces this value in the extension (same public key in certificate request): 9d77f29e4fa15e46237d59a7c00efde9d286b9dc
This is my CNG code:
NTSTATUS statusBCryptOpenAlgorithmProvider;
NTSTATUS statusBCryptHash;
BCRYPT_ALG_HANDLE hHashAlg;
LPBYTE pbHash;
DWORD dwHash = 20;
LPSTR lpstrPublicKeyEncoded;
DWORD dwPublicKeyEncoded;
CRYPT_DATA_BLOB skiBlob;
if (!CryptBinaryToStringA(
infoPublicKey.PublicKey.pbData,
infoPublicKey.PublicKey.cbData,
CRYPT_STRING_BINARY,
NULL,
&dwPublicKeyEncoded
))
{
throw std::runtime_error("Error getting length of encoded binary string (CryptBinaryToString)");
}
lpstrPublicKeyEncoded = (LPSTR)LocalAlloc(0, dwPublicKeyEncoded);
if (!CryptBinaryToStringA(
infoPublicKey.PublicKey.pbData,
infoPublicKey.PublicKey.cbData,
CRYPT_STRING_BINARY,
lpstrPublicKeyEncoded,
&dwPublicKeyEncoded
))
{
LocalFree(lpstrPublicKeyEncoded);
throw std::runtime_error("Error encoding binary string (CryptBinaryToString)");
}
statusBCryptOpenAlgorithmProvider = BCryptOpenAlgorithmProvider(
&hHashAlg,
BCRYPT_SHA1_ALGORITHM,
MS_PRIMITIVE_PROVIDER,
0
);
if (0 != statusBCryptOpenAlgorithmProvider)
{
LocalFree(lpstrPublicKeyEncoded);
throw std::runtime_error("Error opening SHA1 algorithm provider (BCryptOpenAlgorithmProvider)");
}
pbHash = (LPBYTE)LocalAlloc(0, dwHash);
statusBCryptHash = BCryptHash(
hHashAlg,
NULL,
0,
(BYTE*)lpstrPublicKeyEncoded,
dwPublicKeyEncoded,
pbHash,
dwHash
);
if (0 != statusBCryptHash)
{
LocalFree(lpstrPublicKeyEncoded);
BCryptCloseAlgorithmProvider(hHashAlg, 0);
LocalFree(pbHash);
throw std::runtime_error("Error hashing public key (BCryptHash)");
}
skiBlob.pbData = pbHash;
skiBlob.cbData = dwHash;
BCryptCloseAlgorithmProvider(hHashAlg, 0);
LocalFree(pbHash);
LocalFree(lpstrPublicKeyEncoded);
//encode subject key identifier extension
LPBYTE pbEncodeSKI = NULL;
DWORD dwEncodedSKI;
if (!CryptEncodeObject(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
szOID_SUBJECT_KEY_IDENTIFIER,
(void*)&skiBlob,
NULL,
&dwEncodedSKI
))
{
throw std::runtime_error("Unable to get length to encode extension: subject key identifier");
}
pbEncodeSKI = (LPBYTE)LocalAlloc(0, dwEncodedSKI);
if (!CryptEncodeObject(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
szOID_SUBJECT_KEY_IDENTIFIER,
(void*)&skiBlob,
pbEncodeSKI,
&dwEncodedSKI
))
{
throw std::runtime_error("Unable to encode extension: subject key identifier");
}
This produces this SKI value (different): 210816297e8e76879f99ec4762452b5d38967b5b
Any clue what I am doing wrong in the CNG code sample? There is apparently a magic sequence of calls but I don't know what it is.