Load an PEM encoded X.509 certificate into Windows CryptoAPI
Asked Answered
A

3

15

I need to load a PEM encoded X.509 certificate into a Windows Crypto API context to use with C++. They are the ones that have -----BEGIN RSA XXX KEY----- and -----END RSA XXX KEY-----. I found examples for Python and .NET but they use specific functions I can't relate to the plain Windows Crypto API.

I understand how to encrypt/decrypt once I've got a HCRYPTKEY. BUT, I just don't get how to import the Base64 blob in the .PEM file(s) and get a HCRYPTKEY that I can use out of it.

I have that strange feeling that there is more to it than simply calling CryptDecodeObject().

Any pointers that can put me on track? I've already lost two days doing "trial & error" programming and getting nowhere.

Almire answered 5/8, 2009 at 4:24 Comment(0)
M
22

KJKHyperion said in his answer:

I discovered the "magic" sequence of calls to import a RSA public key in PEM format. Here you go:

  1. decode the key into a binary blob with CryptStringToBinary; pass CRYPT_STRING_BASE64HEADER in dwFlags
  2. decode the binary key blob into a CERT_PUBLIC_KEY_INFO with CryptDecodeObjectEx; pass X509_ASN_ENCODING in dwCertEncodingType and X509_PUBLIC_KEY_INFO in lpszStructType
  3. decode the PublicKey blob from the CERT_PUBLIC_KEY_INFO into a RSA key blob with CryptDecodeObjectEx; pass X509_ASN_ENCODING in dwCertEncodingType and RSA_CSP_PUBLICKEYBLOB in lpszStructType
  4. import the RSA key blob with CryptImportKey

This sequence really helped me understand what's going on, but it didn't work for me as-is. The second call to CryptDecodeObjectEx gave me an error: "ASN.1 bad tag value met". After many attempts at understanding Microsoft documentation, I finally realized that the output of the fist decode cannot be decoded as ASN again, and that it is actually ready for import. With this understanding I found the answer in the following link:

http://www.ms-news.net/f2748/problem-importing-public-key-4052577.html

Following is my own program that imports a public key from a .pem file to a CryptApi context:

int main()
{
    char           pemPubKey[2048];
    int            readLen;
    char           derPubKey[2048];
    size_t         derPubKeyLen = 2048;
    CERT_PUBLIC_KEY_INFO *publicKeyInfo;
    int            publicKeyInfoLen;
    HANDLE         hFile;
    HCRYPTPROV     hProv = 0;
    HCRYPTKEY      hKey = 0;

    /*
     * Read the public key cert from the file
     */
    hFile = CreateFileA( "c:\\pub.pem", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL );
    if ( hFile == INVALID_HANDLE_VALUE )
    {
        fprintf( stderr, "Failed to open file. error: %d\n", GetLastError() );
    }

    if ( !ReadFile( hFile, pemPubKey, 2048, &readLen, NULL ) )
    {
        fprintf( stderr, "Failed to read file. error: %d\n", GetLastError() );
    }

    /*
     * Convert from PEM format to DER format - removes header and footer and decodes from base64
     */
    if ( !CryptStringToBinaryA( pemPubKey, 0, CRYPT_STRING_BASE64HEADER, derPubKey, &derPubKeyLen, NULL, NULL ) )
    {
        fprintf( stderr, "CryptStringToBinary failed. Err: %d\n", GetLastError() );
    }

    /*
     * Decode from DER format to CERT_PUBLIC_KEY_INFO
     */
    if ( !CryptDecodeObjectEx( X509_ASN_ENCODING, X509_PUBLIC_KEY_INFO, derPubKey, derPubKeyLen, 
                               CRYPT_ENCODE_ALLOC_FLAG, NULL, &publicKeyInfo, &publicKeyInfoLen ) )
    {
        fprintf( stderr, "CryptDecodeObjectEx 1 failed. Err: %p\n", GetLastError() );
        return -1;
    }

    /*
     * Acquire context 
     */
    if( !CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) )
    {
        {
            printf( "CryptAcquireContext failed - err=0x%x.\n", GetLastError() );
            return -1;
        }
    }

    /*
     * Import the public key using the context
     */
    if ( !CryptImportPublicKeyInfo( hProv, X509_ASN_ENCODING, publicKeyInfo, &hKey ) )
    {
        fprintf( stderr, "CryptImportPublicKeyInfo failed. error: %d\n", GetLastError() );
        return -1;
    }
    LocalFree( publicKeyInfo );

    /*
     * Now use hKey to encrypt whatever you need.
     */

    return 0;
}
Mixie answered 27/9, 2010 at 11:25 Comment(2)
could you provide WORKING public key? Your program still not work for me..but my key is really valid.Sanity
@Sanity depending on the implementation, the digital signature might need to be fully byte-reversed before being verifiedCorrey
H
12

I discovered the "magic" sequence of calls to import a RSA public key in PEM format. Here you go:

  1. decode the key into a binary blob with CryptStringToBinary; pass CRYPT_STRING_BASE64HEADER in dwFlags
  2. decode the binary key blob into a CERT_PUBLIC_KEY_INFO with CryptDecodeObjectEx; pass X509_ASN_ENCODING in dwCertEncodingType and X509_PUBLIC_KEY_INFO in lpszStructType
  3. decode the PublicKey blob from the CERT_PUBLIC_KEY_INFO into a RSA key blob with CryptDecodeObjectEx; pass X509_ASN_ENCODING in dwCertEncodingType and RSA_CSP_PUBLICKEYBLOB in lpszStructType
  4. import the RSA key blob with CryptImportKey
Hyperspace answered 14/1, 2010 at 14:1 Comment(0)
T
2

I'm currently facing the same difficulty. I haven't finished coding a solution but as I understand it you need to strip off the ----- BEGIN etc ----- and ----- END etc ------ tags and decode the Base64.

This leaves you with a DER encoded string, which you need to parse to get the modulus and public exponent. From those you can populate the PUBLICKEYSTRUC and RSAPUBKEY structures. Good luck ;-)

Thither answered 26/8, 2009 at 11:28 Comment(1)
Look into CryptDecodeObjectEx with X509_ASN_ENCODING and RSA_CSP_PUBLICKEYBLOB options. Seems to decode and fill the structure correctly, but you may still need to swap the byte-order of some parts.Thither

© 2022 - 2024 — McMap. All rights reserved.