0

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.

Community
  • 1
  • 1
Timothy John Laird
  • 1,101
  • 2
  • 13
  • 24
  • I may have been using the wrong function to do this (or re-inventing the wheel). CryptCreateKeyIdentifierFromCSP looks like a better fit, but I don't see an equivalent CNG function. Maybe they just haven't written one yet. Does anyone know how I would know if I used incorrect subject key identifiers? What exactly would break and where? What should I look for? – Timothy John Laird Feb 08 '18 at 16:40

1 Answers1

2

Here you go: both CNG and CAPI variants.

HRESULT capiCreateKeyIdentifierFromPublicKey(NCRYPT_KEY_HANDLE hCngKey, CRYPT_DATA_BLOB* outHash)
{
    HRESULT                 hr         = S_OK;
    BOOL                    bResult    = FALSE;

    PCERT_PUBLIC_KEY_INFO   pCertInfo  = NULL;
    DWORD                   cbCertInfo = 0;

    outHash->pbData = NULL;
    outHash->cbData = 0;


    /* STEP1: Extract public key. */
    bResult = CryptExportPublicKeyInfo(hCngKey, 0, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, &cbCertInfo);
    if (!bResult) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }

    pCertInfo = (PCERT_PUBLIC_KEY_INFO)HeapAlloc(GetProcessHeap(), 0, cbCertInfo);
    if (NULL == pCertInfo) {
        hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
        goto Cleanup;
    }


    bResult = CryptExportPublicKeyInfo(hCngKey, 0, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pCertInfo, &cbCertInfo);
    if (!bResult) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }



    /* STEP2: Make hash. */
    bResult = CryptHashPublicKeyInfo(NULL, CALG_SHA1, 0, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pCertInfo, NULL, &outHash->cbData);
    if (!bResult) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }

    outHash->pbData = (BYTE*)HeapAlloc(GetProcessHeap(), 0, outHash->cbData);

    bResult = CryptHashPublicKeyInfo(NULL, CALG_SHA1, 0, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pCertInfo, outHash->pbData, &outHash->cbData);
    if (!bResult) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }


Cleanup:
    if (!SUCCEEDED(hr) && NULL != outHash->pbData) {
        HeapFree(GetProcessHeap(), 0, outHash->pbData);
        outHash->pbData = NULL;
        outHash->cbData = 0;
    }

    if (NULL != pCertInfo) {
        HeapFree(GetProcessHeap(), 0, pCertInfo);
        pCertInfo = 0;
    }

    return hr;
}


