Get RSA public key from CRYPT_BIT_BLOB in WinHTTP?
Asked Answered
Z

1

9

I am trying to get the RSA public key info in WinHTTP. So far I've got the certificate info in CERT_CONTEXT structure. I can get encryption algorithm and others as follows:

PCCERT_CONTEXT cert;
DWORD certLen = sizeof(PCCERT_CONTEXT);
WinHttpQueryOption(hRequest, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert, &certLen);

The encryption algorithm is got by

LPSTR pubKeyAlgo = cert->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId;

And we might get the public key as follows:

CRYPT_BIT_BLOB pubKey = cert->pCertInfo->SubjectPublicKeyInfo.PublicKey;
BYTE *p = pKey.pbData;

But as per the documentation, this is an encoded form:

PublicKey

BLOB containing an encoded public key.

So how to get the actual RSA public key parameters like modulus and exponent?

Zachary answered 26/5, 2014 at 6:43 Comment(7)
The pbData is a bit string that is the containment of another ASN.1 encoding. The encoding is A SEQUENCE of two INTEGER (the modulus and public exponent) you seek.Disturbance
Thanks, but how to decode it in hexadecimal or decimal?Zachary
Are you asking how you decode an ASN.1 SEQUENCE (and the aforementioned two INTEGER values?Disturbance
Yes. So that I get the modulus and public exponent..Zachary
See the wiki on ASN.1 encoding. It's pretty straight forward. I'll warn you not to be shocked if the key modulus is one byte longer than you may expect (it may be, say 257 bytes rather than the 256 you expect for a 2048-bit key). The reason is INTEGER in ASN.1 is signed, and RSA key moduli are not. To ensure it is represented as positive it is common to prepend a 00 byte to the value if the true unsigned value's high-bit is lit. You'll know it when/if you see it; trust me.Disturbance
I tried decoding it using this function: CryptDecodeObjectEx() but it's returning CRYPT_E_ASN1_BADTAG Can you please provide some sample codes? Please.Zachary
Got it working. Thanks! It's actually a structure of quite a more things.Zachary
U
3

Here's an example of extracting the modulus and exponent of a RSA Public Key using Wincrypt. The example certificate is a SSH client cert from RFC 6187. The certificate can be used for SSH's x509v3-rsa2048-sha256 signature encoding method. The OpenSSL conf file is at the end of the answer, but was adapted from How to create a self-signed certificate with OpenSSL.

RSA_PUBLIC_KEY_XX is used to cast the blob returned from CryptDecodeObject to the packed RSA structures the API returns. The name *_XX was used to avoid colliding with a future Microsoft structure name.

#include <iostream>
#include <iomanip>
#include <sstream>
#include <memory>
#include <string>
#include <vector>

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <wincrypt.h>

#pragma comment(lib, "kernel32")
#pragma comment(lib, "crypt32")

typedef struct _RSA_PUBLIC_KEY_XX
{
    #pragma pack(push, 1)
    PUBLICKEYSTRUC PublicKeyStruc;
    RSAPUBKEY RsaPubKey;
    BYTE RsaModulus[ANYSIZE_ARRAY];
    #pragma pack(pop)
} RSA_PUBLIC_KEY_XX, *PRSA_PUBLIC_KEY_XX, *const PCRSA_PUBLIC_KEY_XX;

extern std::string pemCertificate;
void PrintErrorAndThrow(const char* fnName, DWORD dwError);

int main(int argc, char* argv[])
{
    DWORD dwSize = 0;
    std::vector<BYTE> asnCertificate;
    std::vector<BYTE> rsaPublicKey;

    BOOL bResult = CryptStringToBinary(
        &pemCertificate[0],
        static_cast<DWORD>(pemCertificate.size()),
        CRYPT_STRING_BASE64HEADER,
        NULL, &dwSize,
        NULL, NULL);

    if (bResult == FALSE)
        PrintErrorAndThrow("CryptStringToBinary", GetLastError());

    asnCertificate.resize(dwSize);
    dwSize = static_cast<DWORD>(asnCertificate.size());

    bResult = CryptStringToBinary(
        &pemCertificate[0],
        static_cast<DWORD>(pemCertificate.size()),
        CRYPT_STRING_BASE64HEADER,
        &asnCertificate[0], &dwSize,
        NULL, NULL);

    if (bResult == FALSE)
        PrintErrorAndThrow("CryptStringToBinary", GetLastError());

    PCCERT_CONTEXT pContext = CertCreateCertificateContext(
        X509_ASN_ENCODING,
        static_cast<PBYTE>(&asnCertificate[0]),
        static_cast<DWORD>(asnCertificate.size()));

    if (pContext == NULL)
        PrintErrorAndThrow("CertCreateCertificateContext", GetLastError());

    PCERT_INFO pInfo = pContext->pCertInfo;
    PCERT_PUBLIC_KEY_INFO pSubjectPublicKeyInfo = &pInfo->SubjectPublicKeyInfo;
    PCRYPT_BIT_BLOB pPublicKey = &pSubjectPublicKeyInfo->PublicKey;

    bResult = CryptDecodeObject(
        pContext->dwCertEncodingType,
        RSA_CSP_PUBLICKEYBLOB,
        pPublicKey->pbData, 
        pPublicKey->cbData,
        0,  // flags
        NULL, &dwSize);

    if (bResult == FALSE)
        PrintErrorAndThrow("CryptDecodeObject", GetLastError());

    rsaPublicKey.resize(dwSize);
    dwSize = static_cast<DWORD>(rsaPublicKey.size());

    bResult = CryptDecodeObject(
        pContext->dwCertEncodingType,
        RSA_CSP_PUBLICKEYBLOB,
        pPublicKey->pbData, 
        pPublicKey->cbData,
        0,  // flags
        &rsaPublicKey[0],
        &dwSize);

    if (bResult == FALSE)
        PrintErrorAndThrow("CryptDecodeObject", GetLastError());

    PCRSA_PUBLIC_KEY_XX pRsaPublicKey = reinterpret_cast<PCRSA_PUBLIC_KEY_XX>(&rsaPublicKey[0]);
    DWORD dwModulusSize = pRsaPublicKey->RsaPubKey.bitlen/8;
    DWORD dwExponent = pRsaPublicKey->RsaPubKey.pubexp;
    DWORD dwMagic = pRsaPublicKey->RsaPubKey.magic;

    if (dwMagic != 0x31415352 /*RSA1*/)
        PrintErrorAndThrow("CryptDecodeObject", ERROR_INVALID_DATA);

    // The modulus is little-endian. Iterate in reverse for big-endian.
    std::ostringstream oss;
    for (size_t i=dwModulusSize; i > 0; --i)
    {
        oss << std::hex << std::setw(2) << std::setfill('0');
        oss << (unsigned int)pRsaPublicKey->RsaModulus[i-1];
        if (i > 1) { oss << ":"; }
    }    

    std::cout << "Magic: " << "0x" << std::hex << dwMagic << std::endl;
    std::cout << "Exponent: " << std::dec << dwExponent << std::endl;
    std::cout << "Modulus: " << oss.str() << std::endl;

    if (pContext)
        CertFreeCertificateContext(pContext);

    return 0;
}

void PrintErrorAndThrow(const char* fnName, DWORD dwError)
{
    std::ostringstream oss;
    oss << fnName << " failed, error " << dwError << " (";
    oss << "0x" << std::hex << dwError << ")" << std::endl;
    std::cerr << oss.str() << std::endl;
    throw std::runtime_error(oss.str());
}

std::string pemCertificate = 
    "-----BEGIN CERTIFICATE-----\r\n"
    "MIIESjCCAzKgAwIBAgIUb2d597NowQ3H/4UE/qjrqzT+EPkwDQYJKoZIhvcNAQEL\r\n"
    "BQAwfDELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk5ZMREwDwYDVQQHDAhOZXcgWW9y\r\n"
    "azEVMBMGA1UECgwMRXhhbXBsZSwgTExDMREwDwYDVQQDDAhKb2huIERvZTEjMCEG\r\n"
    "CSqGSIb3DQEJARYUam9obi5kb2VAZXhhbXBsZS5jb20wHhcNMTkwOTE3MTU1OTA1\r\n"
    "WhcNMjAwOTE2MTU1OTA1WjB8MQswCQYDVQQGEwJVUzELMAkGA1UECAwCTlkxETAP\r\n"
    "BgNVBAcMCE5ldyBZb3JrMRUwEwYDVQQKDAxFeGFtcGxlLCBMTEMxETAPBgNVBAMM\r\n"
    "CEpvaG4gRG9lMSMwIQYJKoZIhvcNAQkBFhRqb2huLmRvZUBleGFtcGxlLmNvbTCC\r\n"
    "ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOd2VZa5G8clxnT90+1mkCGp\r\n"
    "ufPAEXKLwX7THhIOf00NYRpumuhvMBUHFNcH6H17NHM/iauLacXsYIJlHWmTTl1t\r\n"
    "0r7fxzVTA4NF8Eedpv+RGXRm3eC/e+Nz/MkdInWZ7qkp6AE9juiEop9rmQPtwM/f\r\n"
    "4/LwE2fi+XyhFTwMr2zIUuEVlk1fWaabOVtHweLCWxvstWtomxN4egVY4kB634zM\r\n"
    "VywMGRz4YrKzFxSaTYWt5wNoMoPRGeEKFwhBfuXmHIO9QQSxqIfBEoxQfBdppzSN\r\n"
    "5qe84Gn9nFSepgmJtw6XLxm31SgMBOuKiwFbFni+oZEnRg9ssy7UYmaAaxcqfikC\r\n"
    "AwEAAaOBwzCBwDAdBgNVHQ4EFgQU1nhsQ/gKlSadP9uEUNMj5ezkt7gwHwYDVR0j\r\n"
    "BBgwFoAU1nhsQ/gKlSadP9uEUNMj5ezkt7gwCQYDVR0TBAIwADALBgNVHQ8EBAMC\r\n"
    "B4AwHwYDVR0RBBgwFoEUam9obi5kb2VAZXhhbXBsZS5jb20wMAYJYIZIAYb4QgEN\r\n"
    "BCMWIU9wZW5TU0wgR2VuZXJhdGVkIFNTSCBDZXJ0aWZpY2F0ZTATBgNVHSUEDDAK\r\n"
    "BggrBgEFBQcDFTANBgkqhkiG9w0BAQsFAAOCAQEAUkCY+WX55RuqzuuRMt5hEzUh\r\n"
    "xfCVLddfBxmokmsodc8qgHlsNkRLLZATyKgnFF5FGIroiNF6QcJ5QJXDZZyz2+6p\r\n"
    "m0V1jHF51BtoGxBR1I1ERLT0QOJHC53R6+/OPIaADKdPoXHIpPVQhJww6e1X6CU1\r\n"
    "IjIWmliqixhvId5WbU5et6ZpFNs2ZFbPGBk4RrKR5SjxwLj7Jm+THzV660xIYsow\r\n"
    "/CP5ox7ga7+OFR6q4kFQldc6Ah5bRFKI8fgWHpnhlS9hB29BiMjrC/p7bm0CL2Su\r\n"
    "QER4F/HCpBHBDwkweg1h9DrT62DYgqvgvRxR/j8GPL7Hqg2kLuFcHt3SkQeNZg==\r\n"
    "-----END CERTIFICATE-----\r\n";

Compile the program using a Visual Studio Developer command prompt.

C:\Users\Test>cl.exe /nologo /W4 /Zi /TP /GR /EHsc cert_test.cxx /link /out:cert_test.exe
cert_test.cxx
cert_test.cxx(37) : warning C4100: 'argv' : unreferenced formal parameter
cert_test.cxx(37) : warning C4100: 'argc' : unreferenced formal parameter

And running the program results in the following.

C:\Users\Test>.\cert_test.exe
Magic: 0x31415352
Exponent: 65537
Modulus: e7:76:55:96:b9:1b:c7:25:c6:74:fd:d3:ed:66:90:21:a9:b9:f3:c0:11:72:8b:c1
:7e:d3:1e:12:0e:7f:4d:0d:61:1a:6e:9a:e8:6f:30:15:07:14:d7:07:e8:7d:7b:34:73:3f:8
9:ab:8b:69:c5:ec:60:82:65:1d:69:93:4e:5d:6d:d2:be:df:c7:35:53:03:83:45:f0:47:9d:
a6:ff:91:19:74:66:dd:e0:bf:7b:e3:73:fc:c9:1d:22:75:99:ee:a9:29:e8:01:3d:8e:e8:84
:a2:9f:6b:99:03:ed:c0:cf:df:e3:f2:f0:13:67:e2:f9:7c:a1:15:3c:0c:af:6c:c8:52:e1:1
5:96:4d:5f:59:a6:9b:39:5b:47:c1:e2:c2:5b:1b:ec:b5:6b:68:9b:13:78:7a:05:58:e2:40:
7a:df:8c:cc:57:2c:0c:19:1c:f8:62:b2:b3:17:14:9a:4d:85:ad:e7:03:68:32:83:d1:19:e1
:0a:17:08:41:7e:e5:e6:1c:83:bd:41:04:b1:a8:87:c1:12:8c:50:7c:17:69:a7:34:8d:e6:a
7:bc:e0:69:fd:9c:54:9e:a6:09:89:b7:0e:97:2f:19:b7:d5:28:0c:04:eb:8a:8b:01:5b:16:
78:be:a1:91:27:46:0f:6c:b3:2e:d4:62:66:80:6b:17:2a:7e:29

Here is a dump of the certificate using OpenSSL's x509 subcommand.

$ cat cert_test.cert.pem | openssl x509 -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            6f:67:79:f7:b3:68:c1:0d:c7:ff:85:04:fe:a8:eb:ab:34:fe:10:f9
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, ST = NY, L = New York, O = "Example, LLC", CN = John Doe, emailAddress = [email protected]
        Validity
            Not Before: Sep 17 15:59:05 2019 GMT
            Not After : Sep 16 15:59:05 2020 GMT
        Subject: C = US, ST = NY, L = New York, O = "Example, LLC", CN = John Doe, emailAddress = [email protected]
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:e7:76:55:96:b9:1b:c7:25:c6:74:fd:d3:ed:66:
                    90:21:a9:b9:f3:c0:11:72:8b:c1:7e:d3:1e:12:0e:
                    7f:4d:0d:61:1a:6e:9a:e8:6f:30:15:07:14:d7:07:
                    e8:7d:7b:34:73:3f:89:ab:8b:69:c5:ec:60:82:65:
                    1d:69:93:4e:5d:6d:d2:be:df:c7:35:53:03:83:45:
                    f0:47:9d:a6:ff:91:19:74:66:dd:e0:bf:7b:e3:73:
                    fc:c9:1d:22:75:99:ee:a9:29:e8:01:3d:8e:e8:84:
                    a2:9f:6b:99:03:ed:c0:cf:df:e3:f2:f0:13:67:e2:
                    f9:7c:a1:15:3c:0c:af:6c:c8:52:e1:15:96:4d:5f:
                    59:a6:9b:39:5b:47:c1:e2:c2:5b:1b:ec:b5:6b:68:
                    9b:13:78:7a:05:58:e2:40:7a:df:8c:cc:57:2c:0c:
                    19:1c:f8:62:b2:b3:17:14:9a:4d:85:ad:e7:03:68:
                    32:83:d1:19:e1:0a:17:08:41:7e:e5:e6:1c:83:bd:
                    41:04:b1:a8:87:c1:12:8c:50:7c:17:69:a7:34:8d:
                    e6:a7:bc:e0:69:fd:9c:54:9e:a6:09:89:b7:0e:97:
                    2f:19:b7:d5:28:0c:04:eb:8a:8b:01:5b:16:78:be:
                    a1:91:27:46:0f:6c:b3:2e:d4:62:66:80:6b:17:2a:
                    7e:29
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier: 
                D6:78:6C:43:F8:0A:95:26:9D:3F:DB:84:50:D3:23:E5:EC:E4:B7:B8
            X509v3 Authority Key Identifier: 
                keyid:D6:78:6C:43:F8:0A:95:26:9D:3F:DB:84:50:D3:23:E5:EC:E4:B7:B8

            X509v3 Basic Constraints: 
                CA:FALSE
            X509v3 Key Usage: 
                Digital Signature
            X509v3 Subject Alternative Name: 
                email:[email protected]
            Netscape Comment: 
                OpenSSL Generated SSH Certificate
            X509v3 Extended Key Usage: 
                SSH Client
    Signature Algorithm: sha256WithRSAEncryption
         52:40:98:f9:65:f9:e5:1b:aa:ce:eb:91:32:de:61:13:35:21:
         c5:f0:95:2d:d7:5f:07:19:a8:92:6b:28:75:cf:2a:80:79:6c:
         36:44:4b:2d:90:13:c8:a8:27:14:5e:45:18:8a:e8:88:d1:7a:
         41:c2:79:40:95:c3:65:9c:b3:db:ee:a9:9b:45:75:8c:71:79:
         d4:1b:68:1b:10:51:d4:8d:44:44:b4:f4:40:e2:47:0b:9d:d1:
         eb:ef:ce:3c:86:80:0c:a7:4f:a1:71:c8:a4:f5:50:84:9c:30:
         e9:ed:57:e8:25:35:22:32:16:9a:58:aa:8b:18:6f:21:de:56:
         6d:4e:5e:b7:a6:69:14:db:36:64:56:cf:18:19:38:46:b2:91:
         e5:28:f1:c0:b8:fb:26:6f:93:1f:35:7a:eb:4c:48:62:ca:30:
         fc:23:f9:a3:1e:e0:6b:bf:8e:15:1e:aa:e2:41:50:95:d7:3a:
         02:1e:5b:44:52:88:f1:f8:16:1e:99:e1:95:2f:61:07:6f:41:
         88:c8:eb:0b:fa:7b:6e:6d:02:2f:64:ae:40:44:78:17:f1:c2:
         a4:11:c1:0f:09:30:7a:0d:61:f4:3a:d3:eb:60:d8:82:ab:e0:
         bd:1c:51:fe:3f:06:3c:be:c7:aa:0d:a4:2e:e1:5c:1e:dd:d2:
         91:07:8d:66

Here is the OpenSSL configuration file from How to create a self-signed certificate with OpenSSL tweaked for a SSH client.

# Create a self signed certificate:
#     openssl req -config cert_test.conf -new -x509 -sha256 -newkey rsa:2048 \
#       -nodes -keyout cert_test.key.pem -days 365 -out cert_test.cert.pem
#
# Create a signing request (notice the lack of -x509 option):
#     openssl req -config cert_test.conf -new -sha256 -newkey rsa:2048 \
#       -nodes -keyout cert_test.key.pem -days 365 -out cert_test.cert.pem

[ req ]
default_bits        = 2048
default_keyfile     = server-key.pem
distinguished_name  = subject
req_extensions      = req_ext
x509_extensions     = x509_ext
string_mask         = utf8only

# The Subject DN can be formed using X501 or RFC 4514 (see RFC 4519 for a description).
[ subject ]
countryName         = Country Name (2 letter code)
countryName_default     = US

stateOrProvinceName     = State or Province Name (full name)
stateOrProvinceName_default = NY

localityName            = Locality Name (eg, city)
localityName_default        = New York

organizationName         = Organization Name (eg, company)
organizationName_default    = Example, LLC

commonName          = Common Name (e.g. server FQDN or YOUR name)
commonName_default        = John Doe

emailAddress            = Email Address
emailAddress_default    = [email protected]

# Section x509_ext is used when generating a self-signed certificate. I.e., openssl req -x509 ...
[ x509_ext ]

subjectKeyIdentifier        = hash
authorityKeyIdentifier      = keyid,issuer

basicConstraints        = CA:FALSE
keyUsage                = digitalSignature
subjectAltName          = @alternate_names
nsComment               = "OpenSSL Generated SSH Certificate"
extendedKeyUsage        = secureShellClient

# Section req_ext is used when generating a certificate signing request. I.e., openssl req ...
[ req_ext ]

subjectKeyIdentifier    = hash

basicConstraints        = CA:FALSE
keyUsage                = digitalSignature
subjectAltName          = @alternate_names
nsComment               = "OpenSSL Generated SSH Certificate"
extendedKeyUsage        = secureShellClient

[ alternate_names ]

email            = [email protected]
Underneath answered 17/9, 2019 at 16:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.