Search code examples
c++winapicryptographycng

How to convert a public key in PEM format to a CNG key blob?


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?


Solution

  • for convert PEM public key to CNG - generic steps is next:

    1. CryptStringToBinaryA for convert string to binary
    2. CryptDecodeObjectEx with X509_PUBLIC_KEY_INFO - convert binary to CERT_PUBLIC_KEY_INFO
    3. CryptImportPublicKeyInfoEx2 - import CERT_PUBLIC_KEY_INFO to CNG

    example 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                            [.......