如何计算CNG中的Subject Key Identifier?

How to calculate Subject Key Identifier in CNG?

我的目标是使用 Microsoft CNG 为证书填充主题密钥标识符扩展 (2.5.29.14)。我以前用 Microsoft CAPI 做过,但我使用的函数:

CryptHashPublicKeyInfo

https://msdn.microsoft.com/en-us/library/windows/desktop/aa380204(v=vs.85).aspx

现已折旧。 CNG没有这样的方法。但是,上面 link 中对 CryptHashPublicKeyInfo 的描述说他们对 public 密钥信息进行 SHA1 哈希。因此,我对 CNG 中的 public 密钥字节对 CryptHashPublicKeyInfo (CAPI) 中的相同数据进行了 SHA1 散列,这两个散列不同。我需要解决这个差异。明确地说,我的逻辑是 运行 来自同一个 CSR 的同一个 public 键。

RFC 5280 中的详细信息似乎证实了 Microsoft 所说的内容: 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).

^我猜 Microsoft 正在处理案例 #1。

这是我的 CAPI 代码:

//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");
}

这会在扩展中生成此值(证书请求中的相同 public 密钥):9d77f29e4fa15e46237d59a7c00efde9d286b9dc

这是我的 CNG 代码:

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");
}

这会产生这个 SKI 值(不同):210816297e8e76879f99ec4762452b5d38967b5b

知道我在 CNG 代码示例中做错了什么吗?显然有一个神奇的调用序列,但我不知道它是什么。

给你:CNG 和 CAPI 变体。

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://docs.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;
}

用法:

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;
        }

    }
}

......