Fixed: Allow to use custom CA Certificates under all platforms if libcurl compiled with OpenSSL for WebIG and login

--HG--
branch : develop
This commit is contained in:
kervala 2018-08-05 14:31:53 +02:00
parent 1000e29a96
commit f04fd9809c
4 changed files with 224 additions and 155 deletions

View file

@ -17,19 +17,24 @@
#ifndef CL_CURL_CERTIFICATES_HTML_H #ifndef CL_CURL_CERTIFICATES_HTML_H
#define CL_CURL_CERTIFICATES_HTML_H #define CL_CURL_CERTIFICATES_HTML_H
#include <curl/curl.h>
#include "nel/misc/types_nl.h" #include "nel/misc/types_nl.h"
#include <curl/curl.h>
namespace NLGUI namespace NLGUI
{ {
#if defined(NL_OS_WINDOWS) class CCurlCertificates
class CCurlCertificates { {
public: public:
// check if compiled with OpenSSL backend
static void init(CURL *curl);
// allow to use custom PEM certificates
static void addCertificateFile(const std::string &cert);
// cURL SSL certificate loading // cURL SSL certificate loading
static CURLcode sslCtxFunction(CURL *curl, void *sslctx, void *parm); static CURLcode sslCtxFunction(CURL *curl, void *sslctx, void *parm);
}; };
#endif // NL_OS_WINDOWS
} // namespace } // namespace
#endif #endif

View file

@ -21,11 +21,7 @@
#include <openssl/x509.h> #include <openssl/x509.h>
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/err.h>
#if defined(NL_OS_WINDOWS)
#pragma comment(lib, "crypt32.lib")
#pragma comment(lib, "cryptui.lib")
#endif
using namespace std; using namespace std;
using namespace NLMISC; using namespace NLMISC;
@ -36,36 +32,68 @@ using namespace NLMISC;
namespace NLGUI namespace NLGUI
{ {
#if defined(NL_OS_WINDOWS)
static std::vector<X509 *> x509CertList;
// //
// x509CertList lifetime manager // x509CertList lifetime manager
// //
class SX509Certificates { class SX509Certificates
{
public: public:
SX509Certificates() std::vector<X509 *> CertList;
std::vector<std::string> FilesList;
bool isUsingOpenSSLBackend;
bool isInitialized;
SX509Certificates():isUsingOpenSSLBackend(false), isInitialized(false)
{ {
curl_version_info_data *data;
data = curl_version_info(CURLVERSION_NOW);
if (!(data && data->features & CURL_VERSION_SSPI))
{
addCertificatesFrom("CA");
addCertificatesFrom("AuthRoot");
addCertificatesFrom("ROOT");
}
} }
~SX509Certificates() ~SX509Certificates()
{ {
for (uint i = 0; i < x509CertList.size(); ++i) for (uint i = 0; i < CertList.size(); ++i)
{ {
X509_free(x509CertList[i]); X509_free(CertList[i]);
} }
x509CertList.clear(); CertList.clear();
} }
void init(CURL *curl)
{
if (isInitialized) return;
// get information on CURL
curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
// get more information on CURL session
curl_tlssessioninfo *sessionInfo;
CURLcode res = curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &sessionInfo);
// only use OpenSSL callback if not using Windows SSPI and using OpenSSL backend
if (!res && sessionInfo && sessionInfo->backend == CURLSSLBACKEND_OPENSSL && !(data && data->features & CURL_VERSION_SSPI))
{
#ifdef NL_OS_WINDOWS
// load native Windows CA Certs
addCertificatesFrom("CA");
addCertificatesFrom("AuthRoot");
addCertificatesFrom("ROOT");
// we manually loaded native CA Certs, don't need to use custom certificates
isUsingOpenSSLBackend = false;
#else
isUsingOpenSSLBackend = true;
#endif
}
else
{
// if CURL is using SSPI under Windows or SecureChannel under OS X, we'll use native system CA Certs
isUsingOpenSSLBackend = false;
}
isInitialized = true;
}
#ifdef NL_OS_WINDOWS
void addCertificatesFrom(LPCSTR root) void addCertificatesFrom(LPCSTR root)
{ {
HCERTSTORE hStore; HCERTSTORE hStore;
@ -80,7 +108,7 @@ namespace NLGUI
x509 = d2i_X509(NULL, (const unsigned char **)&pContext->pbCertEncoded, pContext->cbCertEncoded); x509 = d2i_X509(NULL, (const unsigned char **)&pContext->pbCertEncoded, pContext->cbCertEncoded);
if (x509) if (x509)
{ {
x509CertList.push_back(x509); CertList.push_back(x509);
} }
} }
CertFreeCertificateContext(pContext); CertFreeCertificateContext(pContext);
@ -88,26 +116,157 @@ namespace NLGUI
} }
// this is called before debug context is set and log ends up in log.log // this is called before debug context is set and log ends up in log.log
//nlinfo("Loaded %d certificates from '%s' certificate store", List.size(), root); nlinfo("Loaded %d certificates from '%s' certificate store", (int)CertList.size(), root);
}
#endif
void addCertificatesFromFile(const std::string &cert)
{
if (!isInitialized)
{
nlwarning("You MUST call NLGUI::CCurlCertificates::init before adding new certificates");
return;
}
if (!isUsingOpenSSLBackend) return;
// this file was already loaded
if (std::find(FilesList.begin(), FilesList.end(), cert) != FilesList.end()) return;
FilesList.push_back(cert);
// look for certificate in search paths
string path = CPath::lookup(cert);
nlinfo("Cert path '%s'", path.c_str());
if (path.empty())
{
nlwarning("Unable to find %s", cert.c_str());
return;
}
CIFile file;
// open certificate
if (!file.open(path))
{
nlwarning("Unable to open %s", path.c_str());
return;
}
// load certificate content into memory
std::vector<uint8> buffer(file.getFileSize());
file.serialBuffer(&buffer[0], file.getFileSize());
// get a BIO
BIO *bio = BIO_new_mem_buf(&buffer[0], file.getFileSize());
if (bio)
{
// use it to read the PEM formatted certificate from memory into an X509
// structure that SSL can use
STACK_OF(X509_INFO) *info = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
if (info)
{
// iterate over all entries from the PEM file, add them to the x509_store one by one
for (sint i = 0; i < sk_X509_INFO_num(info); ++i)
{
X509_INFO *itmp = sk_X509_INFO_value(info, i);
if (itmp && itmp->x509)
{
CertList.push_back(X509_dup(itmp->x509));
}
}
// cleanup
sk_X509_INFO_pop_free(info, X509_INFO_free);
}
else
{
nlwarning("Unable to read PEM info");
}
// decrease reference counts
BIO_free(bio);
}
else
{
nlwarning("Unable to allocate BIO buffer for certificates");
}
} }
}; };
/// this will be initialized on startup and cleared on exit /// this will be initialized on startup and cleared on exit
static SX509Certificates x509CertListManager; static SX509Certificates x509CertListManager;
// ***************************************************************************
// static
void CCurlCertificates::init(CURL *curl)
{
x509CertListManager.init(curl);
}
// ***************************************************************************
// static
void CCurlCertificates::addCertificateFile(const std::string &cert)
{
x509CertListManager.addCertificatesFromFile(cert);
}
// *************************************************************************** // ***************************************************************************
// static // static
CURLcode CCurlCertificates::sslCtxFunction(CURL *curl, void *sslctx, void *parm) CURLcode CCurlCertificates::sslCtxFunction(CURL *curl, void *sslctx, void *parm)
{ {
if (x509CertList.size() > 0) CURLcode res = CURLE_OK;
if (x509CertListManager.CertList.size() > 0)
{ {
SSL_CTX *ctx = (SSL_CTX*)sslctx; SSL_CTX *ctx = (SSL_CTX*)sslctx;
X509_STORE *x509store = SSL_CTX_get_cert_store(ctx); X509_STORE *x509store = SSL_CTX_get_cert_store(ctx);
if (x509store) if (x509store)
{ {
for (uint i = 0; i < x509CertList.size(); ++i) char errorBuffer[1024];
for (uint i = 0, ilen = x509CertListManager.CertList.size(); i < ilen; ++i)
{ {
X509_STORE_add_cert(x509store, x509CertList[i]); X509_NAME *subject = X509_get_subject_name(x509CertListManager.CertList[i]);
std::string name;
unsigned char *tmp = NULL;
// construct a multiline string with name
for (int j = 0, jlen = X509_NAME_entry_count(subject); j < jlen; ++j)
{
X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, j);
ASN1_STRING *d = X509_NAME_ENTRY_get_data(e);
if (ASN1_STRING_to_UTF8(&tmp, d) > 0)
{
name += NLMISC::toString("%s\n", tmp);
OPENSSL_free(tmp);
}
}
// add our certificate to this store
if (X509_STORE_add_cert(x509store, x509CertListManager.CertList[i]) == 0)
{
uint errCode = ERR_get_error();
// ignore already in hash table errors
if (ERR_GET_LIB(errCode) != ERR_LIB_X509 || ERR_GET_REASON(errCode) != X509_R_CERT_ALREADY_IN_HASH_TABLE)
{
ERR_error_string_n(errCode, errorBuffer, 1024);
nlwarning("Error adding certificate %s: %s", name.c_str(), errorBuffer);
res = CURLE_SSL_CACERT;
}
}
else
{
nldebug("Added certificate %s", name.c_str());
}
} }
} }
else else
@ -115,9 +274,13 @@ namespace NLGUI
nlwarning("SSL_CTX_get_cert_store returned NULL"); nlwarning("SSL_CTX_get_cert_store returned NULL");
} }
} }
return CURLE_OK; else
{
res = CURLE_SSL_CACERT;
}
return res;
} }
#endif // NL_OS_WINDOWS
}// namespace }// namespace

