From: Charles-Henri Bruyand Date: Fri, 19 Nov 2021 14:21:15 +0000 (+0100) Subject: dnsdist: add support for password protected PCKS12 files for TLS configuration X-Git-Tag: auth-4.7.0-alpha1~115^2~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6de19ce2c74c1067813c0ccc61f5cbe01557e0d4;p=thirdparty%2Fpdns.git dnsdist: add support for password protected PCKS12 files for TLS configuration --- diff --git a/pdns/dnsdist-lua.cc b/pdns/dnsdist-lua.cc index 4424b34135..769f62d794 100644 --- a/pdns/dnsdist-lua.cc +++ b/pdns/dnsdist-lua.cc @@ -132,13 +132,25 @@ static void parseLocalBindVars(boost::optional vars, bool& reusePor } #if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) -static bool loadTLSCertificateAndKeys(const std::string& context, std::vector>& pairs, boost::variant>> certFiles, boost::variant>> keyFiles) +static bool loadTLSCertificateAndKeys(const std::string& context, std::vector& pairs, boost::variant, std::vector>, std::vector>>> certFiles, boost::variant>> keyFiles, std::optional password = std::nullopt) { if (certFiles.type() == typeid(std::string) && keyFiles.type() == typeid(std::string)) { auto certFile = boost::get(certFiles); auto keyFile = boost::get(keyFiles); pairs.clear(); - pairs.emplace_back(certFile, keyFile); + pairs.emplace_back(certFile, keyFile, password); + } + else if (certFiles.type() == typeid(std::shared_ptr)) { + auto cert = boost::get>(certFiles); + pairs.clear(); + pairs.emplace_back(*cert); + } + else if (certFiles.type() == typeid(std::vector>>)) { + auto certs = boost::get>>>(certFiles); + pairs.clear(); + for (const auto& cert : certs) { + pairs.emplace_back(*(cert.second)); + } } else if (certFiles.type() == typeid(std::vector>) && keyFiles.type() == typeid(std::vector>)) { auto certFilesVect = boost::get>>(certFiles); @@ -146,7 +158,7 @@ static bool loadTLSCertificateAndKeys(const std::string& context, std::vector>>> certFiles, boost::optional>>> keyFiles, boost::optional>>> urls, boost::optional vars) { + typedef std::unordered_map tlscertificateopts_t; + luaCtx.writeFunction("newTLSCertificate", [client](const std::string& cert, boost::optional opts) { + std::shared_ptr result = nullptr; + if (client) { + return result; + } +#if defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS) + std::optional key, password; + if (opts) { + if (opts->count("key")) { + key = boost::get((*opts)["key"]); + } + if (opts->count("password")) { + password = boost::get((*opts)["password"]); + } + } + result = std::make_shared(TLSCertKeyPair{cert, key, password}); +#endif + return result; + }); + luaCtx.writeFunction("addDOHLocal", [client](const std::string& addr, boost::optional, std::vector>, std::vector>>>> certFiles, boost::optional>>> keyFiles, boost::optional>>> urls, boost::optional vars) { if (client) { return; } @@ -2331,7 +2363,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) } auto frontend = std::make_shared(); - 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::*)(boost::variant>> certFiles, boost::variant>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr frontend, boost::variant>> certFiles, boost::variant>> keyFiles) { + luaCtx.registerFunction::*)(boost::variant, std::vector>, std::vector>>> certFiles, boost::variant>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr frontend, boost::variant, std::vector>, std::vector>>> certFiles, boost::variant>> 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>> certFiles, boost::variant>> keyFiles, boost::optional vars) { + luaCtx.writeFunction("addTLSLocal", [client](const std::string& addr, boost::variant, std::vector>, std::vector>>> certFiles, boost::variant>> keyFiles, boost::optional vars) { if (client) { return; } @@ -2730,7 +2762,7 @@ static void setupLuaConfig(LuaContext& luaCtx, bool client, bool configCheck) frontend->setupTLS(); }); - luaCtx.registerFunction::*)(boost::variant>> certFiles, boost::variant>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr& frontend, boost::variant>> certFiles, boost::variant>> keyFiles) { + luaCtx.registerFunction::*)(boost::variant, std::vector>, std::vector>>> certFiles, boost::variant>> keyFiles)>("loadNewCertificatesAndKeys", [](std::shared_ptr& frontend, boost::variant, std::vector>, std::vector>>> certFiles, boost::variant>> keyFiles) { #ifdef HAVE_DNS_OVER_TLS if (loadTLSCertificateAndKeys("TLSFrontend::loadNewCertificatesAndKeys", frontend->d_tlsConfig.d_certKeyPairs, certFiles, keyFiles)) { frontend->setupTLS(); diff --git a/pdns/dnsdistdist/docs/reference/config.rst b/pdns/dnsdistdist/docs/reference/config.rst index b11ac768e9..1863757a56 100644 --- a/pdns/dnsdistdist/docs/reference/config.rst +++ b/pdns/dnsdistdist/docs/reference/config.rst @@ -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) diff --git a/pdns/libssl.cc b/pdns/libssl.cc index dfce1a02e9..d5fb6d42bc 100644 --- a/pdns/libssl.cc +++ b/pdns/libssl.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #ifdef HAVE_LIBSODIUM @@ -785,25 +786,53 @@ std::unique_ptr libssl_init_server_context(const TLS std::vector 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(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(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(keyptr, EVP_PKEY_free); + auto cert = std::unique_ptr(certptr, X509_free); + auto ca = std::unique_ptr(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 { diff --git a/pdns/libssl.hh b/pdns/libssl.hh index db12751221..aeb4059ef5 100644 --- a/pdns/libssl.hh +++ b/pdns/libssl.hh @@ -7,6 +7,7 @@ #include #include #include +#include #include "config.h" #include "circular_buffer.hh" @@ -14,10 +15,20 @@ enum class LibsslTLSVersion : uint8_t { Unknown, TLS10, TLS11, TLS12, TLS13 }; +struct TLSCertKeyPair +{ + std::string d_cert; + std::optional d_key; + std::optional d_password; + explicit TLSCertKeyPair(const std::string& cert, std::optional key = std::nullopt, std::optional password = std::nullopt): + d_cert(cert), d_key(key), d_password(password) { + } +}; + class TLSConfig { public: - std::vector> d_certKeyPairs; + std::vector d_certKeyPairs; std::vector d_ocspFiles; std::string d_ciphers; diff --git a/pdns/tcpiohandler.cc b/pdns/tcpiohandler.cc index ce531313d9..3534d4223b 100644 --- a/pdns/tcpiohandler.cc +++ b/pdns/tcpiohandler.cc @@ -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; }