diff options
Diffstat (limited to '')
-rw-r--r-- | libssl.cc | 1130 |
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 */ |