Related
I tried to export private key from certificate store by CNG API. It work fine when export RSA private key, but failed in EC private key.
The code failed in NCryptExportKey() with 0x80090029.
Is there any document from MS said: Export EC private key not support? or any sample code?
Here is my code:
NCRYPT_KEY_HANDLE hKey = NULL;
SECURITY_STATUS secStatus = ERROR_SUCCESS;
NTSTATUS status = STATUS_UNSUCCESSFUL;
DWORD dwKeySpec, cbData = 0, cbBlob = 0, KeyPolicy = 0;
PBYTE pbHash = NULL, pbBlob = NULL;
PCCERT_CONTEXT pSignerCert = NULL;
unsigned char *MessagePrivKey;
Struct_Return ExportMessage = { NULL, 0 };
bool bStatus;
pSignerCert = GetCert(MY_CERT_NAME);
if (!CryptAcquireCertificatePrivateKey(
pSignerCert,
CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG,
NULL,
&hKey,
&dwKeySpec,
NULL))
{
goto End;
}
if (FAILED(secStatus = NCryptExportKey(
hKey,
NULL,
NCRYPT_PKCS8_PRIVATE_KEY_BLOB,
NULL,
NULL,
0,
&cbBlob,
0)))
{
wprintf(L"**** Error 0x%x returned by NCryptExportKey\n", secStatus);
goto End;
}
pbBlob = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbBlob);
if (NULL == pbBlob)
{
wprintf(L"**** memory allocation failed\n");
goto End;
}
if (FAILED(secStatus = NCryptExportKey(
hKey,
NULL,
NCRYPT_PKCS8_PRIVATE_KEY_BLOB,
NULL,
pbBlob,
cbBlob,
&cbBlob,
0)))
{
wprintf(L"**** Error 0x%x returned by NCryptExportKey\n", secStatus);
goto End;
}
I also tried to call NCryptSetProperty() before export, but it failed with 0x8009000b.
KeyPolicy = NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG | NCRYPT_ALLOW_EXPORT_FLAG;
if (FAILED(secStatus = NCryptSetProperty(
hKey,
NCRYPT_EXPORT_POLICY_PROPERTY,
(PBYTE)&KeyPolicy,
sizeof(KeyPolicy),
NCRYPT_PERSIST_FLAG)))
{
wprintf(L"**** Error 0x%x returned by NCryptSetProperty\n", secStatus);
goto End;
}
1. How to sign with RSA private key? (SOLVED)
I followed this sample to sign data with private key. I only reserve hash and sign functions, and use ECDSA P-256 private key like the sample.
Here are my steps and the program work fine:
BCryptOpenAlgorithmProvider()
Hash some data
NCryptOpenStorageProvider()
NCryptCreatePersistedKey()
NCryptFinalizeKey()
NCryptSignHash()
NCryptSignHash()
I tried to sign with RSA private key, replace NCRYPT_ECDSA_P256_ALGORITHM with NCRYPT_RSA_ALGORITHM and NCRYPT_RSA_SIGN_ALGORITHM, but it failed in second NCryptSignHash() with NTE_INVALID_PARAMETER.
//create a persisted key
if (FAILED(secStatus = NCryptCreatePersistedKey(
hProv,
&hKey,
NCRYPT_RSA_ALGORITHM,
L"my RSA key",
0,
0)))
{
wprintf(L"**** Error 0x%x returned by NCryptCreatePersistedKey\n", secStatus);
goto Cleanup;
}
2. Why the program can't find the private key after first run?
The private key is created in the program, I tried to sign with X509 certificate which has private key(ECDSA P-256) in My certifacate store.
Here are my steps and the program only work in the first run. It failed in CryptAcquireCertificatePrivateKey() with NTE_BAD_KEYSET error
BCryptOpenAlgorithmProvider()
Hash some data
CertOpenStore()
CertFindCertificateInStore()
CryptAcquireCertificatePrivateKey()
NCryptSignHash()
NCryptSignHash()
// Open the certificate store.
if (!(hCertStore = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
NULL,
CERT_SYSTEM_STORE_CURRENT_USER,
CERT_STORE_NAME)))
{
MyHandleError(const_cast<LPTSTR>("The MY store could not be opened."));
}
swprintf(wMY_SIGNER_NAME, 100, L"%hs", MY_SIGNER_NAME);
if (pSignerCert = CertFindCertificateInStore(
hCertStore,
MY_ENCODING_TYPE,
0,
CERT_FIND_SUBJECT_STR,
wMY_SIGNER_NAME,
NULL))
{
//_tprintf(TEXT("The signer's certificate was found.\n"));
}
else
{
MyHandleError(const_cast<LPTSTR>("Signer certificate not found."));
}
if (CryptAcquireCertificatePrivateKey(
pSignerCert,
CRYPT_ACQUIRE_PREFER_NCRYPT_KEY_FLAG, NULL,
&hKey,
&dwKeySpec,
false))
{
printf("Get Priv OK\n");
}
else
{
if (GetLastError() == NTE_BAD_KEYSET)
printf("NTE_BAD_KEYSET\n");
}
UPDATE:
if (FAILED(secStatus = NCryptSignHash(
hKey,
0,
pbHash,
cbHash,
NULL,
0,
&cbSignature,
0)))
{
wprintf(L"**** Error 0x%x returned by NCryptSignHash1\n", secStatus);
goto Cleanup;
}
//allocate the signature buffer
pbSignature = (PBYTE)HeapAlloc(GetProcessHeap(), 0, cbSignature);
if (NULL == pbSignature)
{
wprintf(L"**** memory allocation failed\n");
goto Cleanup;
}
if (FAILED(secStatus = NCryptSignHash(
hKey,
0,
pbHash,
cbHash,
pbSignature,
cbSignature,
&cbSignature,
0)))
{
wprintf(L"**** Error 0x%x returned by NCryptSignHash2\n", secStatus);
goto Cleanup;
}
UPDATE2:
The code work after removing NCryptDeleteKey(hKey, 0). I think this API delete my EC key! But I don't know why RSA key not be deleted?
I'm trying to make simple working example of encryption and decryption with BCrypt but I'm can't get it to work because I don't understand exactly how.
From BCryptEncrypt Function, Microsoft Docs:
NTSTATUS BCryptEncrypt(
BCRYPT_KEY_HANDLE hKey,
PUCHAR pbInput,
ULONG cbInput,
VOID *pPaddingInfo,
PUCHAR pbIV,
ULONG cbIV,
PUCHAR pbOutput,
ULONG cbOutput,
ULONG *pcbResult,
ULONG dwFlags
);
I generated a simple 512bit key pair on 8gwifi.org:
string Public_Key = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAJSNbUhCkU9RuY99L8kC2WRJ8TBES3WX1u9wYlANvUFU/h7lU8VNOWI8dNGCQ6UbK2ypHFom+Zm4BaG1zokwcUkCAwEAAQ==";
string Private_Key = "MIIBOgIBAAJBAJSNbUhCkU9RuY99L8kC2WRJ8TBES3WX1u9wYlANvUFU/h7lU8VNOWI8dNGCQ6UbK2ypHFom+Zm4BaG1zokwcUkCAwEAAQJAZ9bwZAl8L5jt//o/E+C0+2Cggt/Ka5nG+bpyTok8GNTyaG+Prmz/QCYdI3VuYdONdfAPm3jLwtbK9wTt1E8HAQIhAM8jg1nwjN9+nhPyFo0F+2o8y47mq1kHnCn+gqAdW8MhAiEAt5gQcCqX2Y5KbmMoqtQ+4RIEHQ8HD+fyGqxWUhVpESkCIEtylQJqgvVZCj0bnakqN6Q/lqlrTZg1FGWbZXrqlqThAiEAilt5v94Jc7Ws2AW4Rw0OmfVGzlNd4hnNNVa88r0Z4gkCIGfFy2H8pGxHxg1GKE2mSZAfpRMyjqeq119S3t/bhqY2";
string Encrypt_Me = "Hello World";
To be honest I don't understand exactly how to use this function for this situation, I tried searching a simple example but couldn't find any.
Thank you.
There is an sample here: https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/007a0e26-7fc0-4079-9b63-2ad23f866836/bug-in-rsa-encryptiondecryption-using-cng?forum=windowssdk
Please note that the first byte of encrypted data should not exceed 0xb6. And there is a detailed explanation in the post.
Take encryption as an example,
First, use BCryptOpenAlgorithmProvider to load and initialize a CNG provider that specify RSA.
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
status = BCryptOpenAlgorithmProvider(&hAlgorithm,
BCRYPT_RSA_ALGORITHM,
NULL,
0);
if (!NT_SUCCESS(status)) {
printf("Failed to get algorithm provider..status : %08x\n", status);
goto cleanup;
}
Then, BCryptImportKeyPair
status = BCryptImportKeyPair(hAlgorithm,
NULL,
BCRYPT_RSAPUBLIC_BLOB,
&hKey,
PublicKey,
PublicKeySize,
BCRYPT_NO_KEY_VALIDATION);
if (!NT_SUCCESS(status)) {
printf("Failed to import Private key..status : %08x\n", status);
goto cleanup;
}
To get Encrypted Buffer Size:
status = BCryptEncrypt(hKey,
InputData,
InputDataSize,
NULL,
NULL,
0,
NULL,
0,
&EncryptedBufferSize,
0
);
if (!NT_SUCCESS(status)) {
printf("Failed to get required size of buffer..status : %08x\n", status);
goto cleanup;
}
encryptedBuffer = (PUCHAR)HeapAlloc(GetProcessHeap(), 0, encryptedBufferSize);
if (encryptedBuffer == NULL) {
printf("failed to allocate memory for blindedFEKBuffer\n");
goto cleanup;
}
Encrypte Data:
status = BCryptEncrypt(hKey,
InputData,
InputDataSize,
NULL,
NULL,
0,
encryptedBuffer,
encryptedBufferSize,
&encryptedBufferSize,
0
);
if (!NT_SUCCESS(status)) {
printf("Failed encrypt data..status : %08x\n", status);
goto cleanup;
}
cleanup:
if (hKey)
BCryptDestroyKey(hKey);
if (hAlgorithm)
BCryptCloseAlgorithmProvider(hAlgorithm, 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.
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;
}
}
}
......
I've been using the following code (taken from KB323809 article) to retrieve information about the code signature on the executable file. This works fine for a single digital signature.
But how to retrieve information for multiple code signatures?
In that case the Microsoft code below simply retrives info only for the first signature.
My thought was to call CryptMsgGetParam with CMSG_SIGNER_COUNT_PARAM to get the number of signatures and then pass each signature index to the subsequent call to CryptMsgGetParam with CMSG_SIGNER_INFO_PARAM (in the code below.) But this approach always returns 1 signature, even if I clearly have more, like 3 in this example:
#include <windows.h>
#include <wincrypt.h>
#include <wintrust.h>
#include <stdio.h>
#include <tchar.h>
#pragma comment(lib, "crypt32.lib")
#define ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)
typedef struct {
LPWSTR lpszProgramName;
LPWSTR lpszPublisherLink;
LPWSTR lpszMoreInfoLink;
} SPROG_PUBLISHERINFO, *PSPROG_PUBLISHERINFO;
BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,
PSPROG_PUBLISHERINFO Info);
BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st);
BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext);
BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo,
PCMSG_SIGNER_INFO *pCounterSignerInfo);
int _tmain(int argc, TCHAR *argv[])
{
WCHAR szFileName[MAX_PATH];
HCERTSTORE hStore = NULL;
HCRYPTMSG hMsg = NULL;
PCCERT_CONTEXT pCertContext = NULL;
BOOL fResult;
DWORD dwEncoding, dwContentType, dwFormatType;
PCMSG_SIGNER_INFO pSignerInfo = NULL;
PCMSG_SIGNER_INFO pCounterSignerInfo = NULL;
DWORD dwSignerInfo;
CERT_INFO CertInfo;
SPROG_PUBLISHERINFO ProgPubInfo;
SYSTEMTIME st;
ZeroMemory(&ProgPubInfo, sizeof(ProgPubInfo));
__try
{
if (argc != 2)
{
_tprintf(_T("Usage: SignedFileInfo <filename>\n"));
return 0;
}
#ifdef UNICODE
lstrcpynW(szFileName, argv[1], MAX_PATH);
#else
if (mbstowcs(szFileName, argv[1], MAX_PATH) == -1)
{
printf("Unable to convert to unicode.\n");
__leave;
}
#endif
// Get message handle and store handle from the signed file.
fResult = CryptQueryObject(CERT_QUERY_OBJECT_FILE,
szFileName,
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
CERT_QUERY_FORMAT_FLAG_BINARY,
0,
&dwEncoding,
&dwContentType,
&dwFormatType,
&hStore,
&hMsg,
NULL);
if (!fResult)
{
_tprintf(_T("CryptQueryObject failed with %x\n"), GetLastError());
__leave;
}
// Get signer information size.
fResult = CryptMsgGetParam(hMsg,
CMSG_SIGNER_INFO_PARAM,
0,
NULL,
&dwSignerInfo);
if (!fResult)
{
_tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());
__leave;
}
// Allocate memory for signer information.
pSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSignerInfo);
if (!pSignerInfo)
{
_tprintf(_T("Unable to allocate memory for Signer Info.\n"));
__leave;
}
// Get Signer Information.
fResult = CryptMsgGetParam(hMsg,
CMSG_SIGNER_INFO_PARAM,
0,
(PVOID)pSignerInfo,
&dwSignerInfo);
if (!fResult)
{
_tprintf(_T("CryptMsgGetParam failed with %x\n"), GetLastError());
__leave;
}
// Get program name and publisher information from
// signer info structure.
if (GetProgAndPublisherInfo(pSignerInfo, &ProgPubInfo))
{
if (ProgPubInfo.lpszProgramName != NULL)
{
wprintf(L"Program Name : %s\n",
ProgPubInfo.lpszProgramName);
}
if (ProgPubInfo.lpszPublisherLink != NULL)
{
wprintf(L"Publisher Link : %s\n",
ProgPubInfo.lpszPublisherLink);
}
if (ProgPubInfo.lpszMoreInfoLink != NULL)
{
wprintf(L"MoreInfo Link : %s\n",
ProgPubInfo.lpszMoreInfoLink);
}
}
_tprintf(_T("\n"));
// Search for the signer certificate in the temporary
// certificate store.
CertInfo.Issuer = pSignerInfo->Issuer;
CertInfo.SerialNumber = pSignerInfo->SerialNumber;
pCertContext = CertFindCertificateInStore(hStore,
ENCODING,
0,
CERT_FIND_SUBJECT_CERT,
(PVOID)&CertInfo,
NULL);
if (!pCertContext)
{
_tprintf(_T("CertFindCertificateInStore failed with %x\n"),
GetLastError());
__leave;
}
// Print Signer certificate information.
_tprintf(_T("Signer Certificate:\n\n"));
PrintCertificateInfo(pCertContext);
_tprintf(_T("\n"));
// Get the timestamp certificate signerinfo structure.
if (GetTimeStampSignerInfo(pSignerInfo, &pCounterSignerInfo))
{
// Search for Timestamp certificate in the temporary
// certificate store.
CertInfo.Issuer = pCounterSignerInfo->Issuer;
CertInfo.SerialNumber = pCounterSignerInfo->SerialNumber;
pCertContext = CertFindCertificateInStore(hStore,
ENCODING,
0,
CERT_FIND_SUBJECT_CERT,
(PVOID)&CertInfo,
NULL);
if (!pCertContext)
{
_tprintf(_T("CertFindCertificateInStore failed with %x\n"),
GetLastError());
__leave;
}
// Print timestamp certificate information.
_tprintf(_T("TimeStamp Certificate:\n\n"));
PrintCertificateInfo(pCertContext);
_tprintf(_T("\n"));
// Find Date of timestamp.
if (GetDateOfTimeStamp(pCounterSignerInfo, &st))
{
_tprintf(_T("Date of TimeStamp : %02d/%02d/%04d %02d:%02d\n"),
st.wMonth,
st.wDay,
st.wYear,
st.wHour,
st.wMinute);
}
_tprintf(_T("\n"));
}
}
__finally
{
// Clean up.
if (ProgPubInfo.lpszProgramName != NULL)
LocalFree(ProgPubInfo.lpszProgramName);
if (ProgPubInfo.lpszPublisherLink != NULL)
LocalFree(ProgPubInfo.lpszPublisherLink);
if (ProgPubInfo.lpszMoreInfoLink != NULL)
LocalFree(ProgPubInfo.lpszMoreInfoLink);
if (pSignerInfo != NULL) LocalFree(pSignerInfo);
if (pCounterSignerInfo != NULL) LocalFree(pCounterSignerInfo);
if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);
if (hStore != NULL) CertCloseStore(hStore, 0);
if (hMsg != NULL) CryptMsgClose(hMsg);
}
return 0;
}
BOOL PrintCertificateInfo(PCCERT_CONTEXT pCertContext)
{
BOOL fReturn = FALSE;
LPTSTR szName = NULL;
DWORD dwData;
__try
{
// Print Serial Number.
_tprintf(_T("Serial Number: "));
dwData = pCertContext->pCertInfo->SerialNumber.cbData;
for (DWORD n = 0; n < dwData; n++)
{
_tprintf(_T("%02x "),
pCertContext->pCertInfo->SerialNumber.pbData[dwData - (n + 1)]);
}
_tprintf(_T("\n"));
// Get Issuer name size.
if (!(dwData = CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
NULL,
0)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// Allocate memory for Issuer name.
szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
if (!szName)
{
_tprintf(_T("Unable to allocate memory for issuer name.\n"));
__leave;
}
// Get Issuer name.
if (!(CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
CERT_NAME_ISSUER_FLAG,
NULL,
szName,
dwData)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// print Issuer name.
_tprintf(_T("Issuer Name: %s\n"), szName);
LocalFree(szName);
szName = NULL;
// Get Subject name size.
if (!(dwData = CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
NULL,
0)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// Allocate memory for subject name.
szName = (LPTSTR)LocalAlloc(LPTR, dwData * sizeof(TCHAR));
if (!szName)
{
_tprintf(_T("Unable to allocate memory for subject name.\n"));
__leave;
}
// Get subject name.
if (!(CertGetNameString(pCertContext,
CERT_NAME_SIMPLE_DISPLAY_TYPE,
0,
NULL,
szName,
dwData)))
{
_tprintf(_T("CertGetNameString failed.\n"));
__leave;
}
// Print Subject Name.
_tprintf(_T("Subject Name: %s\n"), szName);
fReturn = TRUE;
}
__finally
{
if (szName != NULL) LocalFree(szName);
}
return fReturn;
}
LPWSTR AllocateAndCopyWideString(LPCWSTR inputString)
{
LPWSTR outputString = NULL;
outputString = (LPWSTR)LocalAlloc(LPTR,
(wcslen(inputString) + 1) * sizeof(WCHAR));
if (outputString != NULL)
{
lstrcpyW(outputString, inputString);
}
return outputString;
}
BOOL GetProgAndPublisherInfo(PCMSG_SIGNER_INFO pSignerInfo,
PSPROG_PUBLISHERINFO Info)
{
BOOL fReturn = FALSE;
PSPC_SP_OPUS_INFO OpusInfo = NULL;
DWORD dwData;
BOOL fResult;
__try
{
// Loop through authenticated attributes and find
// SPC_SP_OPUS_INFO_OBJID OID.
for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)
{
if (lstrcmpA(SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)
{
// Get Size of SPC_SP_OPUS_INFO structure.
fResult = CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
NULL,
&dwData);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
// Allocate memory for SPC_SP_OPUS_INFO structure.
OpusInfo = (PSPC_SP_OPUS_INFO)LocalAlloc(LPTR, dwData);
if (!OpusInfo)
{
_tprintf(_T("Unable to allocate memory for Publisher Info.\n"));
__leave;
}
// Decode and get SPC_SP_OPUS_INFO structure.
fResult = CryptDecodeObject(ENCODING,
SPC_SP_OPUS_INFO_OBJID,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
OpusInfo,
&dwData);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
// Fill in Program Name if present.
if (OpusInfo->pwszProgramName)
{
Info->lpszProgramName =
AllocateAndCopyWideString(OpusInfo->pwszProgramName);
}
else
Info->lpszProgramName = NULL;
// Fill in Publisher Information if present.
if (OpusInfo->pPublisherInfo)
{
switch (OpusInfo->pPublisherInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
Info->lpszPublisherLink =
AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszUrl);
break;
case SPC_FILE_LINK_CHOICE:
Info->lpszPublisherLink =
AllocateAndCopyWideString(OpusInfo->pPublisherInfo->pwszFile);
break;
default:
Info->lpszPublisherLink = NULL;
break;
}
}
else
{
Info->lpszPublisherLink = NULL;
}
// Fill in More Info if present.
if (OpusInfo->pMoreInfo)
{
switch (OpusInfo->pMoreInfo->dwLinkChoice)
{
case SPC_URL_LINK_CHOICE:
Info->lpszMoreInfoLink =
AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszUrl);
break;
case SPC_FILE_LINK_CHOICE:
Info->lpszMoreInfoLink =
AllocateAndCopyWideString(OpusInfo->pMoreInfo->pwszFile);
break;
default:
Info->lpszMoreInfoLink = NULL;
break;
}
}
else
{
Info->lpszMoreInfoLink = NULL;
}
fReturn = TRUE;
break; // Break from for loop.
} // lstrcmp SPC_SP_OPUS_INFO_OBJID
} // for
}
__finally
{
if (OpusInfo != NULL) LocalFree(OpusInfo);
}
return fReturn;
}
BOOL GetDateOfTimeStamp(PCMSG_SIGNER_INFO pSignerInfo, SYSTEMTIME *st)
{
BOOL fResult;
FILETIME lft, ft;
DWORD dwData;
BOOL fReturn = FALSE;
// Loop through authenticated attributes and find
// szOID_RSA_signingTime OID.
for (DWORD n = 0; n < pSignerInfo->AuthAttrs.cAttr; n++)
{
if (lstrcmpA(szOID_RSA_signingTime,
pSignerInfo->AuthAttrs.rgAttr[n].pszObjId) == 0)
{
// Decode and get FILETIME structure.
dwData = sizeof(ft);
fResult = CryptDecodeObject(ENCODING,
szOID_RSA_signingTime,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->AuthAttrs.rgAttr[n].rgValue[0].cbData,
0,
(PVOID)&ft,
&dwData);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
break;
}
// Convert to local time.
FileTimeToLocalFileTime(&ft, &lft);
FileTimeToSystemTime(&lft, st);
fReturn = TRUE;
break; // Break from for loop.
} //lstrcmp szOID_RSA_signingTime
} // for
return fReturn;
}
BOOL GetTimeStampSignerInfo(PCMSG_SIGNER_INFO pSignerInfo, PCMSG_SIGNER_INFO *pCounterSignerInfo)
{
PCCERT_CONTEXT pCertContext = NULL;
BOOL fReturn = FALSE;
BOOL fResult;
DWORD dwSize;
__try
{
*pCounterSignerInfo = NULL;
// Loop through unathenticated attributes for
// szOID_RSA_counterSign OID.
for (DWORD n = 0; n < pSignerInfo->UnauthAttrs.cAttr; n++)
{
if (lstrcmpA(pSignerInfo->UnauthAttrs.rgAttr[n].pszObjId,
szOID_RSA_counterSign) == 0)
{
// Get size of CMSG_SIGNER_INFO structure.
fResult = CryptDecodeObject(ENCODING,
PKCS7_SIGNER_INFO,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,
0,
NULL,
&dwSize);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
// Allocate memory for CMSG_SIGNER_INFO.
*pCounterSignerInfo = (PCMSG_SIGNER_INFO)LocalAlloc(LPTR, dwSize);
if (!*pCounterSignerInfo)
{
_tprintf(_T("Unable to allocate memory for timestamp info.\n"));
__leave;
}
// Decode and get CMSG_SIGNER_INFO structure
// for timestamp certificate.
fResult = CryptDecodeObject(ENCODING,
PKCS7_SIGNER_INFO,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].pbData,
pSignerInfo->UnauthAttrs.rgAttr[n].rgValue[0].cbData,
0,
(PVOID)*pCounterSignerInfo,
&dwSize);
if (!fResult)
{
_tprintf(_T("CryptDecodeObject failed with %x\n"),
GetLastError());
__leave;
}
fReturn = TRUE;
break; // Break from for loop.
}
}
}
__finally
{
// Clean up.
if (pCertContext != NULL) CertFreeCertificateContext(pCertContext);
}
return fReturn;
}
In addition to the answer of Daniel Sie.
Found an attribute (szOID_NESTED_SIGNATURE) that would contain the CMSG_SIGNER_INFO that need to be decoded with following steps (this is Delphi code but sense is clear):
LNestedMsg := CryptMsgOpenToDecode(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 0, 0, 0, nil, 0);
CryptMsgUpdate(LNestedMsg, LFindedAttr.rgValue.pbData, LFindedAttr.rgValue.cbData, True);
CryptMsgGetParam(LNestedMsg, CMSG_SIGNER_INFO_PARAM, 0, nil, #LSize);
CryptMsgGetParam(LNestedMsg, CMSG_SIGNER_INFO_PARAM, 0, LNestedSignerInfo, #LSize);
The acquired CMSG_SIGNER_INFO (LNestedSignerInfo) is the nested signature (in our case SHA2 signature).
To obtain the time-stamp information (RFC3161) of that signature - search the Unauthenticated attribute with pszObjId = szOID_RFC3161_counterSign (1.3.6.1.4.1.311.3.3.1).
Found attribute would contain the CMSG_SIGNER_INFO of the time-stamp counter signature, that also need to be decoded by previously described steps (CryptMsgOpenToDecode, CryptMsgUpdate, CryptMsgGetParam).
The CERT_CONTEXT of nested signature or timestamp counter signature is need to be searched in store with is obtained from corresponding HCRYPTMSG (result of CryptMsgOpenToDecode).
LNestedStore := CertOpenStore(CERT_STORE_PROV_MSG, PKCS_7_ASN_ENCODING or X509_ASN_ENCODING, 0, 0, LNestedMsg);
LTimeStampStore := CertOpenStore(CERT_STORE_PROV_MSG, PKCS_7_ASN_ENCODING or X509_ASN_ENCODING, 0, 0, LTimeStampMsg);
Example to decode szOID_RFC3161_counterSign attribute:
LNestedSignerAttr := LNestedSigner.UnauthAttrs.rgAttr;
for I := 0 to LNestedSigner.UnauthAttrs.cAttr - 1 do
begin
if SameText(string(LNestedSignerAttr.pszObjId), szOID_RFC3161_counterSign) then
begin
LNestedTimeStampMsg := CryptMsgOpenToDecode(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING, 0, 0, 0, nil, nil);
if not Assigned(LNestedTimeStampMsg) then
RaiseLastOSError;
try
if not CryptMsgUpdate(LNestedTimeStampMsg, LNestedSignerAttr.rgValue.pbData, LNestedSignerAttr.rgValue.cbData, True) then
RaiseLastOSError;
if not CryptMsgGetParam(LNestedTimeStampMsg, CMSG_SIGNER_INFO_PARAM, 0, nil, #LSize) then
RaiseLastOSError;
GetMem(LTimeStampSigner, LSize);
try
if not CryptMsgGetParam(LNestedTimeStampMsg, CMSG_SIGNER_INFO_PARAM, 0, LTimeStampSigner, #LSize) then
RaiseLastOSError;
LAttr := LTimeStampSigner.AuthAttrs.rgAttr;
for J := 0 to LTimeStampSigner.AuthAttrs.cAttr - 1 do
begin
if SameText(string(LAttr.pszObjId), szOID_RSA_signingTime) then
begin
LSize := SizeOf(LFileTime);
if not CryptDecodeObject(X509_ASN_ENCODING or PKCS_7_ASN_ENCODING,
szOID_RSA_signingTime, LAttr.rgValue.pbData, LAttr.rgValue.cbData, 0, #LFileTime, #LSize) then
RaiseLastOSError;
if FileTimeToLocalFileTime(#LFileTime, LLocalFileTime)
and FileTimeToSystemTime(#LLocalFileTime, LSystemTime) then
SHA2TimeStamp := SystemTimeToDateTime(LSystemTime)
else
SHA2TimeStamp := 0;
end;
Inc(LAttr);
end;
finally
FreeMem(LTimeStampSigner);
end;
finally
if not CryptMsgClose(LNestedTimeStampMsg) then
RaiseLastOSError;
end;
end;
Inc(LNestedSignerAttr);
end;
Authenticode stores secondary signatures in the UnauthenticatedAttributes of primary signer (index 0), instead of additional PKCS 7 signer.
From the primary signature, search the UnauthenticatedAttribue for below:
//Indicates the attribute is an octet encoded PKCS7
define szOID_NESTED_SIGNATURE "1.3.6.1.4.1.311.2.4.1"
The encoded object of this attribute is a full PKCS 7 signer.
Thanks.