Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get RSA public key from CRYPT_BIT_BLOB in WinHTTP?

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?

like image 918
kushpf Avatar asked May 26 '14 06:05

kushpf


1 Answers

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]
like image 155
jww Avatar answered Nov 04 '22 11:11

jww