2

How can I use a certificate read from the Windows Certificate store in OpenSSL?

I've setup a Windows project based on Boost Beasts's http_server_async_ssl.cpp - basically added an https server into my project. When I use the hardcoded certificate in that example, everything works, but when I export that certificate and load it into my code, I get "no shared cipher".

I set up a minimal example. In main.cpp:79-83 I use load_server_certificate which loads a certificate based on the thumbprint - this is what fails with "no shared cipher". If I instead comment that out and use load_static_server_certificate (the original example hard coded keys) it works (I can post, get a response, etc.).

Basically, this program searches for a given certificate thumbprint (searching through the store with CertEnumCertificatesInStore), and once it finds a match, it loads the certificate. I based loading the certificate into the context off of an example I found while reading every Stack Overflow question about this - this answer especially.

ex/certificate_helpers.cpp:

509 = d2i_X509(nullptr, const_cast<const BYTE**>(&pCertContext->pbCertEncoded), pCertContext->cbCertEncoded);
bio = BIO_new(BIO_s_mem());
PEM_write_bio_X509(bio, x509);
ctx.add_certificate_authority(boost::asio::buffer(certificates.data(), certificates.size()), ec);

My context is setup before my stream starts (that seemed to be a common question), my certificate appears to be loading correctly. In the certificate store my certificate has a private key.

My next step is to figure out if I can extract that private key and potentially load it with add_private_key, I also see the that the example uses a Diffie-Hellman parameter, if this is also required I have no idea how to use it - do I need it?

halfer
  • 19,824
  • 17
  • 99
  • 186
Matt
  • 1,928
  • 24
  • 44

1 Answers1

2

For using the certificate you could use use_certificate interface like you do for the add_certificate_authority interface.

For the private key is gets a little harder. See my answer here as a example of reading a RSA private key into a EVP_PKEY (ECC would require different code). Then you can use the PEM_write_bio_PrivateKey function to generate a pem blob and use the use_private_key interface to use it.

You can also skip the conversation to PEM format if you call the openssl functions directly using the ssl_context native_handle method.

e.g.

X509 *cert = readCert();
SSL_CTX_use_certificate(ctx.native_handle(), cert); // instead of the use_certificate call

EVP_PKEY *key = readKey();
SSL_use_PrivateKey(ctx.native_handle(), key); // instead of the use_private_key call

X509 *cert = readChainCert();
SSL_CTX_add_extra_chain_cert(ctx.native_handle(), cert); // use chain cert

X509 *cert = readCaCert();
X509_STORE *store = SSL_CTX_get_cert_store(ctx.native_handle()); // instead of the add_certificate_authority call
X509_STORE_add_cert(store, cert);

Update: Add example to use a PCCERT_CONTEXT (i.e. uses CryptAcquireCertificatePrivateKey api).

EVP_PKEY* extract_private_key(const PCCERT_CONTEXT context)
{
    HCRYPTPROV_OR_NCRYPT_KEY_HANDLE key_handle;
    DWORD key_spec = 0;
    BOOL free_key;
    if (!CryptAcquireCertificatePrivateKey(context, CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG | CRYPT_ACQUIRE_SILENT_FLAG, nullptr, &key_handle, &key_spec, &free_key))
    {
        return nullptr;
    }

    EVP_PKEY* pkey = nullptr;
    DWORD length = 0;
    if(SUCCEEDED(NCryptExportKey(key_handle, NULL, BCRYPT_RSAFULLPRIVATE_BLOB, nullptr, nullptr, 0, &length, 0)))
    {
        auto data = std::make_unique<BYTE[]>(length);

        if(SUCCEEDED(NCryptExportKey(key_handle, NULL, BCRYPT_RSAFULLPRIVATE_BLOB, nullptr, data.get(), length, &length, 0)))
        {
            // https://learn.microsoft.com/en-us/windows/desktop/api/bcrypt/ns-bcrypt-_bcrypt_rsakey_blob
            auto const blob = reinterpret_cast<BCRYPT_RSAKEY_BLOB*>(data.get());

            if(blob->Magic == BCRYPT_RSAFULLPRIVATE_MAGIC)
            {
                auto rsa = RSA_new();

                // n is the modulus common to both public and private key
                auto const n = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp, blob->cbModulus, nullptr);
                // e is the public exponent
                auto const e = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB), blob->cbPublicExp, nullptr);
                // d is the private exponent
                auto const d = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1, blob->cbModulus, nullptr);

                RSA_set0_key(rsa, n, e, d);

                // p and q are the first and second factor of n
                auto const p = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus, blob->cbPrime1, nullptr); 
                auto const q = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1, blob->cbPrime2, nullptr); 

                RSA_set0_factors(rsa, p, q);

                // dmp1, dmq1 and iqmp are the exponents and coefficient for CRT calculations
                auto const dmp1 = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + blob->cbPrime2, blob->cbPrime1, nullptr); 
                auto const dmq1 = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1, blob->cbPrime2, nullptr); 
                auto const iqmp = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1 + blob->cbPrime2, blob->cbPrime1, nullptr); 

                RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);

                pkey = EVP_PKEY_new();

                // ownership of rsa transferred to pkey
                EVP_PKEY_assign_RSA(pkey, rsa);
            }
        }
    }

    if(free_key)
    {
        NCryptFreeObject(key_handle);
    }

    return pkey;
}
Shane Powell
  • 13,698
  • 2
  • 49
  • 61
  • hey, thanks!. Just to be sure, `readCert`, `readKey`, `readhChainCert`, and `readCaCert` are all just example functions, right? To get the key, I would use [CryptAcquireCertificatePrivateKey](https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptacquirecertificateprivatekey), [CertGetCertificateChain](https://docs.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-certgetcertificatechain), _etc_? Is every part of this required? Or are there things I can skip? This all seems more complicated than I initially thought it would be when I heard "add https". :) – Matt Feb 12 '20 at 13:38
  • The ‘cert’ examples are just the result of the d2i_X509 call. The key would be the result of the call to my answer to read a rsa private key. It’s just skipping the part of converting the cer/key to pem format just to use them in asio (the pem_write_bio_xxx calls). – Shane Powell Feb 12 '20 at 13:48
  • The [answer](https://stackoverflow.com/a/59345986/23235) seems to already start with a handle to the key (I guess from `CryptAcquireCertificatePrivateKey`?) Couldn't I just use that and read it into `SSL_use_PrivateKey`? I'm not sure how to use code in that answer (I'm sorry, I'm really new to certificates.) And everything (cert/pkey/chain cert/authority) are all required? Thanks again. – Matt Feb 12 '20 at 14:21
  • Yes you can use CryptAcquireCertiicatePrivateKey. The problem is that there is no common format for private keys between windows and openssl that you can extract from win32 API. You have to break the private key down into it's bits and then reconstruct it on the other side. That is what my example answer is doing – Shane Powell Feb 12 '20 at 16:21
  • Thanks @ShanePowell, this is exactly what my team needed as well. Is it okay with you if we reuse your solution in an OS project (apache-minifi, licensed under Apache-2.0 License)? – Adam Hunyadi Dec 01 '20 at 12:55
  • 1
    @AdamHunyadi, I have no problems with you doing that. – Shane Powell Dec 01 '20 at 17:31