Reliable way to get root CA certificates on Windows - c++

I'm using boost asio to connect to my valid certificate (signed by root CA). The code I'm using is the ssl client example available from boost docs.
The only line I added is:
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23_client);
ctx.set_default_verify_paths(); <------------- Add default verification paths
ctx.set_password_callback(&password_callback);
client c(io_service, ctx, iterator);
io_service.run();
the problem is: when using this code with a locally installed copy of openSSH (installed from a msi installer) the paths are found correctly and my certificate validated. When I download my own copy of the openSSH repository and compile it this line no longer works and I don't have root CA certificates to validate my own one (therefore it fails).
Since I'd like to eventually distribute these clients on customer machines I'd like to avoid setting environment variables like SSL_CERT_DIR and the like. How can I find root CA certificates with boost asio reliably or alternatively configure my openSSH from source compilation to find them?

You can load the root CAs from the windows CA store. It already contains the "default" trusted root CA certificates and can be managed through certmgr. Use the following function to replace set_default_verify_paths under windows:
#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) {
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);
SSL_CTX_set_cert_store(ctx.native_handle(), store);
}
This will load the certificates from the windows ca store. 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:
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);
#if BOOST_OS_WINDOWS
add_windows_root_certs(ctx);
#else
ctx.set_default_verify_paths();
#endif
ctx.set_password_callback(&password_callback);
client c(io_service, ctx, iterator);
io_service.run();
Note: You will probably need to add crypt32 to your linked libraries.
Note 2: BOOST_OS_WINDOWS needs Boost Predef

Related

Boost ASIO SSL handshake failure

When attempting to securely connect to a remote IMAP server using Boost ASIO, the server handshake fails on every connection. The exception message reads:
handshake: unregistered scheme (STORE routines) [asio.ssl:369098857]
My code is below (url is a std::string_view containing the host URL):
using boost::asio::ip::tcp;
namespace ssl = boost::asio::ssl;
using SSLSocket = ssl::stream<tcp::socket>;
boost::asio::io_context context;
ssl::context ssl_context(ssl::context::tls);
SSLSocket socket(context, ssl_context);
ssl_context.set_default_verify_paths();
tcp::resolver resolver(context);
auto endpoints = resolver.resolve(url, "993");
boost::asio::connect(socket.next_layer(), endpoints);
socket.set_verify_mode(ssl::verify_peer);
socket.set_verify_callback(ssl::host_name_verification(url.data()));
socket.handshake(SSLSocket::client);
The code immediately throws an exception on the final line, which is a blocking synchronous handshake.
The prior two lines set up host name verification, similar to how it's done in the official ASIO tutorial. These checks seem to be causing an issue, however, because when they are removed the handshake succeeds. Obviously, this is not a good solution.
After stepping through some of ASIO's internals, I found that the last three lines of the above snippet could be replaced by:
SSL_set_verify(socket.native_handle(), SSL_VERIFY_PEER, nullptr);
socket.handshake(SSLSocket::client);
and the same error occurs. SSL_set_verify is an OpenSSL function, and the fact that setting a null callback directly causes the same issue makes me think that the issue is with my system's OpenSSL environment and not ASIO or the host name verification callback. However, I have not been able to determine what exactly the error means and what could be causing it.
Here is a list of things I have tried while troubleshooting:
Load the system's certificate (.pem) file explicitly Thinking maybe ASIO and/or OpenSSL's were not able to load the right certificates to do the validation, I found my system's (a Mac) certificate file at /private/etc/ssl/cert.pem. I then inserted the following line:
ssl_context.load_verify_file("/private/etc/ssl/cert.pem");
directly after set_default_verify_paths() is called. My program loads this certificate file without complaining, but it doesn't fix the handshake error.
Use a different version of OpenSSL At first I was using Apple's system version of OpenSSL (which is really LibreSSL 2.8.3). I then rebuilt my code using the Homebrew package manager's version of OpenSSL (OpenSSL 3.0.4). This also did not fix the issue, even when I tried calling load_verify_file as in point 1.
Sanity check using the OpenSSL command-line tool To make sure my network connection and URL/port number were correct, I tried connecting to the IMAP server over SSL using the following command:
openssl s_client -connect my.url.com:993 -crlf -verify 1
and it worked fine, connecting to the IMAP server and enabling me to send/receive IMAP responses.
Has anyone encountered similar issues when using OpenSSL and ASIO? I'm not very familiar with setting up an SSL/TLS connection, but I don't see what could be causing the problem.
Thanks for your help!
Given that openssl s_client -connect my.url.com:993 -crlf -verify 1 succeeds there is not a lot that seems wrong. One thing catches my eye: I'd configure the context before constructing an SSL stream from it:
ssl::context ssl_context(ssl::context::tls);
ssl_context.set_default_verify_paths();
SSLSocket socket(context, ssl_context);
Also, openssl likely uses SNI extensions:
// Set SNI Hostname (many hosts need this to handshake successfully)
if(! SSL_set_tlsext_host_name(socket.native_handle(), hostname.c_str()))
{
throw boost::system::system_error(
::ERR_get_error(), boost::asio::error::get_ssl_category());
}
Finally, make sure the url string view contains correct data, notably that it's a valid hostname and null-terminated string. In this case I'd prefer to use a string representation that guarantees null-termination:
Summary
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
using boost::asio::ip::tcp;
namespace ssl = boost::asio::ssl;
using SSLSocket = ssl::stream<tcp::socket>;
int main() {
boost::asio::io_context context;
ssl::context ssl_context(ssl::context::tls);
ssl_context.set_default_verify_paths();
SSLSocket socket(context, ssl_context);
tcp::resolver r(context);
std::string hostname = "www.example.com";
auto endpoints = r.resolve(hostname, "443");
boost::asio::connect(socket.next_layer(), endpoints);
socket.set_verify_mode(ssl::verify_peer);
socket.set_verify_callback(ssl::host_name_verification(hostname));
// Set SNI Hostname (many hosts need this to handshake successfully)
if(! SSL_set_tlsext_host_name(socket.native_handle(), hostname.c_str()))
{
throw boost::system::system_error(
::ERR_get_error(), boost::asio::error::get_ssl_category());
}
socket.handshake(SSLSocket::client);
}
In my case I manged to make it work with this:
ssl_context.load_verify_file("/etc/ssl/certs/ca-bundle.crt");
so pointing to the ca-bundle file instead of the cert file.