View file

@ -398,14 +398,21 @@ namespace NLGUI
// https:// // https://
if (toLower(download.url.substr(0, 8)) == "https://") if (toLower(download.url.substr(0, 8)) == "https://")
{ {
#if defined(NL_OS_WINDOWS) // check if compiled with OpenSSL backend
curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction); CCurlCertificates::init(curl);
#else
if (!options.curlCABundle.empty()) // specify custom CA certs
CCurlCertificates::addCertificateFile(options.curlCABundle);
// would allow to provide the CA in memory instead of using CURLOPT_CAINFO, but needs to include and link OpenSSL
if (curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction) != CURLE_OK)
{ {
curl_easy_setopt(curl, CURLOPT_CAINFO, options.curlCABundle.c_str()); nlwarning("Unable to support CURLOPT_SSL_CTX_FUNCTION, curl not compiled with OpenSSL ?");
} }
#endif
// set both CURLOPT_CAINFO and CURLOPT_CAPATH to NULL to be sure we won't use default values (these files can be missing and generate errors)
curl_easy_setopt(curl, CURLOPT_CAINFO, NULL);
curl_easy_setopt(curl, CURLOPT_CAPATH, NULL);
} }
download.data = new CCurlWWWData(curl, download.url); download.data = new CCurlWWWData(curl, download.url);

