frontend->d_customResponseHeaders.push_back(headerResponse);
}
}
+
+ if (vars->count("ticketKeyFile")) {
+ frontend->d_ticketKeyFile = boost::get<const string>((*vars)["ticketKeyFile"]);
+ }
+
+ if (vars->count("ticketsKeysRotationDelay")) {
+ frontend->d_ticketsKeyRotationDelay = boost::get<int>((*vars)["ticketsKeysRotationDelay"]);
+ }
+
+ if (vars->count("numberOfTicketsKeys")) {
+ frontend->d_numberOfTicketsKeys = boost::get<int>((*vars)["numberOfTicketsKeys"]);
+ }
+
+ if (vars->count("sessionTickets")) {
+ frontend->d_enableTickets = boost::get<bool>((*vars)["sessionTickets"]);
+ }
+
+ if (vars->count("numberOfStoredSessions")) {
+ auto value = boost::get<int>((*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<std::vector<std::pair<int, std::string>>>((*vars)["ocspResponses"]);
for (const auto& file : files) {
}
});
+ g_lua.registerFunction<void(std::shared_ptr<DOHFrontend>::*)()>("rotateTicketsKey", [](std::shared_ptr<DOHFrontend> frontend) {
+ if (frontend != nullptr) {
+ frontend->rotateTicketsKey(time(nullptr));
+ }
+ });
+
+ g_lua.registerFunction<void(std::shared_ptr<DOHFrontend>::*)(const std::string&)>("loadTicketsKeys", [](std::shared_ptr<DOHFrontend> frontend, const std::string& file) {
+ if (frontend != nullptr) {
+ frontend->loadTicketsKeys(file);
+ }
+ });
+
g_lua.registerFunction<void(std::shared_ptr<DOHFrontend>::*)(const std::map<int, std::shared_ptr<DOHResponseMapEntry>>&)>("setResponsesMap", [](std::shared_ptr<DOHFrontend> frontend, const std::map<int, std::shared_ptr<DOHResponseMapEntry>>& map) {
if (frontend != nullptr) {
std::vector<std::shared_ptr<DOHResponseMapEntry>> newMap;
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.
return libssl_ocsp_stapling_callback(ssl, *ocspMap);
}
-static std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> getTLSContext(const std::vector<std::pair<std::string, std::string>>& pairs, const std::string& ciphers, const std::string& ciphers13, LibsslTLSVersion minTLSVersion, const std::vector<std::string>& ocspFiles, std::map<int, std::string>& 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<DOHFrontend*>(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<SSL_CTX, void(*)(SSL_CTX*)> getTLSContext(DOHFrontend& df,
+ std::map<int, std::string>& ocspResponses)
{
auto ctx = std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free);
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<OpenSSLTLSTicketKeysRing>(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<int> 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);
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);
}
}
- 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;
}
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();
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<DOHAcceptContext>(new DOHAcceptContext());
d_dsc = std::make_shared<DOHServerConfig>(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();
#include <openssl/rand.h>
#include <openssl/ssl.h>
+#ifdef HAVE_LIBSODIUM
+#include <sodium.h>
+#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};
OPENSSL_free(openssllocks);
}
-static std::atomic<uint64_t> s_users;
#endif /* (OPENSSL_VERSION_NUMBER < 0x1010000fL || defined LIBRESSL_VERSION_NUMBER) */
+static std::atomic<uint64_t> 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();
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<int, std::string>& ocspMap)
#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<OpenSSLTLSTicketKey> newKey)
+{
+ WriteLock wl(&d_lock);
+ d_ticketKeys.push_front(newKey);
+}
+
+std::shared_ptr<OpenSSLTLSTicketKey> OpenSSLTLSTicketKeysRing::getEncryptionKey()
+{
+ ReadLock rl(&d_lock);
+ return d_ticketKeys.front();
+}
+
+std::shared_ptr<OpenSSLTLSTicketKey> 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<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(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);
+}
+
+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 */
-#include <fstream>
#include "config.h"
-#include "circular_buffer.hh"
#include "dolog.hh"
#include "iputils.hh"
#include "lock.hh"
#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<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()
- {
-#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<OpenSSLTLSTicketKey> newKey)
- {
- WriteLock wl(&d_lock);
- d_ticketKeys.push_back(newKey);
- }
-
- std::shared_ptr<OpenSSLTLSTicketKey> getEncryptionKey()
- {
- ReadLock rl(&d_lock);
- return d_ticketKeys.front();
- }
-
- std::shared_ptr<OpenSSLTLSTicketKey> 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<std::shared_ptr<OpenSSLTLSTicketKey> > d_ticketKeys;
- pthread_rwlock_t d_lock;
-};
-
class OpenSSLTLSConnection: public TLSConnection
{
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, void(*)(SSL_CTX*)>(SSL_CTX_new(SSLv23_server_method()), SSL_CTX_free);
if (!d_tlsCtx) {
/* 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());
{
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<OpenSSLTLSIOCtx*>(SSL_CTX_get_ex_data(sslCtx, s_ticketsKeyIndex));
+ OpenSSLTLSIOCtx* ctx = reinterpret_cast<OpenSSLTLSIOCtx*>(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)
void rotateTicketsKey(time_t now) override
{
- auto newKey = std::make_shared<OpenSSLTLSTicketKey>();
- d_ticketKeys.addKey(newKey);
+ d_ticketKeys.rotateTicketsKey(now);
if (d_ticketsKeyRotationDelay > 0) {
d_ticketsKeyNextRotation = now + d_ticketsKeyRotationDelay;
void loadTicketsKeys(const std::string& keyFile) override
{
- bool keyLoaded = false;
- ifstream file(keyFile);
- try {
- do {
- auto newKey = std::make_shared<OpenSSLTLSTicketKey>(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
OpenSSLTLSTicketKeysRing d_ticketKeys;
std::map<int, std::string> d_ocspResponses;
std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> d_tlsCtx;
- static std::atomic<uint64_t> s_users;
};
-std::atomic<uint64_t> OpenSSLTLSIOCtx::s_users(0);
-
#endif /* HAVE_LIBSSL */
#ifdef HAVE_GNUTLS
std::string d_ciphers13;
std::string d_serverTokens{"h2o/dnsdist"};
LibsslTLSVersion d_minTLSVersion{LibsslTLSVersion::TLS10};
+#ifdef HAVE_DNS_OVER_HTTPS
+ std::unique_ptr<OpenSSLTLSTicketKeysRing> d_ticketKeys{nullptr};
+#endif
std::vector<std::pair<std::string, std::string>> d_customResponseHeaders;
ComboAddress d_local;
uint32_t d_idleTimeout{30}; // HTTP idle timeout in seconds
std::vector<std::string> 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<uint64_t> d_httpconnects; // number of TCP/IP connections established
std::atomic<uint64_t> d_tls10queries; // valid DNS queries received via TLSv1.0
HTTPVersionStats d_http1Stats;
HTTPVersionStats d_http2Stats;
+
#ifndef HAVE_DNS_OVER_HTTPS
void setup()
{
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 */
};
#pragma once
+#include <fstream>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "config.h"
+#include "circular_buffer.hh"
+#include "lock.hh"
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<OpenSSLTLSTicketKey> newKey);
+ std::shared_ptr<OpenSSLTLSTicketKey> getEncryptionKey();
+ std::shared_ptr<OpenSSLTLSTicketKey> 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<std::shared_ptr<OpenSSLTLSTicketKey> > 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<int, std::string>& ocspMap);
std::map<int, std::string> libssl_load_ocsp_responses(const std::vector<std::string>& ocspFiles, std::vector<int> keyTypes);