diff --git a/code/nel/include/nel/gui/curl_certificates.h b/code/nel/include/nel/gui/curl_certificates.h index 021b13360..dd4e923a0 100644 --- a/code/nel/include/nel/gui/curl_certificates.h +++ b/code/nel/include/nel/gui/curl_certificates.h @@ -17,19 +17,24 @@ #ifndef CL_CURL_CERTIFICATES_HTML_H #define CL_CURL_CERTIFICATES_HTML_H -#include - #include "nel/misc/types_nl.h" +#include + namespace NLGUI { -#if defined(NL_OS_WINDOWS) - class CCurlCertificates { + class CCurlCertificates + { 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 static CURLcode sslCtxFunction(CURL *curl, void *sslctx, void *parm); }; -#endif // NL_OS_WINDOWS } // namespace #endif diff --git a/code/nel/src/gui/curl_certificates.cpp b/code/nel/src/gui/curl_certificates.cpp index 6cf34c8b8..a279d35f6 100644 --- a/code/nel/src/gui/curl_certificates.cpp +++ b/code/nel/src/gui/curl_certificates.cpp @@ -21,11 +21,7 @@ #include #include - -#if defined(NL_OS_WINDOWS) -#pragma comment(lib, "crypt32.lib") -#pragma comment(lib, "cryptui.lib") -#endif +#include using namespace std; using namespace NLMISC; @@ -36,36 +32,68 @@ using namespace NLMISC; namespace NLGUI { -#if defined(NL_OS_WINDOWS) - static std::vector x509CertList; - // // x509CertList lifetime manager // - class SX509Certificates { + class SX509Certificates + { public: - SX509Certificates() + std::vector CertList; + std::vector 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() { - 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) { HCERTSTORE hStore; @@ -80,7 +108,7 @@ namespace NLGUI x509 = d2i_X509(NULL, (const unsigned char **)&pContext->pbCertEncoded, pContext->cbCertEncoded); if (x509) { - x509CertList.push_back(x509); + CertList.push_back(x509); } } CertFreeCertificateContext(pContext); @@ -88,26 +116,157 @@ namespace NLGUI } // 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 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 static SX509Certificates x509CertListManager; + // *************************************************************************** + // static + void CCurlCertificates::init(CURL *curl) + { + x509CertListManager.init(curl); + } + + // *************************************************************************** + // static + void CCurlCertificates::addCertificateFile(const std::string &cert) + { + x509CertListManager.addCertificatesFromFile(cert); + } + // *************************************************************************** // static 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; X509_STORE *x509store = SSL_CTX_get_cert_store(ctx); 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 @@ -115,9 +274,13 @@ namespace NLGUI nlwarning("SSL_CTX_get_cert_store returned NULL"); } } - return CURLE_OK; + else + { + res = CURLE_SSL_CACERT; + } + + return res; } -#endif // NL_OS_WINDOWS }// namespace diff --git a/code/nel/src/gui/group_html.cpp b/code/nel/src/gui/group_html.cpp index e0ed401ce..fe08b6931 100644 --- a/code/nel/src/gui/group_html.cpp +++ b/code/nel/src/gui/group_html.cpp @@ -398,14 +398,21 @@ namespace NLGUI // https:// if (toLower(download.url.substr(0, 8)) == "https://") { -#if defined(NL_OS_WINDOWS) - curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &CCurlCertificates::sslCtxFunction); -#else - if (!options.curlCABundle.empty()) + // check if compiled with OpenSSL backend + CCurlCertificates::init(curl); + + // 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); diff --git a/code/ryzom/client/src/http_client_curl.cpp b/code/ryzom/client/src/http_client_curl.cpp index 2fd5bad81..418fc61bd 100644 --- a/code/ryzom/client/src/http_client_curl.cpp +++ b/code/ryzom/client/src/http_client_curl.cpp @@ -19,8 +19,7 @@ #include -#include -#include +#include "nel/gui/curl_certificates.h" using namespace NLMISC; using namespace NLNET; @@ -64,119 +63,7 @@ bool CCurlHttpClient::authenticate(const std::string &user, const std::string &p return true; } -const char *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 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; -} +static const std::string CAFilename = "ssl_ca_cert.pem"; // this is the certificate "Thawte Server CA" // *************************************************************************** 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_VERIFYPEER, verify ? 1 : 0); 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 - 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 ?"); }