]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
dnsdist: add support for password protected PCKS12 files for TLS configuration
authorCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Fri, 19 Nov 2021 14:21:15 +0000 (15:21 +0100)
committerCharles-Henri Bruyand <charles-henri.bruyand@open-xchange.com>
Thu, 16 Dec 2021 09:08:58 +0000 (10:08 +0100)
pdns/dnsdist-lua.cc
pdns/dnsdistdist/docs/reference/config.rst
pdns/libssl.cc
pdns/libssl.hh
pdns/tcpiohandler.cc

index 4424b34135e5e4092c2b6cf00f4d3c2966eff25f..769f62d794dc146046e267a9b99439449fd55ab6 100644 (file)
@@ -132,13 +132,25 @@ static void parseLocalBindVars(boost::optional<localbind_t> vars, bool& reusePor
 }
 
 #if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
-static bool loadTLSCertificateAndKeys(const std::string& context, std::vector<std::pair<std::string, std::string>>& pairs, boost::variant<std::string, std::vector<std::pair<int, std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles)
+static bool loadTLSCertificateAndKeys(const std::string& context, std::vector<TLSCertKeyPair>& pairs, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, std::vector<std::pair<int, std::string>>, std::vector<std::pair<int, std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles, std::optional<std::string> password = std::nullopt)
 {
   if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) {
     auto certFile = boost::get<std::string>(certFiles);
     auto keyFile = boost::get<std::string>(keyFiles);
     pairs.clear();
-    pairs.emplace_back(certFile, keyFile);
+    pairs.emplace_back(certFile, keyFile, password);
+  }
+  else if (certFiles.type() == typeid(std::shared_ptr<TLSCertKeyPair>)) {
+    auto cert = boost::get<std::shared_ptr<TLSCertKeyPair>>(certFiles);
+    pairs.clear();
+    pairs.emplace_back(*cert);
+  }
+  else if (certFiles.type() == typeid(std::vector<std::pair<int, std::shared_ptr<TLSCertKeyPair>>>)) {
+    auto certs = boost::get<std::vector<std::pair<int, std::shared_ptr<TLSCertKeyPair>>>>(certFiles);
+    pairs.clear();
+    for (const auto& cert : certs) {
+      pairs.emplace_back(*(cert.second));
+    }
   }
   else if (certFiles.type() == typeid(std::vector<std::pair<int, std::string>>) && keyFiles.type() == typeid(std::vector<std::pair<int, std::string>>)) {
     auto certFilesVect = boost::get<std::vector<std::pair<int, std::string>>>(certFiles);
@@ -146,7 +158,7 @@ static bool loadTLSCertificateAndKeys(const std::string& context, std::vector<st
     if (certFilesVect.size() == keyFilesVect.size()) {
       pairs.clear();
       for (size_t idx = 0; idx < certFilesVect.size(); idx++) {
-        pairs.emplace_back(certFilesVect.at(idx).second, keyFilesVect.at(idx).second);
+        pairs.emplace_back(certFilesVect.at(idx).second, keyFilesVect.at(idx).second, password);
       }
     }
     else {
@@ -2319,7 +2331,27 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
   });
 
-  luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::vector<std::pair<int, std::string>>>> certFiles, boost::optional<boost::variant<std::string, std::vector<std::pair<int, std::string>>>> keyFiles, boost::optional<boost::variant<std::string, vector<pair<int, std::string>>>> urls, boost::optional<localbind_t> vars) {
+  typedef std::unordered_map<std::string, std::string> tlscertificateopts_t;
+  luaCtx.writeFunction("newTLSCertificate", [client](const std::string& cert, boost::optional<tlscertificateopts_t> opts) {
+    std::shared_ptr<TLSCertKeyPair> result = nullptr;
+    if (client) {
+      return result;
+    }
+#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
+    std::optional<std::string> key, password;
+    if (opts) {
+      if (opts->count("key")) {
+        key = boost::get<const string>((*opts)["key"]);
+      }
+      if (opts->count("password")) {
+        password = boost::get<const string>((*opts)["password"]);
+      }
+    }
+    result = std::make_shared<TLSCertKeyPair>(TLSCertKeyPair{cert, key, password});
+#endif
+    return result;
+  });
+  luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional<boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, std::vector<std::pair<int, std::string>>, std::vector<std::pair<int, std::shared_ptr<TLSCertKeyPair>>>>> certFiles, boost::optional<boost::variant<std::string, std::vector<std::pair<int, std::string>>>> keyFiles, boost::optional<boost::variant<std::string, vector<pair<int, std::string>>>> urls, boost::optional<localbind_t> vars) {
     if (client) {
       return;
     }
@@ -2331,7 +2363,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
     auto frontend = std::make_shared<DOHFrontend>();
 
-    if (certFiles && !certFiles->empty() && keyFiles && !keyFiles->empty()) {
+    if (certFiles && !certFiles->empty()) {
       if (!loadTLSCertificateAndKeys("addDOHLocal", frontend->d_tlsConfig.d_certKeyPairs, *certFiles, *keyFiles)) {
         return;
       }
@@ -2510,7 +2542,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
   });
 
-  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<DOHFrontend> frontend, boost::variant<std::string, std::vector<std::pair<int, std::string>>> certFiles, boost::variant<std::string, std ::vector<std::pair<int, std::string>>> keyFiles) {
+  luaCtx.registerFunction<void (std::shared_ptr<DOHFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, std::vector<std::pair<int, std::string>>, std::vector<std::pair<int, std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<DOHFrontend> frontend, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, std::vector<std::pair<int, std::string>>, std::vector<std::pair<int, std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::variant<std::string, std ::vector<std::pair<int, std::string>>> keyFiles) {
 #ifdef HAVE_DNS_OVER_HTTPS
     if (frontend != nullptr) {
       if (loadTLSCertificateAndKeys("DOHFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
@@ -2545,7 +2577,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     }
   });
 
-  luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant<std::string, std::vector<std::pair<int, std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles, boost::optional<localbind_t> vars) {
+  luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, std::vector<std::pair<int, std::string>>, std::vector<std::pair<int, std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles, boost::optional<localbind_t> vars) {
     if (client) {
       return;
     }
@@ -2730,7 +2762,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck)
     frontend->setupTLS();
   });
 