HRESULT cngCreateKeyIdentifierFromPublicKey(NCRYPT_KEY_HANDLE hCngKey, CRYPT_DATA_BLOB* outHash)
{
    // @see https://learn.microsoft.com/en-us/windows/desktop/seccng/creating-a-hash-with-cng
    HRESULT                 hr           = S_OK;
    BOOL                    bResult      = FALSE;

    BCRYPT_ALG_HANDLE       hAlg         = NULL;
    BCRYPT_HASH_HANDLE      hHash        = NULL;
    NTSTATUS                status       = 0;

    DWORD                   cbData       = 0;
    DWORD                   cbHashObject = 0;
    PBYTE                   pbHashObject = NULL;

    PCERT_PUBLIC_KEY_INFO   pCertInfo    = NULL;
    DWORD                   cbCertInfo   = 0;

    BYTE*                   pbEncodedCertInfo = NULL;
    ULONG                   cbEncodedCertInfo = 0;

    outHash->pbData = NULL;
    outHash->cbData = 0;

    /* STEP1: Extract public key. */
    bResult = CryptExportPublicKeyInfo(hCngKey, 0, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, &cbCertInfo);
    if (!bResult) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }

    pCertInfo = (PCERT_PUBLIC_KEY_INFO)HeapAlloc(GetProcessHeap(), 0, cbCertInfo);
    if (NULL == pCertInfo) {
        hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
        goto Cleanup;
    }


    bResult = CryptExportPublicKeyInfo(hCngKey, 0, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pCertInfo, &cbCertInfo);
    if (!bResult) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }



    /* STEP2: Encode the public key. */
    bResult = CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, X509_PUBLIC_KEY_INFO, pCertInfo, pbEncodedCertInfo, &cbEncodedCertInfo);
    if (!bResult) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }

    pbEncodedCertInfo = (BYTE*)HeapAlloc(GetProcessHeap(), 0, cbEncodedCertInfo);

    bResult = CryptEncodeObject(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, X509_PUBLIC_KEY_INFO, pCertInfo, pbEncodedCertInfo, &cbEncodedCertInfo);
    if (!bResult) {
        hr = HRESULT_FROM_WIN32(GetLastError());
        goto Cleanup;
    }



    /* STEP3: Open an algorithm handle. */
    status = BCryptOpenAlgorithmProvider(
        &hAlg,
        BCRYPT_SHA1_ALGORITHM,
        NULL,
        0
    );

    if (!NT_SUCCESS(status)) {
        hr = HRESULT_FROM_NT(status);
        goto Cleanup;
    }



    /* STEP4: Calculate the size of the buffer to hold the hash object. */
    status = BCryptGetProperty(
        hAlg,
        BCRYPT_OBJECT_LENGTH,
        (PBYTE)&cbHashObject,
        sizeof(DWORD),
        &cbData,
        0
    );

    if (!NT_SUCCESS(status)) {
        hr = HRESULT_FROM_NT(status);
        goto Cleanup;
    }



    /* STEP5: Allocate the buffer for hash object on the heap. */
    pbHashObject = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbHashObject);
    if (NULL == pbHashObject) {
        hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
        goto Cleanup;
    }



    /* STEP6: Create a hash object (get handle to CNG hash object). */
    status = BCryptCreateHash(
        hAlg,
        &hHash,
        pbHashObject,
        cbHashObject,
        NULL,
        0,
        0
    );

    if (!NT_SUCCESS(status)) {
        hr = HRESULT_FROM_NT(status);
        goto Cleanup;
    }



    /* STEP7: Calculate the length of buffer for result hash. */
    status = BCryptGetProperty(
        hAlg,
        BCRYPT_HASH_LENGTH,
        (PBYTE)&outHash->cbData,
        sizeof(DWORD),
        &cbData,
        0
    );

    if (!NT_SUCCESS(status)) {
        hr = HRESULT_FROM_NT(status);
        goto Cleanup;
    }



    /* STEP8: Allocate buffer for result hash on the heap. */
    outHash->pbData = (PBYTE)HeapAlloc(GetProcessHeap(), 0, outHash->cbData);
    if (NULL == outHash->pbData) {
        hr = HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
        goto Cleanup;
    }



    /* STEP9: Hash data. */
    status = BCryptHashData(
        hHash,
        (PBYTE)pbEncodedCertInfo,
        cbEncodedCertInfo,
        0
    );

    if (!NT_SUCCESS(status)) {
        hr = HRESULT_FROM_NT(status);
        goto Cleanup;
    }


    /* STEP10: Close hash object and get result value. */
    status = BCryptFinishHash(
        hHash,
        outHash->pbData,
        outHash->cbData,
        0
    );

    if (!NT_SUCCESS(status)) {
        hr = HRESULT_FROM_NT(status);
        goto Cleanup;
    }

Cleanup:
    if (!SUCCEEDED(hr) && NULL != outHash->pbData) {
        HeapFree(GetProcessHeap(), 0, outHash->pbData);
        outHash->pbData = NULL;
        outHash->cbData = 0;
    }

    if (NULL != hHash) {
        BCryptDestroyHash(hHash);
        hHash = NULL;
    }

    if (NULL != pbHashObject) {
        HeapFree(GetProcessHeap(), 0, pbHashObject);
        pbHashObject = NULL;
    }

    if (NULL != hAlg) {
        BCryptCloseAlgorithmProvider(hAlg, 0);
        hAlg = NULL;
    }

    if (NULL != pbEncodedCertInfo) {
        HeapFree(GetProcessHeap(), 0, pbEncodedCertInfo);
        pCertInfo = 0;
    }

    if (NULL != pCertInfo) {
        HeapFree(GetProcessHeap(), 0, pCertInfo);
        pCertInfo = 0;
    }

    return hr;
}

Usage:

CRYPT_DATA_BLOB subjectKeyIdentifier = { 0 };
NCRYPT_KEY_HANDLE hCngKey = NULL;

HRESULT hr = NCryptOpenStorageProvider(&hProvider, MS_KEY_STORAGE_PROVIDER, 0);
if (hr) {
    hr = NCryptOpenKey(hProvider, &hCngKey, wszKeyName, 0, 0);
    if (ERROR_SUCCESS == hr) {
        hr = cngCreateKeyIdentifierFromPublicKey(hCngKey, &subjectKeyIdentifier);
        if (hr) {
            // do smth with data
            // clear the memory
            HeapFree(GetProcessHeap(), 0, subjectKeyIdentifier.pbData);
            subjectKeyIdentifier.pbData = NULL;
            subjectKeyIdentifier.cbData = 0;
        }

    }
}

......
geniuss99
  • 101
  • 5