Use TLS client authentication with casablanca / cpprest in Linux

I wanted to switch over to cpprest for REST requirements, however, in my scenario, TLS client authentication needs to be supported.
I went through the capabilities of the library, but I am not sure whether there is built in support for client authentication.
There are some hacks for trying to get this capability, but it seems only on Windows for the moment, which won't work for me, since my server is on Linux.
This page here, https://github.com/Microsoft/cpprestsdk/issues/810, describes one such get-around for Windows, and a hint of how it can work in Linux builds, but I am not sure how to go about it, since I have little experience in the area.
Work around for TLS client authentication on Windows -
#include <Wincrypt.h>
std::vector<uint8_t> pkcs12_data; // "... your client certificate PKCS#12 with private key goes here ...";
utility::string_t password = "pkcs12_password";
web::http::client::http_client_config cfg;
cfg.set_nativehandle_options([=] (web::http::client::native_handle h) {
CRYPT_DATA_BLOB data;
data.cbData = pkcs12_data.size();
data.pbData = reinterpret_cast<BYTE *>(pkcs12_data.data());
HCERTSTORE hCertStore = PFXImportCertStore(&data, password.c_str(), 0);
PCCERT_CONTEXT hContext = CertFindCertificateInStore(
hCertStore, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY,
NULL, NULL);
WinHttpSetOption(h, WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
(LPVOID)hContext, sizeof(CERT_CONTEXT));
});
web::http::client::http_client http_client(PS("https://secure.com"), cfg);

WinHttpSetOption() failed set TLSv1.2 with error code ERROR_INTERNET_INCORRECT_HANDLE_TYPE