-  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(boost::variant<std::string, std::vector<std::pair<int, std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<TLSFrontend>& frontend, boost::variant<std::string, std::vector<std::pair<int, std::string>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles) {
+  luaCtx.registerFunction<void (std::shared_ptr<TLSFrontend>::*)(boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, std::vector<std::pair<int, std::string>>, std::vector<std::pair<int, std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr<TLSFrontend>& frontend, boost::variant<std::string, std::shared_ptr<TLSCertKeyPair>, std::vector<std::pair<int, std::string>>, std::vector<std::pair<int, std::shared_ptr<TLSCertKeyPair>>>> certFiles, boost::variant<std::string, std::vector<std::pair<int, std::string>>> keyFiles) {
 #ifdef HAVE_DNS_OVER_TLS
     if (loadTLSCertificateAndKeys("TLSFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) {
       frontend->setupTLS();
index b11ac768e959d2fd84a69b51a7b96a47b10f487e..1863757a56d90321d56e5237d870ac38fabaec6b 100644 (file)
@@ -118,13 +118,16 @@ Listen Sockets
     ``enableRenegotiation``, ``exactPathMatching``, ``maxConcurrentTCPConnections`` and ``releaseBuffers`` options added.
     ``internalPipeBufferSize`` now defaults to 1048576 on Linux.
 
+  .. versionchanged:: 1.x.0
+     ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`)
+
   Listen on the specified address and TCP port for incoming DNS over HTTPS connections, presenting the specified X.509 certificate.
   If no certificate (or key) files are specified, listen for incoming DNS over HTTP connections instead.
 
   :param str address: The IP Address with an optional port to listen on.
                       The default port is 443.
-  :param str certFile(s): The path to a X.509 certificate file in PEM format, or a list of paths to such files.
-  :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones.
+  :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a TLSCertificate object.
+  :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains TLSCertificate objects.
   :param str-or-list urls: The path part of a URL, or a list of paths, to accept queries on. Any query with a path matching exactly one of these will be treated as a DoH query (sub-paths can be accepted by setting the ``exactPathMatching`` to false). The default is /dns-query.
   :param table options: A table with key: value pairs with listen options.
 
@@ -168,13 +171,15 @@ Listen Sockets
     ``enableRenegotiation``, ``maxConcurrentTCPConnections``, ``maxInFlight`` and ``releaseBuffers`` options added.
   .. versionchanged:: 1.8.0
     ``tlsAsyncMode`` option added.
+  .. versionchanged:: 1.8.0
+     ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`)
 
   Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate.
 
   :param str address: The IP Address with an optional port to listen on.
                       The default port is 853.
-  :param str certFile(s): The path to a X.509 certificate file in PEM format, or a list of paths to such files.
-  :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones.
+  :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a TLSCertificate object.
+  :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains TLSCertificate objects.
   :param table options: A table with key: value pairs with listen options.
 
   Options:
@@ -1641,6 +1646,25 @@ Other functions
   :param string engineName: The name of the engine to load.
   :param string defaultString: The default string to pass to the engine. The exact value depends on the engine but represents the algorithms to register with the engine, as a list of  comma-separated keywords. For example "RSA,EC,DSA,DH,PKEY,PKEY_CRYPTO,PKEY_ASN1".
 
+.. function:: newTLSCertificate(pathToCert[, options])
+
+  .. versionadded:: 1.8.0
+
+  Creates a TLSCertificate object suited to be used with functions like :func:`addDOHLocal` and :func:`addTLSLocal` for TLS certificate configuration
+
+  :param string pathToCert: Path to a file containing the certificate or a PCKS12 file containing both certificate and the key
+  :param table options: A table with key: value pairs with additional options.
+
+  Options:
+
+  * ``key="path/to/key"``: string - Path to a file containing the key corresponding to the certificate.
+  * ``password="pass"``: string - Password protecting the PCKS12 file if appropriate.
+
+  .. code-block:: lua
+
+    newTLSCertificate("path/to/pub.crt", {key="pat/to/private.pem"})
+    newTLSCertificate("path/to/domain.p12", {password="passphrase"}) -- use a password protected PCKS12 file
+
 DOHFrontend
 ~~~~~~~~~~~
 
@@ -1654,10 +1678,11 @@ DOHFrontend
 
      .. versionadded:: 1.6.1
 
-     Create and switch to a new TLS context using the same options than were passed to the corresponding `addDOHLocal()` directive, but loading new certificates and keys from the selected files, replacing the existing ones.
+     .. versionchanged:: 1.x.0
+        ``certFile`` now accepts a TLSCertificate object or a list of such objects (see :func:`newTLSCertificate`)
 
-     :param str certFile(s): The path to a X.509 certificate file in PEM format, or a list of paths to such files.
-     :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones.
+     :param str certFile(s): The path to a X.509 certificate file in PEM format, a list of paths to such files, or a TLSCertificate object.
+     :param str keyFile(s): The path to the private key file corresponding to the certificate, or a list of paths to such files, whose order should match the certFile(s) ones. Ignored if ``certFile`` contains TLSCertificate objects.
 
   .. method:: DOHFrontend:loadTicketsKeys(ticketsKeysFile)
 
index dfce1a02e97c96ebc11f90459400f691e686c688..d5fb6d42bc14fe6c8e20adbb5f5d97b937697c99 100644 (file)
@@ -19,6 +19,7 @@
 #include <openssl/ocsp.h>
 #include <openssl/rand.h>
 #include <openssl/ssl.h>
+#include <openssl/pkcs12.h>
 #include <fcntl.h>
 
 #ifdef HAVE_LIBSODIUM
@@ -785,25 +786,53 @@ std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> libssl_init_server_context(const TLS
   std::vector<int> keyTypes;
   /* load certificate and private key */
   for (const auto& pair : config.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("An error occurred while trying to load the TLS server certificate file: " + pair.first);
-    }
-    if (SSL_CTX_use_PrivateKey_file(ctx.get(), pair.second.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.second);
+    if (!pair.d_key) {
+      // 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))
+      {
+        ERR_print_errors_fp(stderr);
+        throw std::runtime_error("An error occured while parsing PKCS12 file " + pair.d_cert);
+      }
+      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, sk_X509_free);
+
+      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 {
+      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.second + "' does not match the certificate from '" + pair.first + "'");
+      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.second + "' has an unknown type");
+      throw std::runtime_error("The key from '" + pair.d_key.value() + "' has an unknown type");
     }
     keyTypes.push_back(keyType);
 }
+ }
 
   if (!config.d_ocspFiles.empty()) {
     try {
index db127512217a82598cee4101bc9daa5bff0bd999..aeb4059ef5127a18e9687a8795b295222db891ba 100644 (file)
@@ -7,6 +7,7 @@
 #include <optional>
 #include <string>
 #include <vector>
+#include <optional>
 
 #include "config.h"
 #include "circular_buffer.hh"
 
 enum class LibsslTLSVersion : uint8_t { Unknown, TLS10, TLS11, TLS12, TLS13 };
 
+struct TLSCertKeyPair
+{
+  std::string d_cert;
+  std::optional<std::string> d_key;
+  std::optional<std::string> d_password;
+  explicit TLSCertKeyPair(const std::string& cert, std::optional<std::string> key = std::nullopt, std::optional<std::string> password = std::nullopt):
+    d_cert(cert), d_key(key), d_password(password) {
+  }
+};
+
 class TLSConfig
 {
 public:
-  std::vector<std::pair<std::string, std::string>> d_certKeyPairs;
+  std::vector<TLSCertKeyPair> d_certKeyPairs;
   std::vector<std::string> d_ocspFiles;
 
   std::string d_ciphers;
index ce531313d95467029adc0a954c1da3a295f86f1a..3534d4223ba9b6f44410d81234431974aa10ff53 100644 (file)
@@ -1513,9 +1513,9 @@ public:
     creds = nullptr;
 
     for (const auto& pair : fe.d_tlsConfig.d_certKeyPairs) {
-      rc = gnutls_certificate_set_x509_key_file(d_creds.get(), pair.first.c_str(), pair.second.c_str(), GNUTLS_X509_FMT_PEM);
+      rc = gnutls_certificate_set_x509_key_file(d_creds.get(), pair.d_cert.c_str(), pair.d_key->c_str(), GNUTLS_X509_FMT_PEM);
       if (rc != GNUTLS_E_SUCCESS) {
-        throw std::runtime_error("Error loading certificate ('" + pair.first + "') and key ('" + pair.second + "') for TLS context on " + fe.d_addr.toStringWithPort() + ": " + gnutls_strerror(rc));
+        throw std::runtime_error("Error loading certificate ('" + pair.d_cert + "') and key ('" + pair.d_key.value() + "') for TLS context on " + fe.d_addr.toStringWithPort() + ": " + gnutls_strerror(rc));
       }
     }
 
@@ -1523,7 +1523,7 @@ public:
     for (const auto& file : fe.d_tlsConfig.d_ocspFiles) {
       rc = gnutls_certificate_set_ocsp_status_request_file(d_creds.get(), file.c_str(), count);
       if (rc != GNUTLS_E_SUCCESS) {
-        throw std::runtime_error("Error loading OCSP response from file '" + file + "' for certificate ('" + fe.d_tlsConfig.d_certKeyPairs.at(count).first + "') and key ('" + fe.d_tlsConfig.d_certKeyPairs.at(count).second + "') for TLS context on " + fe.d_addr.toStringWithPort() + ": " + gnutls_strerror(rc));
+        throw std::runtime_error("Error loading OCSP response from file '" + file + "' for certificate ('" + fe.d_tlsConfig.d_certKeyPairs.at(count).d_cert + "') and key ('" + fe.d_tlsConfig.d_certKeyPairs.at(count).d_key.value() + "') for TLS context on " + fe.d_addr.toStringWithPort() + ": " + gnutls_strerror(rc));
       }
       ++count;
     }