如何计算CNG中的主题密钥标识符?

时间:2018-02-07 21:09:30

标签: c++ hash cryptoapi 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没有这样的方法。但是,上面链接中CryptHashPublicKeyInfo的描述表明它们执行公钥信息的SHA1哈希。所以我在CryptHashPublicKeyInfo(CAPI)中的相同数据上对CNG中的公钥字节进行了SHA1哈希,并且两个哈希值不同。我需要解决这个差异。为了清楚起见,我的逻辑运行在同一个CSR的同一个公钥上。

RFC 5280中的详细信息似乎证实了微软的说法: https://tools.ietf.org/html/rfc5280#section-4.2.1.2

  

(1)keyIdentifier由160位SHA-1哈希组成              BIT STRING subjectPublicKey的值(不包括标签,              长度和未使用的位数。)

     

Cooper,et al。标准跟踪[页面   28] RFC 5280 PKIX证书和CRL配置文件
  2008年5月

  (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).

^我猜测微软正在做#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");
}

这会在扩展名中生成此值(证书请求中的公共密钥相同):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代码示例中做错了什么?显然有一个神奇的呼叫序列,但我不知道它是什么。

1 个答案:

答案 0 :(得分:2)

在这里: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;
        }

    }
}

......