I need to verify the signature of a message using Windows' Cryptography API: Next Generation. I have the message, its signature, and a public key in PEM format, plus a prototype implementation that exhibits the desired behavior.
There's one issue with the code that I need to fix: Converting the public key into a key blob as used by CNG.
Currently, I'm decoding the public key
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETHfi8foQF4UtSNVxSFxeu7W+gMxd
SGElhdo7825SD3Lyb+Sqh4G6Kra0ro1BdrM6Qx+hsUx4Qwdby7QY0pzxyA==
-----END PUBLIC KEY-----
using CryptStringToBinaryA
, which produces the following binary data:
30 59 30 13 06 07 2A 86 48 CE 3D 02 01 06 08 2A
86 48 CE 3D 03 01 07 03 42 00 04 4C 77 E2 F1 FA
10 17 85 2D 48 D5 71 48 5C 5E BB B5 BE 80 CC 5D
48 61 25 85 DA 3B F3 6E 52 0F 72 F2 6F E4 AA 87
81 BA 2A B6 B4 AE 8D 41 76 B3 3A 43 1F A1 B1 4C
78 43 07 5B CB B4 18 D2 9C F1 C8
Everything looks fine. Presumably, this is in DER format, using ASN.1. It's 91 bytes in size, specifying elliptic curve cryptography (ECDSA prime256v1
) with the [x]
and [y]
entries starting at offset 27 (for a total of 32 bytes each).
Since I need a key blob to pass into BCryptImportKeyPair, specifically a BCRYPT_ECCKEY_BLOB
:
typedef struct _BCRYPT_ECCKEY_BLOB
{
ULONG dwMagic;
ULONG cbKey;
} BCRYPT_ECCKEY_BLOB, *PBCRYPT_ECCKEY_BLOB;
my prototype creates one ad-hoc, by supplying the BCRYPT_ECDSA_PUBLIC_P256_MAGIC
and a length of 32, followed by the respective 64 bytes from the public key above.
Now that works, but I'd clearly want to implement something more robust. The key seems to encode all data necessary to populate an appropriate CNG key blob, but I'm failing to find any APIs that would help me parse out this information.
Does the CNG (or Wincrypt) provide any functionality that would allow me to either construct a CNG key blob, or otherwise get a BCRYPT_KEY_HANDLE
out of a PEM key? Or am I left with implementing my own ASN.1 parser?
for convert PEM public key to CNG - generic steps is next:
CryptStringToBinaryA
for convert string to binaryCryptDecodeObjectEx
with X509_PUBLIC_KEY_INFO
- convert binary
to CERT_PUBLIC_KEY_INFO
CryptImportPublicKeyInfoEx2
- import CERT_PUBLIC_KEY_INFO
to CNGexample of code
inline ULONG BOOL_TO_ERROR(BOOL f)
{
return f ? NOERROR : GetLastError();
}
ULONG PemToCNG(_In_ PCSTR pszString, _Out_ BCRYPT_KEY_HANDLE* phKey)
{
PBYTE pb = 0;
ULONG cb = 0;
ULONG dwError;
while (NOERROR == (dwError = BOOL_TO_ERROR(
CryptStringToBinaryA(pszString, 0, CRYPT_STRING_BASE64_ANY, pb, &cb, 0, 0))))
{
if (pb)
{
PCERT_PUBLIC_KEY_INFO PublicKeyInfo;
if (NOERROR == (dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
X509_PUBLIC_KEY_INFO, pb, cb,
CRYPT_DECODE_ALLOC_FLAG|
CRYPT_DECODE_NOCOPY_FLAG|
CRYPT_DECODE_SHARE_OID_STRING_FLAG,
0, &PublicKeyInfo, &cb))))
{
dwError = BOOL_TO_ERROR(CryptImportPublicKeyInfoEx2(
X509_ASN_ENCODING, PublicKeyInfo, 0, 0, phKey));
LocalFree(PublicKeyInfo);
}
break;
}
if (!(pb = (PBYTE)LocalAlloc(0, cb)))
{
dwError = GetLastError();
break;
}
}
if (pb)
{
LocalFree(pb);
}
return dwError;
}
void TestPem()
{
static const char Pem[] =
"-----BEGIN PUBLIC KEY-----\r\n"
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAETHfi8foQF4UtSNVxSFxeu7W+gMxd\r\n"
"SGElhdo7825SD3Lyb+Sqh4G6Kra0ro1BdrM6Qx+hsUx4Qwdby7QY0pzxyA==\r\n"
"-----END PUBLIC KEY-----";
BCRYPT_KEY_HANDLE hKey;
if (NOERROR == PemToCNG(Pem, &hKey))
{
PBYTE pb = 0;
ULONG cb = 0;
while (0 <= BCryptExportKey(hKey, 0, BCRYPT_PUBLIC_KEY_BLOB, pb, cb, &cb, 0))
{
if (pb)
{
PSTR psz = 0;
ULONG cch = 0;
while (CryptBinaryToStringA(pb, cb, CRYPT_STRING_HEXASCII, psz, &cch))
{
if (psz)
{
DbgPrint(psz);
break;
}
psz = (PSTR)alloca(cch * sizeof(char));
}
break;
}
pb = (PBYTE)alloca(cb);
}
BCryptDestroyKey(hKey);
}
}
output is
45 43 53 31 20 00 00 00 4c 77 e2 f1 fa 10 17 85 ECS1 ...Lw......
2d 48 d5 71 48 5c 5e bb b5 be 80 cc 5d 48 61 25 -H.qH\^.....]Ha
85 da 3b f3 6e 52 0f 72 f2 6f e4 aa 87 81 ba 2a ..;.nR.r.o.....*
b6 b4 ae 8d 41 76 b3 3a 43 1f a1 b1 4c 78 43 07 ....Av.:C...LxC.
5b cb b4 18 d2 9c f1 c8 [.......