]> granicus.if.org Git - pdns/commitdiff
dnsdist: Implement TLS Session Ticket Keys management for DoH
authorRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 26 Sep 2019 14:19:28 +0000 (16:19 +0200)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Thu, 26 Sep 2019 14:19:28 +0000 (16:19 +0200)
pdns/dnsdist-lua.cc
pdns/dnsdistdist/docs/reference/config.rst
pdns/dnsdistdist/doh.cc
pdns/dnsdistdist/libssl.cc
pdns/dnsdistdist/tcpiohandler.cc
pdns/doh.hh
pdns/libssl.hh

index 5a3a3d00874ce7cda4f3a06829c481828be7b734..4daf3de3e172282399ef7e068eebcfdd639a33d0 100644 (file)
@@ -1746,6 +1746,33 @@ void setupLuaConfig(bool client)
           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) {
@@ -1851,6 +1878,18 @@ void setupLuaConfig(bool client)
         }
       });
 
+    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;
index 092c4db36378302a23fc16a8f4550569f9234ab6..68ce3fcaeb99ce92b4670c7e0c1454c9516cf493 100644 (file)
@@ -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.
index a4b270cd8888136e2b3c0c5347ea7fe31d7f25ba..f5add032b287b0d77e566d6ef382d2b6e9fbe2ba 100644 (file)
@@ -897,7 +897,20 @@ static int ocsp_stapling_callback(SSL* ssl, void* arg)
   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);
 
@@ -907,20 +920,40 @@ static std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> 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<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);
@@ -941,9 +974,9 @@ static std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> 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<SSL_CTX, void(*)(SSL_CTX*)> 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<DOHAcceptContext>(new DOHAcceptContext());
@@ -1003,11 +1097,7 @@ void DOHFrontend::setup()
   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();
index e77e0dd2239e63bb4af7620a4afd0c24b8cecc53..f900365bafc85f68ac541684e6b0b430bad0ddea 100644 (file)
 #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};
@@ -58,24 +62,31 @@ static void openssl_thread_cleanup()
   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();
@@ -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<int, std::string>& ocspMap)
@@ -351,4 +408,159 @@ bool libssl_set_min_tls_version(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& 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<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 */
index 835b3b395a0ab0f4b57224cb2c8d843cf3b63ff4..bb2edad5336bb4e6e5787c8a320a17d6da0a2303 100644 (file)
@@ -1,7 +1,5 @@
-#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:
@@ -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, void(*)(SSL_CTX*)>(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<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)
@@ -558,8 +359,7 @@ public:
 
   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;
@@ -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<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
@@ -601,11 +384,8 @@ private:
   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
index 0b90c02c4d18236f597becd1e232f4548c2649d0..41e6003293710bc4822b779896f7cca83631bc17 100644 (file)
@@ -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<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
@@ -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 */
 };
 
index f0cdd16979f570f8e0a8e138334b605d1a24919d..895e09bbf65ae30e2e136593e71a209b926ad7f2 100644 (file)
@@ -1,11 +1,14 @@
 #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 };
 
@@ -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<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);