g_noLuaSideEffect = boost::logic::indeterminate;
}
-typedef std::unordered_map<std::string, boost::variant<bool, int, std::string, std::vector<std::pair<int,int> >, std::map<std::string,std::string> > > localbind_t;
+typedef std::unordered_map<std::string, boost::variant<bool, int, std::string, std::vector<std::pair<int,int> >, std::map<std::string,std::string>, std::vector<std::pair<int, std::string> > > > localbind_t;
static void parseLocalBindVars(boost::optional<localbind_t> vars, bool& reusePort, int& tcpFastOpenQueueSize, std::string& interface, std::set<int>& cpus)
{
frontend->d_customResponseHeaders.push_back(headerResponse);
}
}
+ if (vars->count("ocspResponses")) {
+ auto files = boost::get<std::vector<std::pair<int, std::string>>>((*vars)["ocspResponses"]);
+ for (const auto& file : files) {
+ frontend->d_ocspFiles.push_back(file.second);
+ }
+ }
}
g_dohlocals.push_back(frontend);
auto cs = std::unique_ptr<ClientState>(new ClientState(frontend->d_local, true, reusePort, tcpFastOpenQueueSize, interface, cpus));
}
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) {
+ frontend->d_ocspFiles.push_back(file.second);
+ }
+ }
}
try {
* ``ciphersTLS13``: str - The TLS ciphers to use for TLS 1.3, in OpenSSL format.
* ``serverTokens``: str - The content of the Server: HTTP header returned by dnsdist. The default is "h2o/dnsdist".
* ``customResponseHeaders={}``: table - Set custom HTTP header(s) returned by dnsdist.
+ * ``ocspResponses``: list - List of files containing OCSP responses, in the same order than the certificates and keys, that will be used to provide OCSP stapling responses.
.. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])
.. versionchanged:: 1.3.3
``numberOfStoredSessions`` option added.
.. versionchanged:: 1.4.0
- ``ciphersTLS13`` option added.
+ ``ciphersTLS13`` and ``ocspResponses`` options added.
Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate.
* ``ticketsKeysRotationDelay``: int - Set the delay before the TLS tickets key is rotated, in seconds. Default is 43200 (12h).
* ``sessionTickets``: bool - Whether session resumption via session tickets is enabled. Default is true, meaning tickets are enabled.
* ``numberOfStoredSessions``: int - The maximum number of sessions kept in memory at the same time. At this time this is only supported by the OpenSSL provider, as stored sessions are not supported with the GnuTLS one. Default is 20480. Setting this value to 0 disables stored session entirely.
+ * ``ocspResponses``: list - List of files containing OCSP responses, in the same order than the certificates and keys, that will be used to provide OCSP stapling responses.
.. function:: setLocal(address[, options])
}
}
+ std::map<int, std::string> d_ocspResponses;
+
private:
h2o_accept_ctx_t d_h2o_accept_ctx;
std::atomic<uint64_t> d_refcnt{1};
return 0;
}
-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)
+static int ocsp_stapling_callback(SSL* ssl, void* arg)
+{
+ if (ssl == nullptr || arg == nullptr) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ const auto ocspMap = reinterpret_cast<std::map<int, std::string>*>(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, const std::vector<std::string>& ocspFiles, 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_CTX_set_ecdh_auto(ctx.get(), 1);
#endif
+ std::vector<int> keyTypes;
/* load certificate and private key */
for (const auto& pair : pairs) {
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 private key file: " + pair.second);
}
+ if (SSL_CTX_check_private_key(ctx.get()) != 1) {
+ ERR_print_errors_fp(stderr);
+ throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, the key from '" + pair.second + "' does not match the certificate from '" + pair.first + "'");
+ }
+ /* store the type of the new key, we might need it later to select the right OCSP stapling response */
+ keyTypes.push_back(libssl_get_last_key_type(ctx));
+ }
+
+ if (!ocspFiles.empty()) {
+ try {
+ ocspResponses = libssl_load_ocsp_responses(ocspFiles, keyTypes);
+
+ SSL_CTX_set_tlsext_status_cb(ctx.get(), &ocsp_stapling_callback);
+ SSL_CTX_set_tlsext_status_arg(ctx.get(), &ocspResponses);
+ }
+ catch(const std::exception& e) {
+ throw std::runtime_error("Unable to load OCSP responses for the SSL/TLS DoH listener: " + std::string(e.what()));
+ }
}
if (SSL_CTX_set_cipher_list(ctx.get(), ciphers.empty() == false ? ciphers.c_str() : DOH_DEFAULT_CIPHERS) != 1) {
if (setupTLS) {
auto tlsCtx = getTLSContext(dsc.df->d_certKeyPairs,
dsc.df->d_ciphers,
- dsc.df->d_ciphers13);
+ dsc.df->d_ciphers13,
+ dsc.df->d_ocspFiles,
+ ctx.d_ocspResponses);
nativeCtx->ssl_ctx = tlsCtx.release();
}
auto tlsCtx = getTLSContext(d_certKeyPairs,
d_ciphers,
- d_ciphers13);
+ d_ciphers13,
+ d_ocspFiles,
+ d_dsc->accept_ctx->d_ocspResponses);
auto accept_ctx = d_dsc->accept_ctx->get();
accept_ctx->ssl_ctx = tlsCtx.release();
#ifdef HAVE_LIBSSL
#include <atomic>
+#include <fstream>
+#include <cstring>
#include <pthread.h>
+
#include <openssl/conf.h>
#include <openssl/err.h>
+#include <openssl/ocsp.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#endif
}
+int libssl_ocsp_stapling_callback(SSL* ssl, const std::map<int, std::string>& ocspMap)
+{
+ auto pkey = SSL_get_privatekey(ssl);
+ if (pkey == nullptr) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ /* look for an OCSP response for the corresponding private key type (RSA, ECDSA..) */
+ const auto& data = ocspMap.find(EVP_PKEY_base_id(pkey));
+ if (data == ocspMap.end()) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ /* we need to allocate a copy because OpenSSL will free the pointer passed to SSL_set_tlsext_status_ocsp_resp() */
+ void* copy = OPENSSL_malloc(data->second.size());
+ if (copy == nullptr) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+
+ memcpy(copy, data->second.data(), data->second.size());
+ SSL_set_tlsext_status_ocsp_resp(ssl, copy, data->second.size());
+ return SSL_TLSEXT_ERR_OK;
+}
+
+static bool libssl_validate_ocsp_response(const std::string& response)
+{
+ auto responsePtr = reinterpret_cast<const unsigned char *>(response.data());
+ std::unique_ptr<OCSP_RESPONSE, void(*)(OCSP_RESPONSE*)> resp(d2i_OCSP_RESPONSE(nullptr, &responsePtr, response.size()), OCSP_RESPONSE_free);
+ if (resp == nullptr) {
+ throw std::runtime_error("Unable to parse OCSP response");
+ }
+
+ int status = OCSP_response_status(resp.get());
+ if (status != OCSP_RESPONSE_STATUS_SUCCESSFUL) {
+ throw std::runtime_error("OCSP response status is not successful: " + std::to_string(status));
+ }
+
+ std::unique_ptr<OCSP_BASICRESP, void(*)(OCSP_BASICRESP*)> basic(OCSP_response_get1_basic(resp.get()), OCSP_BASICRESP_free);
+ if (basic == nullptr) {
+ throw std::runtime_error("Error getting a basic OCSP response");
+ }
+
+ if (OCSP_resp_count(basic.get()) != 1) {
+ throw std::runtime_error("More than one single response in an OCSP basic response");
+ }
+
+ auto singleResponse = OCSP_resp_get0(basic.get(), 0);
+ if (singleResponse == nullptr) {
+ throw std::runtime_error("Error getting a single response from the basic OCSP response");
+ }
+
+ int reason;
+ ASN1_GENERALIZEDTIME* revTime = nullptr;
+ ASN1_GENERALIZEDTIME* thisUpdate = nullptr;
+ ASN1_GENERALIZEDTIME* nextUpdate = nullptr;
+
+ auto singleResponseStatus = OCSP_single_get0_status(singleResponse, &reason, &revTime, &thisUpdate, &nextUpdate);
+ if (singleResponseStatus != V_OCSP_CERTSTATUS_GOOD) {
+ throw std::runtime_error("Invalid status for OCSP single response (" + std::to_string(singleResponseStatus) + ")");
+ }
+ if (thisUpdate == nullptr || nextUpdate == nullptr) {
+ throw std::runtime_error("Error getting validity of OCSP single response");
+ }
+
+ auto validityResult = OCSP_check_validity(thisUpdate, nextUpdate, /* 5 minutes of leeway */ 5 * 60, -1);
+ if (validityResult == 0) {
+ throw std::runtime_error("OCSP single response is not yet, or no longer, valid");
+ }
+
+ return true;
+}
+
+std::map<int, std::string> libssl_load_ocsp_responses(const std::vector<std::string>& ocspFiles, std::vector<int> keyTypes)
+{
+ std::map<int, std::string> ocspResponses;
+
+ if (ocspFiles.size() > keyTypes.size()) {
+ throw std::runtime_error("More OCSP files than certificates and keys loaded!");
+ }
+
+ size_t count = 0;
+ for (const auto& filename : ocspFiles) {
+ std::ifstream file(filename, std::ios::binary);
+ std::string content;
+ while(file) {
+ char buffer[4096];
+ file.read(buffer, sizeof(buffer));
+ if (file.bad()) {
+ file.close();
+ throw std::runtime_error("Unable to load OCSP response from '" + filename + "'");
+ }
+ content.append(buffer, file.gcount());
+ }
+ file.close();
+
+ try {
+ libssl_validate_ocsp_response(content);
+ ocspResponses.insert({keyTypes.at(count), std::move(content)});
+ }
+ catch (const std::exception& e) {
+ throw std::runtime_error("Error checking the validity of OCSP response from '" + filename + "': " + e.what());
+ }
+ ++count;
+ }
+
+ return ocspResponses;
+}
+
+int libssl_get_last_key_type(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx)
+{
+#if (OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined LIBRESSL_VERSION_NUMBER)
+ auto pkey = SSL_CTX_get0_privatekey(ctx.get());
+#else
+ auto temp = std::unique_ptr<SSL, void(*)(SSL*)>(SSL_new(ctx.get()), SSL_free);
+ if (!temp) {
+ return -1;
+ }
+ auto pkey = SSL_get_privatekey(temp.get());
+#endif
+
+ if (!pkey) {
+ return -1;
+ }
+
+ return EVP_PKEY_base_id(pkey);
+}
+
#endif /* HAVE_LIBSSL */
#pragma once
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "config.h"
+
+#ifdef HAVE_LIBSSL
+#include <openssl/ssl.h>
+
void registerOpenSSLUser();
void unregisterOpenSSLUser();
+
+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);
+int libssl_get_last_key_type(std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)>& ctx);
+
+#endif /* HAVE_LIBSSL */
SSL_CTX_sess_set_cache_size(d_tlsCtx.get(), fe.d_maxStoredSessions);
}
+ std::vector<int> keyTypes;
for (const auto& pair : fe.d_certKeyPairs) {
if (SSL_CTX_use_certificate_chain_file(d_tlsCtx.get(), pair.first.c_str()) != 1) {
ERR_print_errors_fp(stderr);
ERR_print_errors_fp(stderr);
throw std::runtime_error("Error loading key from " + pair.second + " for the TLS context on " + fe.d_addr.toStringWithPort());
}
+ if (SSL_CTX_check_private_key(d_tlsCtx.get()) != 1) {
+ ERR_print_errors_fp(stderr);
+ throw std::runtime_error("Key from '" + pair.second + "' does not match the certificate from '" + pair.first + "' for the TLS context on " + fe.d_addr.toStringWithPort());
+ }
+
+ /* store the type of the new key, we might need it later to select the right OCSP stapling response */
+ keyTypes.push_back(libssl_get_last_key_type(d_tlsCtx));
+ }
+
+ if (!fe.d_ocspFiles.empty()) {
+ try {
+ d_ocspResponses = libssl_load_ocsp_responses(fe.d_ocspFiles, keyTypes);
+ }
+ catch(const std::exception& e) {
+ throw std::runtime_error("Error loading responses for the TLS context on " + fe.d_addr.toStringWithPort() + ": " + e.what());
+ }
+
+ SSL_CTX_set_tlsext_status_cb(d_tlsCtx.get(), &OpenSSLTLSIOCtx::ocspStaplingCb);
+ SSL_CTX_set_tlsext_status_arg(d_tlsCtx.get(), &d_ocspResponses);
}
if (!fe.d_ciphers.empty()) {
return 1;
}
+ static int ocspStaplingCb(SSL* ssl, void* arg)
+ {
+ if (ssl == nullptr || arg == nullptr) {
+ return SSL_TLSEXT_ERR_NOACK;
+ }
+ const auto ocspMap = reinterpret_cast<std::map<int, std::string>*>(arg);
+ return libssl_ocsp_stapling_callback(ssl, *ocspMap);
+ }
+
std::unique_ptr<TLSConnection> getConnection(int socket, unsigned int timeout, time_t now) override
{
handleTicketsKeyRotation(now);
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;
};
}
}
+ size_t count = 0;
+ for (const auto& file : fe.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_certKeyPairs.at(count).first + "') and key ('" + fe.d_certKeyPairs.at(count).second + "') for TLS context on " + fe.d_addr.toStringWithPort() + ": " + gnutls_strerror(rc));
+ }
+ ++count;
+ }
+
#if GNUTLS_VERSION_NUMBER >= 0x030600
rc = gnutls_certificate_set_known_dh_params(d_creds.get(), GNUTLS_SEC_PARAM_HIGH);
if (rc != GNUTLS_E_SUCCESS) {
{
std::shared_ptr<DOHServerConfig> d_dsc{nullptr};
std::vector<std::pair<std::string, std::string>> d_certKeyPairs;
+ std::vector<std::string> d_ocspFiles;
std::string d_ciphers;
std::string d_ciphers13;
std::string d_serverTokens{"h2o/dnsdist"};
}
std::vector<std::pair<std::string, std::string>> d_certKeyPairs;
+ std::vector<std::string> d_ocspFiles;
ComboAddress d_addr;
std::string d_ciphers;
std::string d_ciphers13;