I am trying to set TLSv1.1 or v1.2 from C++ (Win) code using cpprest API calls as mentioned. But WinHttpSetOption() is failing with error ERROR_INTERNET_INCORRECT_HANDLE_TYPE (12018).
OS:Windows(7/8)
Tried to set TLSv1.1 and TLS1.2 from registry setting did not work.
Tried to get OpenSLL but opensll1.0.1(which supports TLS1.1 and more) is not available for windows.
Tried to get other than native handle did not find API
auto func = [&](web::http::client::native_handle handle){
BOOL win32Result{ FALSE };
DWORD secure_protocols{ WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1
| WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2 };
win32Result = ::WinHttpSetOption(handle,
WINHTTP_OPTION_SECURE_PROTOCOLS,
&secure_protocols,
sizeof(secure_protocols));
if (FALSE == win32Result) {
std::cout << "Can not set TLS 1.1 or TLS 1.2." << std::endl;
auto err = GetLastError();
CString cstr;
cstr.Format(_T("err = %d"),err);
AfxMessageBox(cstr);
}
};
config.set_validate_certificates(false);
config.set_nativehandle_options(func);
Please help me to set TLSv1.1 or v1.2 using C++ REST API. Or
how to make WinHttpSetOption() successful.
Using WinHttpOpen we can get "session handle" which can be passed to WinHttpSetOption().
This resolve the error "ERROR_INTERNET_INCORRECT_HANDLE_TYPE ".
HINTERNET hSession = WinHttpOpen(L"<Application name>",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0);
Though i am setting the TLS version to 1.2/1.1. Still my "http_request" is using TLSv1.0 which is default in Windows 7/8.1.(This is can confirm using wireshark)
Can any one let me know why "http_request" still using TLS1.0.
Please try installing this update:
https://support.microsoft.com/en-gb/topic/update-to-enable-tls-1-1-and-tls-1-2-as-default-secure-protocols-in-winhttp-in-windows-c4bd73d2-31d7-761e-0178-11268bb10392
"This update provides support for Transport Layer Security (TLS) 1.1 and TLS 1.2 in Windows Server 2012, Windows 7 Service Pack 1 (SP1), and Windows Server 2008 R2 SP1."

gsoap Reading CA file, Windows

I am trying to consume a webservice that I created and deployed in tomcat, enabling ssl in this latter.
For creating the client code, I use gsoap. I generated nedded files, and below my code in C++ :
soap_ssl_init();
soap_init(&soap);
if (soap_ssl_client_context(&soap,
SOAP_SSL_DEFAULT, /* use SOAP_SSL_DEFAULT in production code */
NULL, /* keyfile: required only when client must authenticate to server (see SSL docs on how to obtain this file) */
NULL, /* password to read the keyfile */
"cacert.pem", /* optional cacert file to store trusted certificates */
NULL, /* optional capath to directory with trusted certificates */
NULL /* if randfile!=NULL: use a file with random data to seed randomness */
))
{
soap_print_fault(&soap, stderr);
exit(1);
}
if(soap_call___ns2__Add(&soap,"https://localhost:8443/TestWebService/services/AddService.AddServiceHttpsSoap11Endpoint", NULL,&add, &resp)!= 0)
soap_print_fault(&soap, stderr);
When I execute the program, I got this meesage :
Error 30 fault: SOAP-ENV:Server [no subcode] "SSL error" Detail: Can't
read CA file and directory
As I read, I thinh that I have to generate some files (.pem, certs...).
It's the first time that I use goap and ssl, How could I solve this issue?
I use Windows (coz all examples that I found are for linux)
The problem was not related to my C++ Client code. Infact, I had to add the server certification file to the Bin\security\cacerts file located into my jre.
I had to try to implement a Java client to understand the problem.

Loading CA certificate from memory

I am trying to load CA certificate from memory instead of file. But I keep getting handshake error while connecting. The file loading works perfectly, memory loading fails. What am I missing?
std::ifstream file("message_server_ca.crt");
std::vector<char> fileContents((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
boost::asio::const_buffer buffer(&fileContents.at(0),fileContents.size());
bool useFile = false; // switch between file and memory loading.
boost::asio::ssl::context ctx(io_service, boost::asio::ssl::context::sslv23);
ctx.set_verify_mode(boost::asio::ssl::context::verify_peer);
if(useFile)
{
// This works perfectly!
ctx.load_verify_file("message_server_ca.crt");
}
else
{
// This fails the handshake (asio.ssl:336134278)
ctx.use_certificate(buffer,boost::asio::ssl::context_base::pem);
}
client c(io_service, ctx, iterator);
io_service.run();
It appears that you want add_certificate_authority():
This function is used to add one trusted certification authority from
a memory buffer.
use_certificate() and use_certificate_file() are for the server or client certificate presented in the handshake, i.e. not the CA used to test those certificates.
These functions (load_verify_file() and add_certificate_authority()) are not consistently named. I guess it is because the memory buffer versions were added relatively recently.