5

This is a continuation of my previous question, Boost.Asio SSL context load_verify_paths not loading certificate.

I have a CA file that is generated from the system CA store on Windows. Any invalid/expired certificates are not included in the generated file.

My Boost Asio code does not want to verify using this CA store. The file loads just fine, with ssl::context::load_verify_file returning without an error, but the verify callback keeps getting called with preverified set to false.

Here is an example run of openssl using the certificate file:

Loading 'screen' into random state - done
CONNECTED(00000178)
depth=3 /C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
verify return:1
depth=2 /C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA
Certification Authority
verify return:1
depth=1 /C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA
Domain Validation Secure Server CA
verify return:1
depth=0 /OU=Domain Control Validated/OU=PositiveSSL/CN=example.org
verify return:1
---
Certificate chain
 0 s:/OU=Domain Control Validated/OU=PositiveSSL/CN=example.org
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
 1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
 2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
   i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
---
Server certificate
-----BEGIN CERTIFICATE-----
...snipped...
-----END CERTIFICATE-----
subject=/OU=Domain Control Validated/OU=PositiveSSL/CN=example.org
issuer=/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
---
Acceptable client certificate CA names
/OU=Domain Control Validated/OU=PositiveSSL/CN=example.org
/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
---
SSL handshake has read 5399 bytes and written 334 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 2048 bit
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : DHE-RSA-AES256-SHA
    Session-ID: 8C04CCAE22F4B111AD13F10448ACDAD0C7F567F22C0D05829BDAE1DF9F29A005

    Session-ID-ctx:
    Master-Key: B99A5E1D0C3CF5421C41CDE88B6F21FD9816800409775C497859FAFCAE3A8942
1A670D72808C804A33A10BF9A26B22AB
    Key-Arg   : None
    Start Time: 1477664766
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)

And another run of openssl without the certificate file:

Loading 'screen' into random state - done
CONNECTED(00000178)
depth=2 /C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
verify error:num=20:unable to get local issuer certificate
verify return:0
---
Certificate chain
 0 s:/OU=Domain Control Validated/OU=PositiveSSL/CN=example.org
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
 1 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
   i:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
 2 s:/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
   i:/C=SE/O=AddTrust AB/OU=AddTrust External TTP Network/CN=AddTrust External CA Root
---
Server certificate
-----BEGIN CERTIFICATE-----
...snipped...
-----END CERTIFICATE-----
subject=/OU=Domain Control Validated/OU=PositiveSSL/CN=example.org
issuer=/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
---
Acceptable client certificate CA names
/OU=Domain Control Validated/OU=PositiveSSL/CN=example.org
/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Certification Authority
/C=GB/ST=Greater Manchester/L=Salford/O=COMODO CA Limited/CN=COMODO RSA Domain Validation Secure Server CA
---
SSL handshake has read 5399 bytes and written 334 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 2048 bit
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : DHE-RSA-AES256-SHA
    Session-ID: 18036DF0E136729F9FE1BD8F51AA2FEF687D84D43918895B3F5847A2EB8C7109

    Session-ID-ctx:
    Master-Key: DA4BCC28FB9F4F5BCD9C9FBB51AA02B8A380F0580258A0F67E56BA2BFD627C54
AB700C343F0741A77AC037E54272EB1E
    Key-Arg   : None
    Start Time: 1477665097
    Timeout   : 300 (sec)
    Verify return code: 20 (unable to get local issuer certificate)

As you can see, the certificate file contains the appropriate root certificate, since openssl only connects when using the file.

My question is, why does openssl verify using this file, but Boost Asio does not? How do I correct the issue, so Boost Asio verifies using the generated CA store?

Community
  • 1
  • 1
owacoder
  • 4,815
  • 20
  • 47

2 Answers2

9

If your goal is to use the windows ca store with boost asio, you can "attach" the certificates from the store to a boost ssl context with something like this:

#include <boost/asio/ssl/context.hpp>
#include <wincrypt.h>

void add_windows_root_certs(boost::asio::ssl::context &ctx)
{
    HCERTSTORE hStore = CertOpenSystemStore(0, "ROOT");
    if (hStore == NULL) {
        return;
    }

    X509_STORE *store = X509_STORE_new();
    PCCERT_CONTEXT pContext = NULL;
    while ((pContext = CertEnumCertificatesInStore(hStore, pContext)) != NULL) {
        // convert from DER to internal format
        X509 *x509 = d2i_X509(NULL,
                              (const unsigned char **)&pContext->pbCertEncoded,
                              pContext->cbCertEncoded);
        if(x509 != NULL) {
            X509_STORE_add_cert(store, x509);
            X509_free(x509);
        }
    }

    CertFreeCertificateContext(pContext);
    CertCloseStore(hStore, 0);

    // attach X509_STORE to boost ssl context
    SSL_CTX_set_cert_store(ctx.native_handle(), store);
}

This will load the certificates from the windows ca store (similar to the question you linked). But instead of converting the certificates to base64 it uses d2i_X509 to convert them to the internal OpenSSL format and adds them to an OpenSSL X509_STORE. Then SSL_CTX_set_cert_store attaches that store to the boost ssl context. You can use that to set up your ssl context and then use that for the ssl socket:

namespace ssl = boost::asio::ssl;
ssl::context ctx(ssl::context::tlsv12_client);
ctx.set_options(ssl::context::default_workarounds
                            | ssl::context::no_sslv2
                            | ssl::context::no_sslv3
                            | ssl::context::tlsv12_client);

add_windows_root_certs(ctx);
ctx.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert);

// use custom verify_callback here for debugging purposes
ctx.set_verify_callback(ssl::rfc2818_verification(address));

ssl::stream<boost::asio::ip::tcp::socket> socket(io, ctx);
// socket ready to connect to ssl host
rbr
  • 433
  • 4
  • 10
1

The problem was the creation of another boost::asio::ssl::context that was not loaded with system certificates, instead, it was using set_default_verify_paths, which works on Linux but not Windows. It worked on Linux because the system certificates are in the default verify path, but on Windows, it's an empty directory (I had not set up my OpenSSL install with any certificates).

For anyone else baffled by a similar problem, please, make sure you are actually using the boost::asio::ssl::context you are loading certificates into, so you don't waste as much time on it as I did.

owacoder
  • 4,815
  • 20
  • 47