From: Remi Gacogne Date: Thu, 26 Sep 2019 14:19:28 +0000 (+0200) Subject: dnsdist: Implement TLS Session Ticket Keys management for DoH X-Git-Tag: dnsdist-1.4.0-rc3~1^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0ef9ab1993fadaea494eeb3b12a10705607989f5;p=thirdparty%2Fpdns.git dnsdist: Implement TLS Session Ticket Keys management for DoH --- diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 5a3a3d0087..4daf3de3e1 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -1746,6 +1746,33 @@ void setupLuaConfig(bool client) frontend->d_customResponseHeaders.push_back(headerResponse); } } + + if (vars->count("ticketKeyFile")) { + frontend->d_ticketKeyFile = boost::get((*vars)["ticketKeyFile"]); + } + + if (vars->count("ticketsKeysRotationDelay")) { + frontend->d_ticketsKeyRotationDelay = boost::get((*vars)["ticketsKeysRotationDelay"]); + } + + if (vars->count("numberOfTicketsKeys")) { + frontend->d_numberOfTicketsKeys = boost::get((*vars)["numberOfTicketsKeys"]); + } + + if (vars->count("sessionTickets")) { + frontend->d_enableTickets = boost::get((*vars)["sessionTickets"]); + } + + if (vars->count("numberOfStoredSessions")) { + auto value = boost::get((*vars)["numberOfStoredSessions"]); + if (value < 0) { + errlog("Invalid value '%d' for addDOHLocal() parameter 'numberOfStoredSessions', should be >= 0, dismissing", value); + g_outputBuffer="Invalid value '" + std::to_string(value) + "' for addDOHLocal() parameter 'numberOfStoredSessions', should be >= 0, dimissing"; + return; + } + frontend->d_maxStoredSessions = value; + } + if (vars->count("ocspResponses")) { auto files = boost::get>>((*vars)["ocspResponses"]); for (const auto& file : files) { @@ -1851,6 +1878,18 @@ void setupLuaConfig(bool client) } }); + g_lua.registerFunction::*)()>("rotateTicketsKey", [](std::shared_ptr frontend) { + if (frontend != nullptr) { + frontend->rotateTicketsKey(time(nullptr)); + } + }); + + g_lua.registerFunction::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr frontend, const std::string& file) { + if (frontend != nullptr) { + frontend->loadTicketsKeys(file); + } + }); + g_lua.registerFunction::*)(const std::map>&)>("setResponsesMap", [](std::shared_ptr frontend, const std::map>& map) { if (frontend != nullptr) { std::vector> newMap; diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index 092c4db363..68ce3fcaeb 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -1150,10 +1150,20 @@ DOHFrontend This object represents an address and port dnsdist is listening on for DNS over HTTPS queries. + .. method:: DOHFrontend:loadTicketsKeys(ticketsKeysFile) + + Load new tickets keys from the selected file, replacing the existing ones. These keys should be rotated often and never written to persistent storage to preserve forward secrecy. The default is to generate a random key. dnsdist supports several tickets keys to be able to decrypt existing sessions after the rotation. + + :param str ticketsKeysFile: The path to a file from where TLS tickets keys should be loaded. + .. method:: DOHFrontend:reloadCertificates() Reload the current TLS certificate and key pairs. + .. method:: DOHFrontend:rotateTicketsKey() + + Replace the current TLS tickets key by a new random one. + .. method:: DOHFrontend:setResponsesMap(rules) Set a list of HTTP response rules allowing to intercept HTTP queries very early, before the DNS payload has been processed, and send custom responses including error pages, redirects and static content. diff --git a/pdns/dnsdistdist/doh.cc b/pdns/dnsdistdist/doh.cc index a4b270cd88..f5add032b2 100644 --- a/pdns/dnsdistdist/doh.cc +++ b/pdns/dnsdistdist/doh.cc @@ -897,7 +897,20 @@ static int ocsp_stapling_callback(SSL* ssl, void* arg) return libssl_ocsp_stapling_callback(ssl, *ocspMap); } -static std::unique_ptr getTLSContext(const std::vector>& pairs, const std::string& ciphers, const std::string& ciphers13, LibsslTLSVersion minTLSVersion, const std::vector& ocspFiles, std::map& ocspResponses) +static int ticket_key_callback(SSL *s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc) +{ + DOHFrontend* df = reinterpret_cast(libssl_get_ticket_key_callback_data(s)); + if (df == nullptr || !df->d_ticketKeys) { + return -1; + } + + df->handleTicketsKeyRotation(); + + return libssl_ticket_key_callback(s, *df->d_ticketKeys, keyName, iv, ectx, hctx, enc); +} + +static std::unique_ptr getTLSContext(DOHFrontend& df, + std::map& ocspResponses) { auto ctx = std::unique_ptr(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free); @@ -907,20 +920,40 @@ static std::unique_ptr getTLSContext(const std::vect SSL_OP_NO_COMPRESSION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_SINGLE_DH_USE | - SSL_OP_SINGLE_ECDH_USE; + SSL_OP_SINGLE_ECDH_USE | + SSL_OP_CIPHER_SERVER_PREFERENCE; + + if (!df.d_enableTickets) { + sslOptions |= SSL_OP_NO_TICKET; + } + else { + df.d_ticketKeys = std::unique_ptr(new OpenSSLTLSTicketKeysRing(df.d_numberOfTicketsKeys)); + SSL_CTX_set_tlsext_ticket_key_cb(ctx.get(), &ticket_key_callback); + libssl_set_ticket_key_callback_data(ctx.get(), &df); + } SSL_CTX_set_options(ctx.get(), sslOptions); - if (!libssl_set_min_tls_version(ctx, minTLSVersion)) { - throw std::runtime_error("Failed to set the minimum version to '" + libssl_tls_version_to_string(minTLSVersion) + "' for DoH listener"); + if (!libssl_set_min_tls_version(ctx, df.d_minTLSVersion)) { + throw std::runtime_error("Failed to set the minimum version to '" + libssl_tls_version_to_string(df.d_minTLSVersion) + "' for DoH listener"); } #ifdef SSL_CTX_set_ecdh_auto SSL_CTX_set_ecdh_auto(ctx.get(), 1); #endif + if (df.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(), df.d_maxStoredSessions); + } + std::vector keyTypes; /* load certificate and private key */ - for (const auto& pair : pairs) { + for (const auto& pair : df.d_certKeyPairs) { if (SSL_CTX_use_certificate_chain_file(ctx.get(), pair.first.c_str()) != 1) { ERR_print_errors_fp(stderr); throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, an error occurred while trying to load the DOH server certificate file: " + pair.first); @@ -941,9 +974,9 @@ static std::unique_ptr getTLSContext(const std::vect keyTypes.push_back(keyType); } - if (!ocspFiles.empty()) { + if (!df.d_ocspFiles.empty()) { try { - ocspResponses = libssl_load_ocsp_responses(ocspFiles, keyTypes); + ocspResponses = libssl_load_ocsp_responses(df.d_ocspFiles, keyTypes); SSL_CTX_set_tlsext_status_cb(ctx.get(), &ocsp_stapling_callback); SSL_CTX_set_tlsext_status_arg(ctx.get(), &ocspResponses); @@ -953,18 +986,30 @@ static std::unique_ptr getTLSContext(const std::vect } } - if (SSL_CTX_set_cipher_list(ctx.get(), ciphers.empty() == false ? ciphers.c_str() : DOH_DEFAULT_CIPHERS) != 1) { - throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH ciphers could not be set: " + ciphers); + if (SSL_CTX_set_cipher_list(ctx.get(), df.d_ciphers.empty() == false ? df.d_ciphers.c_str() : DOH_DEFAULT_CIPHERS) != 1) { + throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH ciphers could not be set: " + df.d_ciphers); } #ifdef HAVE_SSL_CTX_SET_CIPHERSUITES - if (!ciphers13.empty() && SSL_CTX_set_ciphersuites(ctx.get(), ciphers13.c_str()) != 1) { - throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH TLS 1.3 ciphers could not be set: " + ciphers13); + if (!df.d_ciphers13.empty() && SSL_CTX_set_ciphersuites(ctx.get(), df.d_ciphers13.c_str()) != 1) { + throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, DOH TLS 1.3 ciphers could not be set: " + df.d_ciphers13); } #endif /* HAVE_SSL_CTX_SET_CIPHERSUITES */ h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols); + try { + if (df.d_ticketKeyFile.empty()) { + df.handleTicketsKeyRotation(); + } + else { + df.loadTicketsKeys(df.d_ticketKeyFile); + } + } + catch (const std::exception& e) { + throw; + } + return ctx; } @@ -974,11 +1019,7 @@ static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool nativeCtx->ctx = &dsc.h2o_ctx; nativeCtx->hosts = dsc.h2o_config.hosts; if (setupTLS && !dsc.df->d_certKeyPairs.empty()) { - auto tlsCtx = getTLSContext(dsc.df->d_certKeyPairs, - dsc.df->d_ciphers, - dsc.df->d_ciphers13, - dsc.df->d_minTLSVersion, - dsc.df->d_ocspFiles, + auto tlsCtx = getTLSContext(*dsc.df, ctx.d_ocspResponses); nativeCtx->ssl_ctx = tlsCtx.release(); @@ -987,6 +1028,59 @@ static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool ctx.release(); } +void DOHFrontend::rotateTicketsKey(time_t now) +{ + if (!d_ticketKeys) { + return; + } + + d_ticketKeys->rotateTicketsKey(now); + + if (d_ticketsKeyRotationDelay > 0) { + d_ticketsKeyNextRotation = now + d_ticketsKeyRotationDelay; + } +} + +void DOHFrontend::loadTicketsKeys(const std::string& keyFile) +{ + if (!d_ticketKeys) { + return; + } + d_ticketKeys->loadTicketsKeys(keyFile); + + if (d_ticketsKeyRotationDelay > 0) { + d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay; + } +} + +void DOHFrontend::handleTicketsKeyRotation() +{ + if (d_ticketsKeyRotationDelay == 0) { + return; + } + + time_t now = time(nullptr); + if (now > d_ticketsKeyNextRotation) { + if (d_rotatingTicketsKey.test_and_set()) { + /* someone is already rotating */ + return; + } + try { + rotateTicketsKey(now); + + d_rotatingTicketsKey.clear(); + } + catch(const std::runtime_error& e) { + d_rotatingTicketsKey.clear(); + throw std::runtime_error(std::string("Error generating a new tickets key for TLS context:") + e.what()); + } + catch(...) { + d_rotatingTicketsKey.clear(); + throw; + } + } +} + void DOHFrontend::reloadCertificates() { auto newAcceptContext = std::unique_ptr(new DOHAcceptContext()); @@ -1003,11 +1097,7 @@ void DOHFrontend::setup() d_dsc = std::make_shared(d_idleTimeout); if (!d_certKeyPairs.empty()) { - auto tlsCtx = getTLSContext(d_certKeyPairs, - d_ciphers, - d_ciphers13, - d_minTLSVersion, - d_ocspFiles, + auto tlsCtx = getTLSContext(*this, d_dsc->accept_ctx->d_ocspResponses); auto accept_ctx = d_dsc->accept_ctx->get(); diff --git a/pdns/dnsdistdist/libssl.cc b/pdns/dnsdistdist/libssl.cc index e77e0dd223..f900365baf 100644 --- a/pdns/dnsdistdist/libssl.cc +++ b/pdns/dnsdistdist/libssl.cc @@ -15,6 +15,10 @@ #include #include +#ifdef HAVE_LIBSODIUM +#include +#endif /* HAVE_LIBSODIUM */ + #if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) /* OpenSSL < 1.1.0 needs support for threading/locking in the calling application. */ static pthread_mutex_t *openssllocks{nullptr}; @@ -58,24 +62,31 @@ static void openssl_thread_cleanup() OPENSSL_free(openssllocks); } -static std::atomic s_users; #endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) */ +static std::atomic s_users; +static int s_ticketsKeyIndex{-1}; + void registerOpenSSLUser() { -#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) if (s_users.fetch_add(1) == 0) { + 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"); + } +#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) SSL_load_error_strings(); OpenSSL_add_ssl_algorithms(); openssl_thread_setup(); - } #endif + } } void unregisterOpenSSLUser() { -#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) if (s_users.fetch_sub(1) == 1) { +#if (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) ERR_free_strings(); EVP_cleanup(); @@ -86,8 +97,54 @@ void unregisterOpenSSLUser() CRYPTO_cleanup_all_ex_data(); openssl_thread_cleanup(); - } #endif + } +} + +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); +} + +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) +{ + if (enc) { + 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) == false) { + return -1; + } + + if (!activeEncryptionKey) { + /* this key is not active, please encrypt the ticket content with the currently active one */ + return 2; + } + + return 1; } int libssl_ocsp_stapling_callback(SSL* ssl, const std::map& ocspMap) @@ -351,4 +408,159 @@ bool libssl_set_min_tls_version(std::unique_ptr& ctx #endif } +OpenSSLTLSTicketKeysRing::OpenSSLTLSTicketKeysRing(size_t capacity) +{ + pthread_rwlock_init(&d_lock, nullptr); + d_ticketKeys.set_capacity(capacity); +} + +OpenSSLTLSTicketKeysRing::~OpenSSLTLSTicketKeysRing() +{ + pthread_rwlock_destroy(&d_lock); +} + +void OpenSSLTLSTicketKeysRing::addKey(std::shared_ptr newKey) +{ + WriteLock wl(&d_lock); + d_ticketKeys.push_front(newKey); +} + +std::shared_ptr OpenSSLTLSTicketKeysRing::getEncryptionKey() +{ + ReadLock rl(&d_lock); + return d_ticketKeys.front(); +} + +std::shared_ptr OpenSSLTLSTicketKeysRing::getDecryptionKey(unsigned char name[TLS_TICKETS_KEY_NAME_SIZE], bool& activeKey) +{ + ReadLock rl(&d_lock); + for (auto& key : d_ticketKeys) { + if (key->nameMatches(name)) { + activeKey = (key == d_ticketKeys.front()); + return key; + } + } + return nullptr; +} + +size_t OpenSSLTLSTicketKeysRing::getKeysCount() +{ + ReadLock rl(&d_lock); + return d_ticketKeys.size(); +} + +void OpenSSLTLSTicketKeysRing::loadTicketsKeys(const std::string& keyFile) +{ + bool keyLoaded = false; + std::ifstream file(keyFile); + try { + do { + auto newKey = std::make_shared(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(); + 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(ifstream& file) +{ + file.read(reinterpret_cast(d_name), sizeof(d_name)); + file.read(reinterpret_cast(d_cipherKey), sizeof(d_cipherKey)); + file.read(reinterpret_cast(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); +} + +int OpenSSLTLSTicketKey::encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const +{ + 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 (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) { + return -1; + } + + return 1; +} + +bool OpenSSLTLSTicketKey::decrypt(const unsigned char* iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const +{ + if (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) { + return false; + } + + if (EVP_DecryptInit_ex(ectx, TLS_TICKETS_CIPHER_ALGO(), nullptr, d_cipherKey, iv) != 1) { + return false; + } + + return true; +} + #endif /* HAVE_LIBSSL */ diff --git a/pdns/dnsdistdist/tcpiohandler.cc b/pdns/dnsdistdist/tcpiohandler.cc index 835b3b395a..bb2edad533 100644 --- a/pdns/dnsdistdist/tcpiohandler.cc +++ b/pdns/dnsdistdist/tcpiohandler.cc @@ -1,7 +1,5 @@ -#include #include "config.h" -#include "circular_buffer.hh" #include "dolog.hh" #include "iputils.hh" #include "lock.hh" @@ -20,163 +18,6 @@ #include "libssl.hh" -/* From rfc5077 Section 4. Recommended Ticket Construction */ -#define TLS_TICKETS_KEY_NAME_SIZE (16) - -/* AES-256 */ -#define TLS_TICKETS_CIPHER_KEY_SIZE (32) -#define TLS_TICKETS_CIPHER_ALGO (EVP_aes_256_cbc) - -/* HMAC SHA-256 */ -#define TLS_TICKETS_MAC_KEY_SIZE (32) -#define TLS_TICKETS_MAC_ALGO (EVP_sha256) - -static int s_ticketsKeyIndex{-1}; - -class OpenSSLTLSTicketKey -{ -public: - 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(ifstream& file) - { - file.read(reinterpret_cast(d_name), sizeof(d_name)); - file.read(reinterpret_cast(d_cipherKey), sizeof(d_cipherKey)); - file.read(reinterpret_cast(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() - { -#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 nameMatches(const unsigned char name[TLS_TICKETS_KEY_NAME_SIZE]) const - { - return (memcmp(d_name, name, sizeof(d_name)) == 0); - } - - int encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const - { - 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 (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) { - return -1; - } - - return 1; - } - - bool decrypt(const unsigned char* iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const - { - if (HMAC_Init_ex(hctx, d_hmacKey, sizeof(d_hmacKey), TLS_TICKETS_MAC_ALGO(), nullptr) != 1) { - return false; - } - - if (EVP_DecryptInit_ex(ectx, TLS_TICKETS_CIPHER_ALGO(), nullptr, d_cipherKey, iv) != 1) { - return false; - } - - return true; - } - -private: - unsigned char d_name[TLS_TICKETS_KEY_NAME_SIZE]; - unsigned char d_cipherKey[TLS_TICKETS_CIPHER_KEY_SIZE]; - unsigned char d_hmacKey[TLS_TICKETS_MAC_KEY_SIZE]; -}; - -class OpenSSLTLSTicketKeysRing -{ -public: - OpenSSLTLSTicketKeysRing(size_t capacity) - { - pthread_rwlock_init(&d_lock, nullptr); - d_ticketKeys.set_capacity(capacity); - } - - ~OpenSSLTLSTicketKeysRing() - { - pthread_rwlock_destroy(&d_lock); - } - - void addKey(std::shared_ptr newKey) - { - WriteLock wl(&d_lock); - d_ticketKeys.push_back(newKey); - } - - std::shared_ptr getEncryptionKey() - { - ReadLock rl(&d_lock); - return d_ticketKeys.front(); - } - - std::shared_ptr getDecryptionKey(unsigned char name[TLS_TICKETS_KEY_NAME_SIZE], bool& activeKey) - { - ReadLock rl(&d_lock); - for (auto& key : d_ticketKeys) { - if (key->nameMatches(name)) { - activeKey = (key == d_ticketKeys.front()); - return key; - } - } - return nullptr; - } - - size_t getKeysCount() - { - ReadLock rl(&d_lock); - return d_ticketKeys.size(); - } - -private: - boost::circular_buffer > d_ticketKeys; - pthread_rwlock_t d_lock; -}; - class OpenSSLTLSConnection: public TLSConnection { public: @@ -389,15 +230,7 @@ public: sslOptions |= SSL_OP_NO_TICKET; } - if (s_users.fetch_add(1) == 0) { - registerOpenSSLUser(); - - 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"); - } - } + registerOpenSSLUser(); d_tlsCtx = std::unique_ptr(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free); if (!d_tlsCtx) { @@ -407,7 +240,8 @@ public: /* use our own ticket keys handler so we can rotate them */ SSL_CTX_set_tlsext_ticket_key_cb(d_tlsCtx.get(), &OpenSSLTLSIOCtx::ticketKeyCb); - SSL_CTX_set_ex_data(d_tlsCtx.get(), s_ticketsKeyIndex, this); + libssl_set_ticket_key_callback_data(d_tlsCtx.get(), this); + SSL_CTX_set_options(d_tlsCtx.get(), sslOptions); if (!libssl_set_min_tls_version(d_tlsCtx, fe.d_minTLSVersion)) { throw std::runtime_error("Failed to set the minimum version to '" + libssl_tls_version_to_string(fe.d_minTLSVersion) + "' for ths TLS context on " + fe.d_addr.toStringWithPort()); @@ -494,50 +328,17 @@ public: { d_tlsCtx.reset(); - if (s_users.fetch_sub(1) == 1) { - unregisterOpenSSLUser(); - } + unregisterOpenSSLUser(); } static int ticketKeyCb(SSL *s, unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc) { - SSL_CTX* sslCtx = SSL_get_SSL_CTX(s); - if (sslCtx == nullptr) { - return -1; - } - - OpenSSLTLSIOCtx* ctx = reinterpret_cast(SSL_CTX_get_ex_data(sslCtx, s_ticketsKeyIndex)); + OpenSSLTLSIOCtx* ctx = reinterpret_cast(libssl_get_ticket_key_callback_data(s)); if (ctx == nullptr) { return -1; } - if (enc) { - const auto key = ctx->d_ticketKeys.getEncryptionKey(); - if (key == nullptr) { - return -1; - } - - return key->encrypt(keyName, iv, ectx, hctx); - } - - bool activeEncryptionKey = false; - - const auto key = ctx->d_ticketKeys.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) == false) { - return -1; - } - - if (!activeEncryptionKey) { - /* this key is not active, please encrypt the ticket content with the currently active one */ - return 2; - } - - return 1; + return libssl_ticket_key_callback(s, ctx->d_ticketKeys, keyName, iv, ectx, hctx, enc); } static int ocspStaplingCb(SSL* ssl, void* arg) @@ -558,8 +359,7 @@ public: void rotateTicketsKey(time_t now) override { - auto newKey = std::make_shared(); - d_ticketKeys.addKey(newKey); + d_ticketKeys.rotateTicketsKey(now); if (d_ticketsKeyRotationDelay > 0) { d_ticketsKeyNextRotation = now + d_ticketsKeyRotationDelay; @@ -568,28 +368,11 @@ public: void loadTicketsKeys(const std::string& keyFile) override { - bool keyLoaded = false; - ifstream file(keyFile); - try { - do { - auto newKey = std::make_shared(file); - d_ticketKeys.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; - } - } + d_ticketKeys.loadTicketsKeys(keyFile); if (d_ticketsKeyRotationDelay > 0) { d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay; } - - file.close(); } size_t getTicketsKeysCount() override @@ -601,11 +384,8 @@ private: OpenSSLTLSTicketKeysRing d_ticketKeys; std::map d_ocspResponses; std::unique_ptr d_tlsCtx; - static std::atomic s_users; }; -std::atomic OpenSSLTLSIOCtx::s_users(0); - #endif /* HAVE_LIBSSL */ #ifdef HAVE_GNUTLS diff --git a/pdns/doh.hh b/pdns/doh.hh index 0b90c02c4d..41e6003293 100644 --- a/pdns/doh.hh +++ b/pdns/doh.hh @@ -48,11 +48,22 @@ struct DOHFrontend std::string d_ciphers13; std::string d_serverTokens{"h2o/dnsdist"}; LibsslTLSVersion d_minTLSVersion{LibsslTLSVersion::TLS10}; +#ifdef HAVE_DNS_OVER_HTTPS + std::unique_ptr d_ticketKeys{nullptr}; +#endif std::vector> d_customResponseHeaders; ComboAddress d_local; uint32_t d_idleTimeout{30}; // HTTP idle timeout in seconds std::vector d_urls; + std::string d_ticketKeyFile; + + std::atomic_flag d_rotatingTicketsKey; + time_t d_ticketsKeyRotationDelay{43200}; + time_t d_ticketsKeyNextRotation{0}; + size_t d_maxStoredSessions{20480}; + uint8_t d_numberOfTicketsKeys{5}; + bool d_enableTickets{true}; std::atomic d_httpconnects; // number of TCP/IP connections established std::atomic d_tls10queries; // valid DNS queries received via TLSv1.0 @@ -82,6 +93,7 @@ struct DOHFrontend HTTPVersionStats d_http1Stats; HTTPVersionStats d_http2Stats; + #ifndef HAVE_DNS_OVER_HTTPS void setup() { @@ -90,9 +102,27 @@ struct DOHFrontend void reloadCertificates() { } + + void rotateTicketsKey(time_t now) + { + } + + void loadTicketsKeys(const std::string& keyFile) + { + } + + void handleTicketsKeyRotation() + { + } + #else void setup(); void reloadCertificates(); + + void rotateTicketsKey(time_t now); + void loadTicketsKeys(const std::string& keyFile); + void handleTicketsKeyRotation(); + #endif /* HAVE_DNS_OVER_HTTPS */ }; diff --git a/pdns/libssl.hh b/pdns/libssl.hh index f0cdd16979..895e09bbf6 100644 --- a/pdns/libssl.hh +++ b/pdns/libssl.hh @@ -1,11 +1,14 @@ #pragma once +#include #include #include #include #include #include "config.h" +#include "circular_buffer.hh" +#include "lock.hh" enum class LibsslTLSVersion { TLS10, TLS11, TLS12, TLS13 }; @@ -15,6 +18,55 @@ enum class LibsslTLSVersion { TLS10, TLS11, TLS12, TLS13 }; void registerOpenSSLUser(); void unregisterOpenSSLUser(); +/* From rfc5077 Section 4. Recommended Ticket Construction */ +#define TLS_TICKETS_KEY_NAME_SIZE (16) + +/* AES-256 */ +#define TLS_TICKETS_CIPHER_KEY_SIZE (32) +#define TLS_TICKETS_CIPHER_ALGO (EVP_aes_256_cbc) + +/* HMAC SHA-256 */ +#define TLS_TICKETS_MAC_KEY_SIZE (32) +#define TLS_TICKETS_MAC_ALGO (EVP_sha256) + +class OpenSSLTLSTicketKey +{ +public: + OpenSSLTLSTicketKey(); + OpenSSLTLSTicketKey(std::ifstream& file); + ~OpenSSLTLSTicketKey(); + + bool nameMatches(const unsigned char name[TLS_TICKETS_KEY_NAME_SIZE]) const; + int encrypt(unsigned char keyName[TLS_TICKETS_KEY_NAME_SIZE], unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const; + bool decrypt(const unsigned char* iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx) const; + +private: + unsigned char d_name[TLS_TICKETS_KEY_NAME_SIZE]; + unsigned char d_cipherKey[TLS_TICKETS_CIPHER_KEY_SIZE]; + unsigned char d_hmacKey[TLS_TICKETS_MAC_KEY_SIZE]; +}; + +class OpenSSLTLSTicketKeysRing +{ +public: + OpenSSLTLSTicketKeysRing(size_t capacity); + ~OpenSSLTLSTicketKeysRing(); + void addKey(std::shared_ptr newKey); + std::shared_ptr getEncryptionKey(); + std::shared_ptr getDecryptionKey(unsigned char name[TLS_TICKETS_KEY_NAME_SIZE], bool& activeKey); + size_t getKeysCount(); + void loadTicketsKeys(const std::string& keyFile); + void rotateTicketsKey(time_t now); + +private: + boost::circular_buffer > d_ticketKeys; + pthread_rwlock_t d_lock; +}; + +void* libssl_get_ticket_key_callback_data(SSL* s); +void libssl_set_ticket_key_callback_data(SSL_CTX* ctx, void* data); +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); + int libssl_ocsp_stapling_callback(SSL* ssl, const std::map& ocspMap); std::map libssl_load_ocsp_responses(const std::vector& ocspFiles, std::vector keyTypes);