Merge branch 'fixes' into main/rendor-staging
[ryzomcore.git] / nel / src / web / curl_certificates.cpp
blob52f04fe25d2fe6702ad93985d0b0d08df439f114
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2019 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2019-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "stdweb.h"
21 #include <nel/web/curl_certificates.h>
23 #include <nel/misc/common.h>
24 #include <nel/misc/debug.h>
25 #include <nel/misc/path.h>
26 #include <nel/misc/file.h>
28 // for compatibility with older versions
29 #ifndef CURL_AT_LEAST_VERSION
30 #define CURL_VERSION_BITS(x,y,z) ((x)<<16|(y)<<8|z)
31 #define CURL_AT_LEAST_VERSION(x,y,z) \
32 (LIBCURL_VERSION_NUM >= CURL_VERSION_BITS(x, y, z))
33 #endif
36 using namespace std;
37 using namespace NLMISC;
38 using namespace NLWEB;
40 #ifdef DEBUG_NEW
41 #define new DEBUG_NEW
42 #endif
44 namespace NLWEB
47 // x509CertList lifetime manager
49 class SX509Certificates
51 public:
52 struct CertEntry
54 X509 *cert;
55 std::string name;
56 std::string file;
58 bool operator == (const std::string &str)
60 return file == str;
64 std::vector<CertEntry> CertList;
66 bool isUsingOpenSSLBackend;
67 bool isInitialized;
69 SX509Certificates():isUsingOpenSSLBackend(false), isInitialized(false)
71 init();
74 ~SX509Certificates()
76 for (uint i = 0; i < CertList.size(); ++i)
78 X509_free(CertList[i].cert);
81 CertList.clear();
84 void init()
86 // init CURL
87 CURL *curl = curl_easy_init();
88 if (!curl) return;
90 // get information on CURL
91 curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
93 bool useOpenSSLBackend = false;
95 #if CURL_AT_LEAST_VERSION(7, 34, 0)
96 // get more information on CURL session
97 curl_tlssessioninfo *sessionInfo;
99 CURLINFO info;
101 #if CURL_AT_LEAST_VERSION(7, 48, 0)
102 info = CURLINFO_TLS_SSL_PTR;
103 #else
104 info = CURLINFO_TLS_SESSION;
105 #endif
107 CURLcode res = curl_easy_getinfo(curl, info, &sessionInfo);
109 // CURL using OpenSSL backend
110 if ((res == CURLE_OK) && sessionInfo && sessionInfo->backend == CURLSSLBACKEND_OPENSSL) useOpenSSLBackend = true;
111 #elif CURL_AT_LEAST_VERSION(7, 12, 3)
112 // get a list of OpenSSL engines
113 struct curl_slist *engines;
115 CURLcode res = curl_easy_getinfo(curl, CURLINFO_SSL_ENGINES, &engines);
117 // CURL using OpenSSL backend
118 // With OpenSSL compiled without any engine, engines will too return NULL
119 // Fortunately, if OpenSSL isn't compiled with engines means we compiled it ourself and CURL is a recent version
120 if ((res == CURLE_OK) && engines)
122 // free engines
123 curl_slist_free_all(engines);
125 useOpenSSLBackend = true;
127 #else
128 // TODO: implement an equivalent, but CURL 7.12 was released in 2004
129 #endif
131 // only use OpenSSL callback if not using Windows SSPI and using OpenSSL backend
132 if (useOpenSSLBackend && !(data && data->features & CURL_VERSION_SSPI))
134 isUsingOpenSSLBackend = true;
136 else
138 // if CURL is using SSPI or SChannel under Windows or DarwinSSL under OS X, we'll use native system CA Certs
139 isUsingOpenSSLBackend = false;
142 // clean up CURL
143 curl_easy_cleanup(curl);
145 isInitialized = true;
148 static std::string getCertName(X509 *cert)
150 // NULL certificate
151 if (!cert) return "";
153 X509_NAME *subject = X509_get_subject_name(cert);
155 std::string name;
156 unsigned char *tmp = NULL;
158 // construct a multiline string with name
159 for (int j = 0, jlen = X509_NAME_entry_count(subject); j < jlen; ++j)
161 X509_NAME_ENTRY *e = X509_NAME_get_entry(subject, j);
162 ASN1_STRING *d = X509_NAME_ENTRY_get_data(e);
164 if (ASN1_STRING_to_UTF8(&tmp, d) > 0)
166 name += NLMISC::toString("%s\n", tmp);
168 OPENSSL_free(tmp);
172 return name;
175 void addCertificatesFromFile(const std::string &cert)
177 if (!isInitialized)
179 nlwarning("CURL not initialized! Check if there are another errors");
180 return;
183 if (!isUsingOpenSSLBackend)
185 nlinfo("CURL not using OpenSSL backend! Unable to use custom certificates");
186 return;
188 else
190 nlinfo("CURL using OpenSSL backend!");
193 // this file was already loaded
194 if (std::find(CertList.begin(), CertList.end(), cert) != CertList.end()) return;
196 // look for certificate in search paths
197 string path = CPath::lookup(cert, false);
199 if (path.empty())
201 nlwarning("Unable to find %s", cert.c_str());
202 return;
205 nlinfo("CURL CA bundle '%s'", path.c_str());
207 CIFile file;
209 // open certificate
210 if (!file.open(path))
212 nlwarning("Unable to open %s", path.c_str());
213 return;
216 // load certificate content into memory
217 std::vector<uint8> buffer(file.getFileSize());
218 file.serialBuffer(&buffer[0], file.getFileSize());
220 // get a BIO
221 BIO *bio = BIO_new_mem_buf(&buffer[0], file.getFileSize());
223 if (bio)
225 // use it to read the PEM formatted certificate from memory into an X509
226 // structure that SSL can use
227 STACK_OF(X509_INFO) *info = PEM_X509_INFO_read_bio(bio, NULL, NULL, NULL);
229 if (info)
231 // iterate over all entries from the PEM file, add them to the x509_store one by one
232 for (sint i = 0; i < sk_X509_INFO_num(info); ++i)
234 X509_INFO *itmp = sk_X509_INFO_value(info, i);
236 if (itmp && itmp->x509)
238 CertEntry entry;
239 entry.cert = X509_dup(itmp->x509);
240 entry.file = cert;
241 entry.name = getCertName(entry.cert);
243 CertList.push_back(entry);
247 // cleanup
248 sk_X509_INFO_pop_free(info, X509_INFO_free);
250 else
252 nlwarning("Unable to read PEM info");
255 // decrease reference counts
256 BIO_free(bio);
258 else
260 nlwarning("Unable to allocate BIO buffer for certificates");
265 /// this will be initialized on startup and cleared on exit
266 static SX509Certificates x509CertListManager;
268 // cURL SSL certificate loading
269 static CURLcode sslCtxFunction(CURL *curl, void *sslctx, void *parm)
271 CURLcode res = CURLE_OK;
273 if (x509CertListManager.CertList.size() > 0)
275 SSL_CTX *ctx = (SSL_CTX*)sslctx;
276 X509_STORE *x509store = SSL_CTX_get_cert_store(ctx);
277 if (x509store)
279 char errorBuffer[1024];
281 for (uint i = 0, ilen = x509CertListManager.CertList.size(); i < ilen; ++i)
283 SX509Certificates::CertEntry entry = x509CertListManager.CertList[i];
285 // add our certificate to this store
286 if (X509_STORE_add_cert(x509store, entry.cert) == 0)
288 uint errCode = ERR_get_error();
290 // ignore already in hash table errors
291 if (ERR_GET_LIB(errCode) != ERR_LIB_X509 || ERR_GET_REASON(errCode) != X509_R_CERT_ALREADY_IN_HASH_TABLE)
293 ERR_error_string_n(errCode, errorBuffer, 1024);
294 nlwarning("Error adding certificate %s: %s", entry.name.c_str(), errorBuffer);
295 // There seems to be intermittent issues (on windows) where cert loading will fail for same 3 to 5 certs
296 // with an 'SSL_shutdown while in init' error. It does not seem to be fatal for connection.
297 //res = CURLE_SSL_CACERT;
300 else
302 // nldebug("Added certificate %s", entry.name.c_str());
306 else
308 nlwarning("SSL_CTX_get_cert_store returned NULL");
311 else
313 res = CURLE_SSL_CACERT;
316 return res;
319 // ***************************************************************************
320 // static
321 void CCurlCertificates::addCertificateFile(const std::string &cert)
323 x509CertListManager.addCertificatesFromFile(cert);
326 // ***************************************************************************
327 // static
328 void CCurlCertificates::useCertificates(CURL *curl)
330 // CURL must be valid, using OpenSSL backend and certificates must be loaded, else return
331 if (!curl || !x509CertListManager.isInitialized || !x509CertListManager.isUsingOpenSSLBackend || x509CertListManager.CertList.empty()) return;
333 curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, "PEM");
335 // would allow to provide the CA in memory instead of using CURLOPT_CAINFO, but needs to include and link OpenSSL
336 if (curl_easy_setopt(curl, CURLOPT_SSL_CTX_FUNCTION, &sslCtxFunction) != CURLE_OK)
338 nlwarning("Unable to support CURLOPT_SSL_CTX_FUNCTION, curl not compiled with OpenSSL ?");
341 // 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)
342 curl_easy_setopt(curl, CURLOPT_CAINFO, NULL);
343 curl_easy_setopt(curl, CURLOPT_CAPATH, NULL);
346 }// namespace
348 /* end of file */