View file

@ -19,8 +19,7 @@
#include <curl/curl.h> #include <curl/curl.h>
#include <openssl/ssl.h> #include "nel/gui/curl_certificates.h"
#include <openssl/err.h>
using namespace NLMISC; using namespace NLMISC;
using namespace NLNET; using namespace NLNET;
@ -64,119 +63,7 @@ bool CCurlHttpClient::authenticate(const std::string &user, const std::string &p
return true; return true;
} }
const char *CAFilename = "ssl_ca_cert.pem"; // this is the certificate "Thawte Server CA" static const std::string CAFilename = "ssl_ca_cert.pem"; // this is the certificate "Thawte Server CA"
// ***************************************************************************
static CURLcode sslctx_function(CURL * /* curl */, void *sslctx, void * /* parm */)
{
// look for certificate in search paths
string path = CPath::lookup(CAFilename);
nlinfo("Cert path '%s'", path.c_str());
if (path.empty())
{
nlwarning("Unable to find %s", CAFilename);
return CURLE_SSL_CACERT;
}
CIFile file;
// open certificate
if (!file.open(path))
{
nlwarning("Unable to open %s", path.c_str());
return CURLE_SSL_CACERT;
}
CURLcode res = CURLE_OK;
// load certificate content into memory
std::vector<uint8> buffer(file.getFileSize());
file.serialBuffer(&buffer[0], file.getFileSize());
// get a BIO
BIO *bio = BIO_new_mem_buf(&buffer[0], file.getFileSize());
char errorBuffer[1024];
if (bio)
{
// get a pointer to the X509 certificate store (which may be empty!)
X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX *)sslctx);
// use it to read the PEM formatted certificate from memory into an X509
// structure that SSL can use
STACK_OF(X509_INFO) *info = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
if (info)
{
// iterate over all entries from the PEM file, add them to the x509_store one by one
for (sint i = 0; i < sk_X509_INFO_num(info); ++i)
{
X509_INFO *itmp = sk_X509_INFO_value(info, i);
if (itmp && itmp->x509)
{
X509_NAME *subject = X509_get_subject_name(itmp->x509);
std::string name;
unsigned char *tmp = NULL;
// construct a multiline string with name
for (int i = 0, ilen = X509_NAME_entry_count(subject); i < ilen; ++i)
{
X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, i);
ASN1_STRING *d = X509_NAME_ENTRY_get_data(e);
if (ASN1_STRING_to_UTF8(&tmp, d) > 0)
{
name += NLMISC::toString("%s\n", tmp);
OPENSSL_free(tmp);
}
}
// add our certificate to this store
if (X509_STORE_add_cert(store, itmp->x509) == 0)
{
uint errCode = ERR_get_error();
// ignore already in hash table errors
if (ERR_GET_LIB(errCode) != ERR_LIB_X509 || ERR_GET_REASON(errCode) != X509_R_CERT_ALREADY_IN_HASH_TABLE)
{
ERR_error_string_n(errCode, errorBuffer, 1024);
nlwarning("Error adding certificate %s: %s", name.c_str(), errorBuffer);
res = CURLE_SSL_CACERT;
}
}
else
{
nlinfo("Added certificate %s", name.c_str());
}
}
}
// cleanup
sk_X509_INFO_pop_free(info, X509_INFO_free);
}
else
{
nlwarning("Unable to read PEM info");
res = CURLE_SSL_CACERT;
}
// decrease reference counts
BIO_free(bio);
}
else
{
nlwarning("Unable to allocate BIO buffer for certificates");
res = CURLE_SSL_CACERT;
}
// all set to go
return res;
}
// *************************************************************************** // ***************************************************************************
bool CCurlHttpClient::verifyServer(bool verify) bool CCurlHttpClient::verifyServer(bool verify)
@ -184,8 +71,15 @@ bool CCurlHttpClient::verifyServer(bool verify)
curl_easy_setopt(_Curl, CURLOPT_SSL_VERIFYHOST, verify ? 2 : 0); curl_easy_setopt(_Curl, CURLOPT_SSL_VERIFYHOST, verify ? 2 : 0);
curl_easy_setopt(_Curl, CURLOPT_SSL_VERIFYPEER, verify ? 1 : 0); curl_easy_setopt(_Curl, CURLOPT_SSL_VERIFYPEER, verify ? 1 : 0);
curl_easy_setopt(_Curl, CURLOPT_SSLCERTTYPE, "PEM"); curl_easy_setopt(_Curl, CURLOPT_SSLCERTTYPE, "PEM");
// check if compiled with OpenSSL backend
NLGUI::CCurlCertificates::init(_Curl);
// specify custom CA certs
NLGUI::CCurlCertificates::addCertificateFile(CAFilename);
// would allow to provide the CA in memory instead of using CURLOPT_CAINFO, but needs to include and link OpenSSL // would allow to provide the CA in memory instead of using CURLOPT_CAINFO, but needs to include and link OpenSSL
if (curl_easy_setopt(_Curl, CURLOPT_SSL_CTX_FUNCTION, *sslctx_function) != CURLE_OK) if (curl_easy_setopt(_Curl, CURLOPT_SSL_CTX_FUNCTION, &NLGUI::CCurlCertificates::sslCtxFunction) != CURLE_OK)
{ {
nlwarning("Unable to support CURLOPT_SSL_CTX_FUNCTION, curl not compiled with OpenSSL ?"); nlwarning("Unable to support CURLOPT_SSL_CTX_FUNCTION, curl not compiled with OpenSSL ?");
} }