Моя цель — заполнить расширение идентификатора ключа субъекта (2.5.29.14) для сертификата с использованием Microsoft CNG. Я сделал это ранее с Microsoft CAPI, но функция, которую я использовал:
CryptHashPublicKeyInfo
https://msdn.microsoft.com/en-us/library/windows/desktop/aa380204(v=vs.85).aspx
Сейчас амортизируется. КПГ не имеет такого метода. Тем не менее, описание CryptHashPublicKeyInfo в приведенной выше ссылке говорит о том, что они выполняют хэш SHA1 для информации открытого ключа. Поэтому я сделал хэш SHA1 байтов открытого ключа в CNG для тех же данных в CryptHashPublicKeyInfo (CAPI), и эти два хэша различны. Мне нужно разрешить эту разницу. Чтобы было ясно, моя логика работает на том же открытом ключе от того же CSR.
Детали в RFC 5280, кажется, подтверждают то, что говорит Microsoft:
https://tools.ietf.org/html/rfc5280#section-4.2.1.2
(1) KeyIdentifier состоит из 160-битного SHA-1 хэша
значение BIT STRING subjectPublicKey (исключая тег,
длина и количество неиспользованных битов).Купер и соавт. Трек Стандартов
28] Сертификат RFC 5280 PKIX и профиль CRL
Май 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");
}
Это создает это значение в расширении (тот же открытый ключ в запросе сертификата): 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;
}
}
}
......
Других решений пока нет …