summaryrefslogtreecommitdiffstats
path: root/libssl.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--libssl.cc1130
1 files changed, 1130 insertions, 0 deletions
diff --git a/libssl.cc b/libssl.cc
new file mode 100644
index 0000000..ab7b77e
--- /dev/null
+++ b/libssl.cc
@@ -0,0 +1,1130 @@
+
+#include "config.h"
+#include "libssl.hh"
+
+#ifdef HAVE_LIBSSL
+
+#include <atomic>
+#include <fstream>
+#include <cstring>
+#include <mutex>
+#include <unordered_map>
+#include <pthread.h>
+
+#include <openssl/conf.h>
+#ifndef OPENSSL_NO_ENGINE
+#include <openssl/engine.h>
+#endif
+#include <openssl/err.h>
+#ifndef DISABLE_OCSP_STAPLING
+#include <openssl/ocsp.h>
+#endif /* DISABLE_OCSP_STAPLING */
+#include <openssl/pkcs12.h>
+#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
+#include <openssl/provider.h>
+#endif /* defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 */
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+#include <fcntl.h>
+
+#if OPENSSL_VERSION_MAJOR >= 3
+#include <openssl/param_build.h>
+#include <openssl/core.h>
+#include <openssl/core_names.h>
+#include <openssl/evp.h>
+#endif
+
+#ifdef HAVE_LIBSODIUM
+#include <sodium.h>
+#endif /* HAVE_LIBSODIUM */
+
+#undef CERT
+#include "misc.hh"
+
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL)
+/* OpenSSL < 1.1.0 needs support for threading/locking in the calling application. */
+
+#include "lock.hh"
+static std::vector<std::mutex> openssllocks;
+
+extern "C" {
+static void openssl_pthreads_locking_callback(int mode, int type, const char *file, int line)
+{
+ if (mode & CRYPTO_LOCK) {
+ openssllocks.at(type).lock();
+
+ } else {
+ openssllocks.at(type).unlock();
+ }
+}
+
+static unsigned long openssl_pthreads_id_callback()
+{
+ return (unsigned long)pthread_self();
+}
+}
+
+static void openssl_thread_setup()
+{
+ openssllocks = std::vector<std::mutex>(CRYPTO_num_locks());
+ CRYPTO_set_id_callback(&openssl_pthreads_id_callback);
+ CRYPTO_set_locking_callback(&openssl_pthreads_locking_callback);
+}
+
+static void openssl_thread_cleanup()
+{
+ CRYPTO_set_locking_callback(nullptr);
+ openssllocks.clear();
+}
+
+#endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2090100fL) */
+
+static std::atomic<uint64_t> s_users;
+
+#if OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
+static LockGuarded<std::unordered_map<std::string, std::unique_ptr<OSSL_PROVIDER, decltype(&OSSL_PROVIDER_unload)>>> s_providers;
+#else
+#ifndef OPENSSL_NO_ENGINE
+static LockGuarded<std::unordered_map<std::string, std::unique_ptr<ENGINE, decltype(&ENGINE_free)>>> s_engines;
+#endif
+#endif
+
+static int s_ticketsKeyIndex{-1};
+static int s_countersIndex{-1};
+static int s_keyLogIndex{-1};
+
+void registerOpenSSLUser()
+{
+ if (s_users.fetch_add(1) == 0) {
+#ifdef HAVE_OPENSSL_INIT_CRYPTO
+#ifndef DISABLE_OPENSSL_ERROR_STRINGS
+ uint64_t cryptoOpts = OPENSSL_INIT_LOAD_CONFIG;
+ const uint64_t sslOpts = 0;
+#else /* DISABLE_OPENSSL_ERROR_STRINGS */
+ uint64_t cryptoOpts = OPENSSL_INIT_LOAD_CONFIG|OPENSSL_INIT_NO_LOAD_CRYPTO_STRINGS;
+ const uint64_t sslOpts = OPENSSL_INIT_NO_LOAD_SSL_STRINGS;
+#endif /* DISABLE_OPENSSL_ERROR_STRINGS */
+ /* load the default configuration file (or one specified via OPENSSL_CONF),
+ which can then be used to load engines.
+ */
+#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
+ /* Since 661595ca0933fe631faeadd14a189acd5d4185e0 we can no longer rely on the ciphers and digests
+ required for TLS to be loaded by OPENSSL_init_ssl(), so let's give up and load everything */
+#else /* OPENSSL_VERSION_MAJOR >= 3 */
+ /* Do not load all ciphers and digests, we only need a few of them and these
+ will be loaded by OPENSSL_init_ssl(). */
+ cryptoOpts |= OPENSSL_INIT_NO_ADD_ALL_CIPHERS|OPENSSL_INIT_NO_ADD_ALL_DIGESTS;
+#endif /* OPENSSL_VERSION_MAJOR >= 3 */
+
+ OPENSSL_init_crypto(cryptoOpts, nullptr);
+ OPENSSL_init_ssl(sslOpts, nullptr);
+#endif /* HAVE_OPENSSL_INIT_CRYPTO */
+
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x2090100fL))
+ /* load error strings for both libcrypto and libssl */
+ SSL_load_error_strings();
+ /* load all ciphers and digests needed for TLS support */
+ OpenSSL_add_ssl_algorithms();
+ openssl_thread_setup();
+#endif
+ s_ticketsKeyIndex = SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+
+ if (s_ticketsKeyIndex == -1) {
+ throw std::runtime_error("Error getting an index for tickets key");
+ }
+
+ s_countersIndex = SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+
+ if (s_countersIndex == -1) {
+ throw std::runtime_error("Error getting an index for counters");
+ }
+
+ s_keyLogIndex = SSL_CTX_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
+
+ if (s_keyLogIndex == -1) {
+ throw std::runtime_error("Error getting an index for TLS key logging");
+ }
+ }
+}
+
+void unregisterOpenSSLUser()
+{
+ if (s_users.fetch_sub(1) == 1) {
+#if OPENSSL_VERSION_MAJOR < 3 || !defined(HAVE_TLS_PROVIDERS)
+#ifndef OPENSSL_NO_ENGINE
+ for (auto& [name, engine] : *s_engines.lock()) {
+ ENGINE_finish(engine.get());
+ engine.reset();
+ }
+ s_engines.lock()->clear();
+#endif
+#endif
+#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined LIBRESSL_VERSION_NUMBER && LIBRESSL_VERSION_NUMBER < 0x2090100fL))
+ ERR_free_strings();
+
+ EVP_cleanup();
+
+ CONF_modules_finish();
+ CONF_modules_free();
+ CONF_modules_unload(1);
+
+ CRYPTO_cleanup_all_ex_data();
+ openssl_thread_cleanup();
+#endif
+ }
+}
+
+#if defined(HAVE_LIBSSL) && OPENSSL_VERSION_MAJOR >= 3 && defined(HAVE_TLS_PROVIDERS)
+std::pair<bool, std::string> libssl_load_provider(const std::string& providerName)
+{
+ if (s_users.load() == 0) {
+ /* We need to make sure that OpenSSL has been properly initialized before loading an engine.
+ This messes up our accounting a bit, so some memory might not be properly released when
+ the program exits when engines are in use. */
+ registerOpenSSLUser();
+ }
+
+ auto providers = s_providers.lock();
+ if (providers->count(providerName) > 0) {
+ return { false, "TLS provider already loaded" };
+ }
+
+ auto provider = std::unique_ptr<OSSL_PROVIDER, decltype(&OSSL_PROVIDER_unload)>(OSSL_PROVIDER_load(nullptr, providerName.c_str()), OSSL_PROVIDER_unload);
+ if (provider == nullptr) {
+ return { false, "unable to load TLS provider '" + providerName + "'" };
+ }
+
+ providers->insert({providerName, std::move(provider)});
+ return { true, "" };
+}
+#endif /* HAVE_LIBSSL && OPENSSL_VERSION_MAJOR >= 3 && HAVE_TLS_PROVIDERS */
+
+#if defined(HAVE_LIBSSL) && !defined(HAVE_TLS_PROVIDERS)
+std::pair<bool, std::string> libssl_load_engine(const std::string& engineName, const std::optional<std::string>& defaultString)
+{
+#ifdef OPENSSL_NO_ENGINE
+ return { false, "OpenSSL has been built without engine support" };
+#else
+ if (s_users.load() == 0) {
+ /* We need to make sure that OpenSSL has been properly initialized before loading an engine.
+ This messes up our accounting a bit, so some memory might not be properly released when
+ the program exits when engines are in use. */
+ registerOpenSSLUser();
+ }
+
+ auto engines = s_engines.lock();
+ if (engines->count(engineName) > 0) {
+ return { false, "TLS engine already loaded" };
+ }
+
+ auto engine = std::unique_ptr<ENGINE, decltype(&ENGINE_free)>(ENGINE_by_id(engineName.c_str()), ENGINE_free);
+ if (engine == nullptr) {
+ return { false, "unable to load TLS engine '" + engineName + "'" };
+ }
+
+ if (!ENGINE_init(engine.get())) {
+ return { false, "Unable to init TLS engine '" + engineName + "'" };
+ }
+
+ if (defaultString) {
+ if (ENGINE_set_default_string(engine.get(), defaultString->c_str()) == 0) {
+ return { false, "error while setting the TLS engine default string" };
+ }
+ }
+
+ engines->insert({engineName, std::move(engine)});
+ return { true, "" };
+#endif
+}
+#endif /* HAVE_LIBSSL && !HAVE_TLS_PROVIDERS */
+
+void* libssl_get_ticket_key_callback_data(SSL* s)
+{
+ SSL_CTX* sslCtx = SSL_get_SSL_CTX(s);
+ if (sslCtx == nullptr) {
+ return nullptr;
+ }
+
+ return SSL_CTX_get_ex_data(sslCtx, s_ticketsKeyIndex);
+}
+
+void libssl_set_ticket_key_callback_data(SSL_CTX* ctx, void* data)
+{
+ SSL_CTX_set_ex_data(ctx, s_ticketsKeyIndex, data);
+}
+
+#if OPENSSL_VERSION_MAJOR >= 3
+int libssl_ticket_key_callback(SSL* s, OpenSSLTLSTicketKeysRing& keyring, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx, int enc)
+#else
+int libssl_ticket_key_callback(SSL* s, OpenSSLTLSTicketKeysRing& keyring, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx, int enc)
+#endif
+{
+ if (enc != 0) {
+ const auto key = keyring.getEncryptionKey();
+ if (key == nullptr) {
+ return -1;
+ }
+
+ return key->encrypt(keyName, iv, ectx, hctx);
+ }
+
+ bool activeEncryptionKey = false;
+
+ const auto key = keyring.getDecryptionKey(keyName, activeEncryptionKey);
+ if (key == nullptr) {
+ /* we don't know this key, just create a new ticket */
+ return 0;
+ }
+
+ if (!key->decrypt(iv, ectx, hctx)) {
+ return -1;
+ }
+
+ if (!activeEncryptionKey) {
+ /* this key is not active, please encrypt the ticket content with the currently active one */
+ return 2;
+ }
+
+ return 1;
+}
+
+static long libssl_server_name_callback(SSL* ssl, int* al, void* arg)
+{
+ (void) al;
+ (void) arg;
+
+ if (SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) {
+ return SSL_TLSEXT_ERR_OK;
+ }
+
+ return SSL_TLSEXT_ERR_NOACK;
+}
+
+static void libssl_info_callback(const SSL *ssl, int where, int ret)
+{
+ SSL_CTX* sslCtx = SSL_get_SSL_CTX(ssl);
+ if (sslCtx == nullptr) {
+ return;
+ }
+
+ TLSErrorCounters* counters = reinterpret_cast<TLSErrorCounters*>(SSL_CTX_get_ex_data(sslCtx, s_countersIndex));
+ if (counters == nullptr) {
+ return;
+ }
+
+ if (where & SSL_CB_ALERT) {
+ const long lastError = ERR_peek_last_error();
+ switch (ERR_GET_REASON(lastError)) {
+#ifdef SSL_R_DH_KEY_TOO_SMALL
+ case SSL_R_DH_KEY_TOO_SMALL:
+ ++counters->d_dhKeyTooSmall;
+ break;
+#endif /* SSL_R_DH_KEY_TOO_SMALL */
+ case SSL_R_NO_SHARED_CIPHER:
+ ++counters->d_noSharedCipher;
+ break;
+ case SSL_R_UNKNOWN_PROTOCOL:
+ ++counters->d_unknownProtocol;
+ break;
+ case SSL_R_UNSUPPORTED_PROTOCOL:
+#ifdef SSL_R_VERSION_TOO_LOW
+ case SSL_R_VERSION_TOO_LOW:
+#endif /* SSL_R_VERSION_TOO_LOW */
+ ++counters->d_unsupportedProtocol;
+ break;
+ case SSL_R_INAPPROPRIATE_FALLBACK:
+ ++counters->d_inappropriateFallBack;
+ break;
+ case SSL_R_UNKNOWN_CIPHER_TYPE:
+ ++counters->d_unknownCipherType;
+ break;
+ case SSL_R_UNKNOWN_KEY_EXCHANGE_TYPE:
+ ++counters->d_unknownKeyExchangeType;
+ break;
+ case SSL_R_UNSUPPORTED_ELLIPTIC_CURVE:
+ ++counters->d_unsupportedEC;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void libssl_set_error_counters_callback(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, TLSErrorCounters* counters)
+{
+ SSL_CTX_set_ex_data(ctx.get(), s_countersIndex, counters);
+ SSL_CTX_set_info_callback(ctx.get(), libssl_info_callback);
+}
+
+#ifndef DISABLE_OCSP_STAPLING
+int libssl_ocsp_stapling_callback(SSL* ssl, const std::map<int, std::string>& ocspMap)
+{
+ auto pkey = SSL_get_privatekey(ssl);
+ if (pkey == nullptr) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ /* look for an OCSP response for the corresponding private key type (RSA, ECDSA..) */
+ const auto& data = ocspMap.find(EVP_PKEY_base_id(pkey));
+ if (data == ocspMap.end()) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ /* we need to allocate a copy because OpenSSL will free the pointer passed to SSL_set_tlsext_status_ocsp_resp() */
+ void* copy = OPENSSL_malloc(data->second.size());
+ if (copy == nullptr) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ memcpy(copy, data->second.data(), data->second.size());
+ SSL_set_tlsext_status_ocsp_resp(ssl, copy, data->second.size());
+ return SSL_TLSEXT_ERR_OK;
+}
+
+static bool libssl_validate_ocsp_response(const std::string& response)
+{
+ auto responsePtr = reinterpret_cast<const unsigned char *>(response.data());
+ std::unique_ptr<OCSP_RESPONSE, void(*)(OCSP_RESPONSE*)> resp(d2i_OCSP_RESPONSE(nullptr, &responsePtr, response.size()), OCSP_RESPONSE_free);
+ if (resp == nullptr) {
+ throw std::runtime_error("Unable to parse OCSP response");
+ }
+
+ int status = OCSP_response_status(resp.get());
+ if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+ throw std::runtime_error("OCSP response status is not successful: " + std::to_string(status));
+ }
+
+ std::unique_ptr<OCSP_BASICRESP, void(*)(OCSP_BASICRESP*)> basic(OCSP_response_get1_basic(resp.get()), OCSP_BASICRESP_free);
+ if (basic == nullptr) {
+ throw std::runtime_error("Error getting a basic OCSP response");
+ }
+
+ if (OCSP_resp_count(basic.get()) != 1) {
+ throw std::runtime_error("More than one single response in an OCSP basic response");
+ }
+
+ auto singleResponse = OCSP_resp_get0(basic.get(), 0);
+ if (singleResponse == nullptr) {
+ throw std::runtime_error("Error getting a single response from the basic OCSP response");
+ }
+
+ int reason;
+ ASN1_GENERALIZEDTIME* revTime = nullptr;
+ ASN1_GENERALIZEDTIME* thisUpdate = nullptr;
+ ASN1_GENERALIZEDTIME* nextUpdate = nullptr;
+
+ auto singleResponseStatus = OCSP_single_get0_status(singleResponse, &reason, &revTime, &thisUpdate, &nextUpdate);
+ if (singleResponseStatus != V_OCSP_CERTSTATUS_GOOD) {
+ throw std::runtime_error("Invalid status for OCSP single response (" + std::to_string(singleResponseStatus) + ")");
+ }
+ if (thisUpdate == nullptr || nextUpdate == nullptr) {
+ throw std::runtime_error("Error getting validity of OCSP single response");
+ }
+
+ auto validityResult = OCSP_check_validity(thisUpdate, nextUpdate, /* 5 minutes of leeway */ 5 * 60, -1);
+ if (validityResult == 0) {
+ throw std::runtime_error("OCSP single response is not yet, or no longer, valid");
+ }
+
+ return true;
+}
+
+static std::map<int, std::string> libssl_load_ocsp_responses(const std::vector<std::string>& ocspFiles, std::vector<int> keyTypes, std::vector<std::string>& warnings)
+{
+ std::map<int, std::string> ocspResponses;
+
+ if (ocspFiles.size() > keyTypes.size()) {
+ throw std::runtime_error("More OCSP files than certificates and keys loaded!");
+ }
+
+ size_t count = 0;
+ for (const auto& filename : ocspFiles) {
+ std::ifstream file(filename, std::ios::binary);
+ std::string content;
+ while (file) {
+ char buffer[4096];
+ file.read(buffer, sizeof(buffer));
+ if (file.bad()) {
+ file.close();
+ warnings.push_back("Unable to load OCSP response from " + filename);
+ continue;
+ }
+ content.append(buffer, file.gcount());
+ }
+ file.close();
+
+ try {
+ libssl_validate_ocsp_response(content);
+ ocspResponses.insert({keyTypes.at(count), std::move(content)});
+ }
+ catch (const std::exception& e) {
+ warnings.push_back("Error checking the validity of OCSP response from '" + filename + "': " + e.what());
+ continue;
+ }
+ ++count;
+ }
+
+ return ocspResponses;
+}
+
+#ifdef HAVE_OCSP_BASIC_SIGN
+bool libssl_generate_ocsp_response(const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin)
+{
+ const EVP_MD* rmd = EVP_sha256();
+
+ auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(certFile.c_str(), "r"), fclose);
+ if (!fp) {
+ throw std::runtime_error("Unable to open '" + certFile + "' when loading the certificate to generate an OCSP response");
+ }
+ auto cert = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
+
+ fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(caCert.c_str(), "r"), fclose);
+ if (!fp) {
+ throw std::runtime_error("Unable to open '" + caCert + "' when loading the issuer certificate to generate an OCSP response");
+ }
+ auto issuer = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
+ fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(caKey.c_str(), "r"), fclose);
+ if (!fp) {
+ throw std::runtime_error("Unable to open '" + caKey + "' when loading the issuer key to generate an OCSP response");
+ }
+ auto issuerKey = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(PEM_read_PrivateKey(fp.get(), nullptr, nullptr, nullptr), EVP_PKEY_free);
+ fp.reset();
+
+ auto bs = std::unique_ptr<OCSP_BASICRESP, void(*)(OCSP_BASICRESP*)>(OCSP_BASICRESP_new(), OCSP_BASICRESP_free);
+ auto thisupd = std::unique_ptr<ASN1_TIME, void(*)(ASN1_TIME*)>(X509_gmtime_adj(nullptr, 0), ASN1_TIME_free);
+ auto nextupd = std::unique_ptr<ASN1_TIME, void(*)(ASN1_TIME*)>(X509_time_adj_ex(nullptr, ndays, nmin * 60, nullptr), ASN1_TIME_free);
+
+ auto cid = std::unique_ptr<OCSP_CERTID, void(*)(OCSP_CERTID*)>(OCSP_cert_to_id(rmd, cert.get(), issuer.get()), OCSP_CERTID_free);
+ OCSP_basic_add1_status(bs.get(), cid.get(), V_OCSP_CERTSTATUS_GOOD, 0, nullptr, thisupd.get(), nextupd.get());
+
+ if (OCSP_basic_sign(bs.get(), issuer.get(), issuerKey.get(), rmd, nullptr, OCSP_NOCERTS) != 1) {
+ throw std::runtime_error("Error while signing the OCSP response");
+ }
+
+ auto resp = std::unique_ptr<OCSP_RESPONSE, void(*)(OCSP_RESPONSE*)>(OCSP_response_create(OCSP_RESPONSE_STATUS_SUCCESSFUL, bs.get()), OCSP_RESPONSE_free);
+ auto bio = std::unique_ptr<BIO, void(*)(BIO*)>(BIO_new_file(outFile.c_str(), "wb"), BIO_vfree);
+ if (!bio) {
+ throw std::runtime_error("Error opening file for writing the OCSP response");
+ }
+
+ // i2d_OCSP_RESPONSE_bio(bio.get(), resp.get()) is unusable from C++ because of an invalid cast
+ ASN1_i2d_bio((i2d_of_void*)i2d_OCSP_RESPONSE, bio.get(), (unsigned char*)resp.get());
+
+ return true;
+}
+#endif /* HAVE_OCSP_BASIC_SIGN */
+#endif /* DISABLE_OCSP_STAPLING */
+
+static int libssl_get_last_key_type(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx)
+{
+#ifdef HAVE_SSL_CTX_GET0_PRIVATEKEY
+ auto pkey = SSL_CTX_get0_privatekey(ctx.get());
+#else
+ auto temp = std::unique_ptr<SSL, void(*)(SSL*)>(SSL_new(ctx.get()), SSL_free);
+ if (!temp) {
+ return -1;
+ }
+ auto pkey = SSL_get_privatekey(temp.get());
+#endif
+
+ if (!pkey) {
+ return -1;
+ }
+
+ return EVP_PKEY_base_id(pkey);
+}
+
+LibsslTLSVersion libssl_tls_version_from_string(const std::string& str)
+{
+ if (str == "tls1.0") {
+ return LibsslTLSVersion::TLS10;
+ }
+ if (str == "tls1.1") {
+ return LibsslTLSVersion::TLS11;
+ }
+ if (str == "tls1.2") {
+ return LibsslTLSVersion::TLS12;
+ }
+ if (str == "tls1.3") {
+ return LibsslTLSVersion::TLS13;
+ }
+ throw std::runtime_error("Unknown TLS version '" + str);
+}
+
+const std::string& libssl_tls_version_to_string(LibsslTLSVersion version)
+{
+ static const std::map<LibsslTLSVersion, std::string> versions = {
+ { LibsslTLSVersion::TLS10, "tls1.0" },
+ { LibsslTLSVersion::TLS11, "tls1.1" },
+ { LibsslTLSVersion::TLS12, "tls1.2" },
+ { LibsslTLSVersion::TLS13, "tls1.3" }
+ };
+
+ const auto& it = versions.find(version);
+ if (it == versions.end()) {
+ throw std::runtime_error("Unknown TLS version (" + std::to_string((int)version) + ")");
+ }
+ return it->second;
+}
+
+bool libssl_set_min_tls_version(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, LibsslTLSVersion version)
+{
+#if defined(HAVE_SSL_CTX_SET_MIN_PROTO_VERSION) || defined(SSL_CTX_set_min_proto_version)
+ /* These functions have been introduced in 1.1.0, and the use of SSL_OP_NO_* is deprecated
+ Warning: SSL_CTX_set_min_proto_version is a function-like macro in OpenSSL */
+ int vers;
+ switch(version) {
+ case LibsslTLSVersion::TLS10:
+ vers = TLS1_VERSION;
+ break;
+ case LibsslTLSVersion::TLS11:
+ vers = TLS1_1_VERSION;
+ break;
+ case LibsslTLSVersion::TLS12:
+ vers = TLS1_2_VERSION;
+ break;
+ case LibsslTLSVersion::TLS13:
+#ifdef TLS1_3_VERSION
+ vers = TLS1_3_VERSION;
+#else
+ return false;
+#endif /* TLS1_3_VERSION */
+ break;
+ default:
+ return false;
+ }
+
+ if (SSL_CTX_set_min_proto_version(ctx.get(), vers) != 1) {
+ return false;
+ }
+ return true;
+#else
+ long vers = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+ switch(version) {
+ case LibsslTLSVersion::TLS10:
+ break;
+ case LibsslTLSVersion::TLS11:
+ vers |= SSL_OP_NO_TLSv1;
+ break;
+ case LibsslTLSVersion::TLS12:
+ vers |= SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1;
+ break;
+ case LibsslTLSVersion::TLS13:
+ vers |= SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2;
+ break;
+ default:
+ return false;
+ }
+
+ long options = SSL_CTX_get_options(ctx.get());
+ SSL_CTX_set_options(ctx.get(), options | vers);
+ return true;
+#endif
+}
+
+OpenSSLTLSTicketKeysRing::OpenSSLTLSTicketKeysRing(size_t capacity)
+{
+ d_ticketKeys.write_lock()->set_capacity(capacity);
+}
+
+OpenSSLTLSTicketKeysRing::~OpenSSLTLSTicketKeysRing()
+{
+}
+
+void OpenSSLTLSTicketKeysRing::addKey(std::shared_ptr<OpenSSLTLSTicketKey> newKey)
+{
+ d_ticketKeys.write_lock()->push_front(newKey);
+}
+
+std::shared_ptr<OpenSSLTLSTicketKey> OpenSSLTLSTicketKeysRing::getEncryptionKey()
+{
+ return d_ticketKeys.read_lock()->front();
+}
+
+std::shared_ptr<OpenSSLTLSTicketKey> OpenSSLTLSTicketKeysRing::getDecryptionKey(unsigned char name[TLS_TICKETS_KEY_NAME_SIZE], bool& activeKey)
+{
+ auto keys = d_ticketKeys.read_lock();
+ for (auto& key : *keys) {
+ if (key->nameMatches(name)) {
+ activeKey = (key == keys->front());
+ return key;
+ }
+ }
+ return nullptr;
+}
+
+size_t OpenSSLTLSTicketKeysRing::getKeysCount()
+{
+ return d_ticketKeys.read_lock()->size();
+}
+
+void OpenSSLTLSTicketKeysRing::loadTicketsKeys(const std::string& keyFile)
+{
+ bool keyLoaded = false;
+ std::ifstream file(keyFile);
+ try {
+ do {
+ auto newKey = std::make_shared<OpenSSLTLSTicketKey>(file);
+ addKey(newKey);
+ keyLoaded = true;
+ }
+ while (!file.fail());
+ }
+ catch (const std::exception& e) {
+ /* if we haven't been able to load at least one key, fail */
+ if (!keyLoaded) {
+ throw;
+ }
+ }
+
+ file.close();
+}
+
+void OpenSSLTLSTicketKeysRing::rotateTicketsKey(time_t now)
+{
+ auto newKey = std::make_shared<OpenSSLTLSTicketKey>();
+ addKey(newKey);
+}
+
+OpenSSLTLSTicketKey::OpenSSLTLSTicketKey()
+{
+ if (RAND_bytes(d_name, sizeof(d_name)) != 1) {
+ throw std::runtime_error("Error while generating the name of the OpenSSL TLS ticket key");
+ }
+
+ if (RAND_bytes(d_cipherKey, sizeof(d_cipherKey)) != 1) {
+ throw std::runtime_error("Error while generating the cipher key of the OpenSSL TLS ticket key");
+ }
+
+ if (RAND_bytes(d_hmacKey, sizeof(d_hmacKey)) != 1) {
+ throw std::runtime_error("Error while generating the HMAC key of the OpenSSL TLS ticket key");
+ }
+#ifdef HAVE_LIBSODIUM
+ sodium_mlock(d_name, sizeof(d_name));
+ sodium_mlock(d_cipherKey, sizeof(d_cipherKey));
+ sodium_mlock(d_hmacKey, sizeof(d_hmacKey));
+#endif /* HAVE_LIBSODIUM */
+}
+
+OpenSSLTLSTicketKey::OpenSSLTLSTicketKey(std::ifstream& file)
+{
+ file.read(reinterpret_cast<char*>(d_name), sizeof(d_name));
+ file.read(reinterpret_cast<char*>(d_cipherKey), sizeof(d_cipherKey));
+ file.read(reinterpret_cast<char*>(d_hmacKey), sizeof(d_hmacKey));
+
+ if (file.fail()) {
+ throw std::runtime_error("Unable to load a ticket key from the OpenSSL tickets key file");
+ }
+#ifdef HAVE_LIBSODIUM
+ sodium_mlock(d_name, sizeof(d_name));
+ sodium_mlock(d_cipherKey, sizeof(d_cipherKey));
+ sodium_mlock(d_hmacKey, sizeof(d_hmacKey));
+#endif /* HAVE_LIBSODIUM */
+}
+
+OpenSSLTLSTicketKey::~OpenSSLTLSTicketKey()
+{
+#ifdef HAVE_LIBSODIUM
+ sodium_munlock(d_name, sizeof(d_name));
+ sodium_munlock(d_cipherKey, sizeof(d_cipherKey));
+ sodium_munlock(d_hmacKey, sizeof(d_hmacKey));
+#else
+ OPENSSL_cleanse(d_name, sizeof(d_name));
+ OPENSSL_cleanse(d_cipherKey, sizeof(d_cipherKey));
+ OPENSSL_cleanse(d_hmacKey, sizeof(d_hmacKey));
+#endif /* HAVE_LIBSODIUM */
+}
+
+bool OpenSSLTLSTicketKey::nameMatches(const unsigned char name[TLS_TICKETS_KEY_NAME_SIZE]) const
+{
+ return (memcmp(d_name, name, sizeof(d_name)) == 0);
+}
+
+#if OPENSSL_VERSION_MAJOR >= 3
+static const std::string sha256KeyName{"sha256"};
+#endif
+
+#if OPENSSL_VERSION_MAJOR >= 3
+int OpenSSLTLSTicketKey::encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx) const
+#else
+int OpenSSLTLSTicketKey::encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx) const
+#endif
+{
+ memcpy(keyName, d_name, sizeof(d_name));
+
+ if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) != 1) {
+ return -1;
+ }
+
+ if (EVP_EncryptInit_ex(ectx, TLS_TICKETS_CIPHER_ALGO(), nullptr, d_cipherKey, iv) != 1) {
+ return -1;
+ }
+
+#if OPENSSL_VERSION_MAJOR >= 3
+ using ParamsBuilder = std::unique_ptr<OSSL_PARAM_BLD, decltype(&OSSL_PARAM_BLD_free)>;
+ using Params = std::unique_ptr<OSSL_PARAM, decltype(&OSSL_PARAM_free)>;
+
+ auto params_build = ParamsBuilder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
+ if (params_build == nullptr) {
+ return -1;
+ }
+
+ if (OSSL_PARAM_BLD_push_utf8_string(params_build.get(), OSSL_MAC_PARAM_DIGEST, sha256KeyName.c_str(), sha256KeyName.size()) == 0) {
+ return -1;
+ }
+
+ auto params = Params(OSSL_PARAM_BLD_to_param(params_build.get()), OSSL_PARAM_free);
+ if (params == nullptr) {
+ return -1;
+ }
+
+ if (EVP_MAC_CTX_set_params(hctx, params.get()) == 0) {
+ return -1;
+ }
+
+ if (EVP_MAC_init(hctx, d_hmacKey, sizeof(d_hmacKey), nullptr) == 0) {
+ return -1;
+ }
+#else
+ if (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) {
+ return -1;
+ }
+#endif
+
+ return 1;
+}
+
+#if OPENSSL_VERSION_MAJOR >= 3
+bool OpenSSLTLSTicketKey::decrypt(const unsigned char* iv, EVP_CIPHER_CTX* ectx, EVP_MAC_CTX* hctx) const
+#else
+bool OpenSSLTLSTicketKey::decrypt(const unsigned char* iv, EVP_CIPHER_CTX* ectx, HMAC_CTX* hctx) const
+#endif
+{
+#if OPENSSL_VERSION_MAJOR >= 3
+ using ParamsBuilder = std::unique_ptr<OSSL_PARAM_BLD, decltype(&OSSL_PARAM_BLD_free)>;
+ using Params = std::unique_ptr<OSSL_PARAM, decltype(&OSSL_PARAM_free)>;
+
+ auto params_build = ParamsBuilder(OSSL_PARAM_BLD_new(), OSSL_PARAM_BLD_free);
+ if (params_build == nullptr) {
+ return false;
+ }
+
+ if (OSSL_PARAM_BLD_push_utf8_string(params_build.get(), OSSL_MAC_PARAM_DIGEST, sha256KeyName.c_str(), sha256KeyName.size()) == 0) {
+ return false;
+ }
+
+ auto params = Params(OSSL_PARAM_BLD_to_param(params_build.get()), OSSL_PARAM_free);
+ if (params == nullptr) {
+ return false;
+ }
+
+ if (EVP_MAC_CTX_set_params(hctx, params.get()) == 0) {
+ return false;
+ }
+
+ if (EVP_MAC_init(hctx, d_hmacKey, sizeof(d_hmacKey), nullptr) == 0) {
+ return false;
+ }
+#else
+ if (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) {
+ return false;
+ }
+#endif
+
+ if (EVP_DecryptInit_ex(ectx, TLS_TICKETS_CIPHER_ALGO(), nullptr, d_cipherKey, iv) != 1) {
+ return false;
+ }
+
+ return true;
+}
+
+std::pair<std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>, std::vector<std::string>> libssl_init_server_context(const TLSConfig& config,
+ std::map<int, std::string>& ocspResponses)
+{
+ std::vector<std::string> warnings;
+ auto ctx = std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free);
+
+ if (!ctx) {
+ throw pdns::OpenSSL::error("Error creating an OpenSSL server context");
+ }
+
+ int sslOptions =
+ SSL_OP_NO_SSLv2 |
+ SSL_OP_NO_SSLv3 |
+ SSL_OP_NO_COMPRESSION |
+ SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION |
+ SSL_OP_SINGLE_DH_USE |
+ SSL_OP_SINGLE_ECDH_USE;
+
+ if (!config.d_enableTickets || config.d_numberOfTicketsKeys == 0) {
+ /* for TLS 1.3 this means no stateless tickets, but stateful tickets might still be issued,
+ which is something we don't want. */
+ sslOptions |= SSL_OP_NO_TICKET;
+ /* really disable all tickets */
+#ifdef HAVE_SSL_CTX_SET_NUM_TICKETS
+ SSL_CTX_set_num_tickets(ctx.get(), 0);
+#endif /* HAVE_SSL_CTX_SET_NUM_TICKETS */
+ }
+
+ if (config.d_ktls) {
+#ifdef SSL_OP_ENABLE_KTLS
+ sslOptions |= SSL_OP_ENABLE_KTLS;
+#endif /* SSL_OP_ENABLE_KTLS */
+ }
+
+ if (config.d_sessionTimeout > 0) {
+ SSL_CTX_set_timeout(ctx.get(), config.d_sessionTimeout);
+ }
+
+ if (config.d_preferServerCiphers) {
+ sslOptions |= SSL_OP_CIPHER_SERVER_PREFERENCE;
+#ifdef SSL_OP_PRIORITIZE_CHACHA
+ sslOptions |= SSL_OP_PRIORITIZE_CHACHA;
+#endif /* SSL_OP_PRIORITIZE_CHACHA */
+ }
+
+ if (!config.d_enableRenegotiation) {
+#ifdef SSL_OP_NO_RENEGOTIATION
+ sslOptions |= SSL_OP_NO_RENEGOTIATION;
+#elif defined(SSL_OP_NO_CLIENT_RENEGOTIATION)
+ sslOptions |= SSL_OP_NO_CLIENT_RENEGOTIATION;
+#endif
+ }
+
+#ifdef SSL_OP_IGNORE_UNEXPECTED_EOF
+ sslOptions |= SSL_OP_IGNORE_UNEXPECTED_EOF;
+#endif
+
+ SSL_CTX_set_options(ctx.get(), sslOptions);
+ if (!libssl_set_min_tls_version(ctx, config.d_minTLSVersion)) {
+ throw std::runtime_error("Failed to set the minimum version to '" + libssl_tls_version_to_string(config.d_minTLSVersion));
+ }
+
+#ifdef SSL_CTX_set_ecdh_auto
+ SSL_CTX_set_ecdh_auto(ctx.get(), 1);
+#endif
+
+ if (config.d_maxStoredSessions == 0) {
+ /* disable stored sessions entirely */
+ SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_OFF);
+ }
+ else {
+ /* use the internal built-in cache to store sessions */
+ SSL_CTX_set_session_cache_mode(ctx.get(), SSL_SESS_CACHE_SERVER);
+ SSL_CTX_sess_set_cache_size(ctx.get(), config.d_maxStoredSessions);
+ }
+
+ long mode = 0;
+#ifdef SSL_MODE_RELEASE_BUFFERS
+ if (config.d_releaseBuffers) {
+ mode |= SSL_MODE_RELEASE_BUFFERS;
+ }
+#endif
+
+ if (config.d_asyncMode) {
+#ifdef SSL_MODE_ASYNC
+ mode |= SSL_MODE_ASYNC;
+#else
+ warnings.push_back("Warning: TLS async mode requested but not supported");
+#endif
+ }
+
+ SSL_CTX_set_mode(ctx.get(), mode);
+
+ /* we need to set this callback to acknowledge the server name sent by the client,
+ otherwise it will not stored in the session and will not be accessible when the
+ session is resumed, causing SSL_get_servername to return nullptr */
+ SSL_CTX_set_tlsext_servername_callback(ctx.get(), &libssl_server_name_callback);
+
+ std::vector<int> keyTypes;
+ /* load certificate and private key */
+ for (const auto& pair : config.d_certKeyPairs) {
+ if (!pair.d_key) {
+#if defined(HAVE_SSL_CTX_USE_CERT_AND_KEY) && HAVE_SSL_CTX_USE_CERT_AND_KEY == 1
+ // If no separate key is given, treat it as a pkcs12 file
+ auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(pair.d_cert.c_str(), "r"), fclose);
+ if (!fp) {
+ throw std::runtime_error("Unable to open file " + pair.d_cert);
+ }
+ auto p12 = std::unique_ptr<PKCS12, void(*)(PKCS12*)>(d2i_PKCS12_fp(fp.get(), nullptr), PKCS12_free);
+ if (!p12) {
+ throw std::runtime_error("Unable to open PKCS12 file " + pair.d_cert);
+ }
+ EVP_PKEY *keyptr = nullptr;
+ X509 *certptr = nullptr;
+ STACK_OF(X509) *captr = nullptr;
+ if (!PKCS12_parse(p12.get(), (pair.d_password ? pair.d_password->c_str() : nullptr), &keyptr, &certptr, &captr)) {
+#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
+ bool failed = true;
+ /* we might be opening a PKCS12 file that uses RC2 CBC or 3DES CBC which, since OpenSSL 3.0.0, requires loading the legacy provider */
+ auto libCtx = OSSL_LIB_CTX_get0_global_default();
+ /* check whether the legacy provider is already loaded */
+ if (!OSSL_PROVIDER_available(libCtx, "legacy")) {
+ /* it's not */
+ auto provider = OSSL_PROVIDER_load(libCtx, "legacy");
+ if (provider != nullptr) {
+ if (PKCS12_parse(p12.get(), (pair.d_password ? pair.d_password->c_str() : nullptr), &keyptr, &certptr, &captr)) {
+ failed = false;
+ }
+ /* we do not want to keep that provider around after that */
+ OSSL_PROVIDER_unload(provider);
+ }
+ }
+ if (failed) {
+#endif /* defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 */
+ ERR_print_errors_fp(stderr);
+ throw std::runtime_error("An error occured while parsing PKCS12 file " + pair.d_cert);
+#if defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3
+ }
+#endif /* defined(OPENSSL_VERSION_MAJOR) && OPENSSL_VERSION_MAJOR >= 3 */
+ }
+ auto key = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(keyptr, EVP_PKEY_free);
+ auto cert = std::unique_ptr<X509, void(*)(X509*)>(certptr, X509_free);
+ auto ca = std::unique_ptr<STACK_OF(X509), void(*)(STACK_OF(X509)*)>(captr, [](STACK_OF(X509)* st){ sk_X509_free(st); });
+
+ if (SSL_CTX_use_cert_and_key(ctx.get(), cert.get(), key.get(), ca.get(), 1) != 1) {
+ ERR_print_errors_fp(stderr);
+ throw std::runtime_error("An error occurred while trying to load the TLS certificate and key from PKCS12 file " + pair.d_cert);
+ }
+#else
+ throw std::runtime_error("PKCS12 files are not supported by your openssl version");
+#endif /* HAVE_SSL_CTX_USE_CERT_AND_KEY */
+ } else {
+ if (SSL_CTX_use_certificate_chain_file(ctx.get(), pair.d_cert.c_str()) != 1) {
+ ERR_print_errors_fp(stderr);
+ throw std::runtime_error("An error occurred while trying to load the TLS server certificate file: " + pair.d_cert);
+ }
+ if (SSL_CTX_use_PrivateKey_file(ctx.get(), pair.d_key->c_str(), SSL_FILETYPE_PEM) != 1) {
+ ERR_print_errors_fp(stderr);
+ throw std::runtime_error("An error occurred while trying to load the TLS server private key file: " + pair.d_key.value());
+ }
+ }
+ if (SSL_CTX_check_private_key(ctx.get()) != 1) {
+ ERR_print_errors_fp(stderr);
+ throw std::runtime_error("The key from '" + pair.d_key.value() + "' does not match the certificate from '" + pair.d_cert + "'");
+ }
+ /* store the type of the new key, we might need it later to select the right OCSP stapling response */
+ auto keyType = libssl_get_last_key_type(ctx);
+ if (keyType < 0) {
+ throw std::runtime_error("The key from '" + pair.d_key.value() + "' has an unknown type");
+ }
+ keyTypes.push_back(keyType);
+ }
+
+#ifndef DISABLE_OCSP_STAPLING
+ if (!config.d_ocspFiles.empty()) {
+ try {
+ ocspResponses = libssl_load_ocsp_responses(config.d_ocspFiles, keyTypes, warnings);
+ }
+ catch(const std::exception& e) {
+ throw std::runtime_error("Unable to load OCSP responses: " + std::string(e.what()));
+ }
+ }
+#endif /* DISABLE_OCSP_STAPLING */
+
+ if (!config.d_ciphers.empty() && SSL_CTX_set_cipher_list(ctx.get(), config.d_ciphers.c_str()) != 1) {
+ throw std::runtime_error("The TLS ciphers could not be set: " + config.d_ciphers);
+ }
+
+#ifdef HAVE_SSL_CTX_SET_CIPHERSUITES
+ if (!config.d_ciphers13.empty() && SSL_CTX_set_ciphersuites(ctx.get(), config.d_ciphers13.c_str()) != 1) {
+ throw std::runtime_error("The TLS 1.3 ciphers could not be set: " + config.d_ciphers13);
+ }
+#endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */
+
+ return std::make_pair(std::move(ctx), std::move(warnings));
+}
+
+#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
+static void libssl_key_log_file_callback(const SSL* ssl, const char* line)
+{
+ SSL_CTX* sslCtx = SSL_get_SSL_CTX(ssl);
+ if (sslCtx == nullptr) {
+ return;
+ }
+
+ auto fp = reinterpret_cast<FILE*>(SSL_CTX_get_ex_data(sslCtx, s_keyLogIndex));
+ if (fp == nullptr) {
+ return;
+ }
+
+ fprintf(fp, "%s\n", line);
+ fflush(fp);
+}
+#endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
+
+std::unique_ptr<FILE, int(*)(FILE*)> libssl_set_key_log_file(std::unique_ptr<SSL_CTX, decltype(&SSL_CTX_free)>& ctx, const std::string& logFile)
+{
+#ifdef HAVE_SSL_CTX_SET_KEYLOG_CALLBACK
+ int fd = open(logFile.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0600);
+ if (fd == -1) {
+ unixDie("Error opening TLS log file '" + logFile + "'");
+ }
+ auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fdopen(fd, "a"), fclose);
+ if (!fp) {
+ int error = errno; // close might clobber errno
+ close(fd);
+ throw std::runtime_error("Error opening TLS log file '" + logFile + "': " + stringerror(error));
+ }
+
+ SSL_CTX_set_ex_data(ctx.get(), s_keyLogIndex, fp.get());
+ SSL_CTX_set_keylog_callback(ctx.get(), &libssl_key_log_file_callback);
+
+ return fp;
+#else
+ return std::unique_ptr<FILE, int(*)(FILE*)>(nullptr, fclose);
+#endif /* HAVE_SSL_CTX_SET_KEYLOG_CALLBACK */
+}
+
+/* called in a client context, if the client advertised more than one ALPN values and the server returned more than one as well, to select the one to use. */
+#ifndef DISABLE_NPN
+void libssl_set_npn_select_callback(SSL_CTX* ctx, int (*cb)(SSL* s, unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg), void* arg)
+{
+#ifdef HAVE_SSL_CTX_SET_NEXT_PROTO_SELECT_CB
+ SSL_CTX_set_next_proto_select_cb(ctx, cb, arg);
+#endif
+}
+#endif /* DISABLE_NPN */
+
+void libssl_set_alpn_select_callback(SSL_CTX* ctx, int (*cb)(SSL* s, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg), void* arg)
+{
+#ifdef HAVE_SSL_CTX_SET_ALPN_SELECT_CB
+ SSL_CTX_set_alpn_select_cb(ctx, cb, arg);
+#endif
+}
+
+bool libssl_set_alpn_protos(SSL_CTX* ctx, const std::vector<std::vector<uint8_t>>& protos)
+{
+#ifdef HAVE_SSL_CTX_SET_ALPN_PROTOS
+ std::vector<uint8_t> wire;
+ for (const auto& proto : protos) {
+ if (proto.size() > std::numeric_limits<uint8_t>::max()) {
+ throw std::runtime_error("Invalid ALPN value");
+ }
+ uint8_t length = proto.size();
+ wire.push_back(length);
+ wire.insert(wire.end(), proto.begin(), proto.end());
+ }
+ return SSL_CTX_set_alpn_protos(ctx, wire.data(), wire.size()) == 0;
+#else
+ return false;
+#endif
+}
+
+
+std::string libssl_get_error_string()
+{
+ BIO *mem = BIO_new(BIO_s_mem());
+ ERR_print_errors(mem);
+ char *p;
+ size_t len = BIO_get_mem_data(mem, &p);
+ std::string msg(p, len);
+ // replace newlines by /
+ if (msg.back() == '\n') {
+ msg.pop_back();
+ }
+ std::replace(msg.begin(), msg.end(), '\n', '/');
+ BIO_free(mem);
+ return msg;
+}
+#endif /* HAVE_LIBSSL */