test_dnsdist(){
run "cd regression-tests.dnsdist"
- run "DNSDISTBIN=$HOME/dnsdist/bin/dnsdist ./runtests -v --ignore-files='(?:^\.|^_,|^setup\.py$|^test_DOH\.py$)'"
+ run "DNSDISTBIN=$HOME/dnsdist/bin/dnsdist ./runtests -v --ignore-files='(?:^\.|^_,|^setup\.py$|^test_DOH\.py$|^test_OCSP\.py$)'"
run "rm -f ./DNSCryptResolver.cert ./DNSCryptResolver.key"
run "cd .."
}
#include "protobuf.hh"
#include "sodcrypto.hh"
+#ifdef HAVE_LIBSSL
+#include "libssl.hh"
+#endif
+
#include <boost/logic/tribool.hpp>
#include <boost/lexical_cast.hpp>
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::vector<std::pair<int, std::string> >, std::map<std::string,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 {
});
g_lua.writeFunction("setAllowEmptyResponse", [](bool allow) { g_allowEmptyResponse=allow; });
+
+#if defined(HAVE_LIBSSL) && defined(HAVE_OCSP_BASIC_SIGN)
+ g_lua.writeFunction("generateOCSPResponse", [](const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin) {
+ return libssl_generate_ocsp_response(certFile, caCert, caKey, outFile, ndays, nmin);
+ });
+#endif /* HAVE_LIBSSL && HAVE_OCSP_BASIC_SIGN*/
}
vector<std::function<void(void)>> setupLua(bool client, const std::string& config)
struct DNSDistStats g_stats;
MetricDefinitionStorage g_metricDefinitions;
-uint16_t g_maxOutstanding{10240};
+uint16_t g_maxOutstanding{std::numeric_limits<uint16_t>::max()};
bool g_verboseHealthChecks{false};
uint32_t g_staleCacheEntriesTTL{0};
bool g_syslog{true};
The experimental :func:`setTCPUseSinglePipe` directive can be used so that all the incoming TCP connections are put into a single queue and handled by the first TCP worker available.
When dispatching UDP queries to backend servers, dnsdist keeps track of at most **n** outstanding queries for each backend.
-This number **n** can be tuned by the :func:`setMaxUDPOutstanding` directive, defaulting to 10240, with a maximum value of 65535.
+This number **n** can be tuned by the :func:`setMaxUDPOutstanding` directive, defaulting to 10240 (65535 since 1.4.0), with a maximum value of 65535.
Large installations are advised to increase the default value at the cost of a slightly increased memory usage.
Most of the query processing is done in C++ for maximum performance, but some operations are executed in Lua for maximum flexibility:
* ``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])
:param int num:
.. function:: setMaxUDPOutstanding(num)
+ .. versionchanged:: 1.4.0
+ Before 1.4.0 the default value was 10240
- Set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 10240
+ Set the maximum number of outstanding UDP queries to a given backend server. This can only be set at configuration time and defaults to 65535 (10240 before 1.4.0)
:param int num:
}
}
+ 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 */
+ auto keyType = libssl_get_last_key_type(ctx);
+ if (keyType < 0) {
+ throw std::runtime_error("Failed to setup SSL/TLS for DoH listener, the key from '" + pair.second + "' has an unknown type");
+ }
+ keyTypes.push_back(keyType);
+ }
+
+ 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);
+}
+
+#ifdef HAVE_OCSP_BASIC_SIGN
+bool libssl_generate_ocsp_response(const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin)
+{
+ const EVP_MD* rmd = EVP_sha256();
+
+ auto fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(certFile.c_str(), "r"), fclose);
+ if (!fp) {
+ throw std::runtime_error("Unable to open '" + certFile + "' when loading the certificate to generate an OCSP response");
+ }
+ auto cert = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
+
+ fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(caCert.c_str(), "r"), fclose);
+ if (!fp) {
+ throw std::runtime_error("Unable to open '" + caCert + "' when loading the issuer certificate to generate an OCSP response");
+ }
+ auto issuer = std::unique_ptr<X509, void(*)(X509*)>(PEM_read_X509_AUX(fp.get(), nullptr, nullptr, nullptr), X509_free);
+ fp = std::unique_ptr<FILE, int(*)(FILE*)>(fopen(caKey.c_str(), "r"), fclose);
+ if (!fp) {
+ throw std::runtime_error("Unable to open '" + caKey + "' when loading the issuer key to generate an OCSP response");
+ }
+ auto issuerKey = std::unique_ptr<EVP_PKEY, void(*)(EVP_PKEY*)>(PEM_read_PrivateKey(fp.get(), nullptr, nullptr, nullptr), EVP_PKEY_free);
+ fp.reset();
+
+ auto bs = std::unique_ptr<OCSP_BASICRESP, void(*)(OCSP_BASICRESP*)>(OCSP_BASICRESP_new(), OCSP_BASICRESP_free);
+ auto thisupd = std::unique_ptr<ASN1_TIME, void(*)(ASN1_TIME*)>(X509_gmtime_adj(nullptr, 0), ASN1_TIME_free);
+ auto nextupd = std::unique_ptr<ASN1_TIME, void(*)(ASN1_TIME*)>(X509_time_adj_ex(nullptr, ndays, nmin * 60, nullptr), ASN1_TIME_free);
+
+ auto cid = std::unique_ptr<OCSP_CERTID, void(*)(OCSP_CERTID*)>(OCSP_cert_to_id(rmd, cert.get(), issuer.get()), OCSP_CERTID_free);
+ OCSP_basic_add1_status(bs.get(), cid.get(), V_OCSP_CERTSTATUS_GOOD, 0, nullptr, thisupd.get(), nextupd.get());
+
+ if (OCSP_basic_sign(bs.get(), issuer.get(), issuerKey.get(), rmd, nullptr, OCSP_NOCERTS) != 1) {
+ throw std::runtime_error("Error while signing the OCSP response");
+ }
+
+ auto resp = std::unique_ptr<OCSP_RESPONSE, void(*)(OCSP_RESPONSE*)>(OCSP_response_create(OCSP_RESPONSE_STATUS_SUCCESSFUL, bs.get()), OCSP_RESPONSE_free);
+ auto bio = std::unique_ptr<BIO, void(*)(BIO*)>(BIO_new_file(outFile.c_str(), "wb"), BIO_vfree);
+ if (!bio) {
+ throw std::runtime_error("Error opening file for writing the OCSP response");
+ }
+
+ // i2d_OCSP_RESPONSE_bio(bio.get(), resp.get()) is unusable from C++ because of an invalid cast
+ ASN1_i2d_bio((i2d_of_void*)i2d_OCSP_RESPONSE, bio.get(), (unsigned char*)resp.get());
+
+ return true;
+}
+#endif /* HAVE_OCSP_BASIC_SIGN */
+
#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);
+
+#ifdef HAVE_OCSP_BASIC_SIGN
+bool libssl_generate_ocsp_response(const std::string& certFile, const std::string& caCert, const std::string& caKey, const std::string& outFile, int ndays, int nmin);
+#endif
+
+#endif /* HAVE_LIBSSL */
save_CFLAGS=$CFLAGS
save_LIBS=$LIBS
CFLAGS="$LIBSSL_CFLAGS $CFLAGS"
- LIBS="$LIBSSL_LIBS $LIBS"
- AC_CHECK_FUNCS([SSL_CTX_set_ciphersuites])
+ LIBS="$LIBSSL_LIBS -lcrypto $LIBS"
+ AC_CHECK_FUNCS([SSL_CTX_set_ciphersuites OCSP_basic_sign])
CFLAGS=$save_CFLAGS
LIBS=$save_LIBS
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 */
+ auto keyType = libssl_get_last_key_type(d_tlsCtx);
+ if (keyType < 0) {
+ throw std::runtime_error("Key from '" + pair.second + "' has an unknown type");
+ }
+ keyTypes.push_back(keyType);
+ }
+
+ 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;
/server.csr
/server.key
/server.pem
+/server.ocsp
/configs
\ No newline at end of file
set -x
fi
-rm -f ca.key ca.pem ca.srl server.csr server.key server.pem server.chain
+rm -f ca.key ca.pem ca.srl server.csr server.key server.pem server.chain server.ocsp
rm -rf configs/*
# Generate a new CA
false
fi
-rm ca.key ca.pem ca.srl server.csr server.key server.pem server.chain
+rm -f ca.key ca.pem ca.srl server.csr server.key server.pem server.chain server.ocsp
--- /dev/null
+#!/usr/bin/env python
+import dns
+import subprocess
+from dnsdisttests import DNSDistTest
+
+class DNSDistOCSPStaplingTest(DNSDistTest):
+
+ @classmethod
+ def checkOCSPStaplingStatus(cls, addr, port, serverName, caFile):
+ testcmd = ['openssl', 's_client', '-CAfile', caFile, '-connect', '%s:%d' % (addr, port), '-status', '-servername', serverName ]
+ output = None
+ try:
+ process = subprocess.Popen(testcmd, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True)
+ output = process.communicate(input='')
+ except subprocess.CalledProcessError as exc:
+ raise AssertionError('dnsdist --check-config failed (%d): %s' % (exc.returncode, exc.output))
+
+ return output[0].decode()
+
+class TestOCSPStaplingDOH(DNSDistOCSPStaplingTest):
+
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _ocspFile = 'server.ocsp'
+ _caCert = 'ca.pem'
+ _caKey = 'ca.key'
+ _dohServerPort = 8443
+ _config_template = """
+ newServer{address="127.0.0.1:%s"}
+
+ -- generate an OCSP response file for our certificate, valid one day
+ generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)
+ addDOHLocal("127.0.0.1:%s", "%s", "%s", { "/" }, { ocspResponses={"%s"}})
+ """
+ _config_params = ['_testServerPort', '_serverCert', '_caCert', '_caKey', '_ocspFile', '_dohServerPort', '_serverCert', '_serverKey', '_ocspFile']
+
+ def testOCSPStapling(self):
+ """
+ OCSP Stapling: DOH
+ """
+ output = self.checkOCSPStaplingStatus('127.0.0.1', self._dohServerPort, self._serverName, self._caCert)
+ self.assertIn('OCSP Response Status: successful (0x0)', output)
+
+class TestOCSPStaplingTLSGnuTLS(DNSDistOCSPStaplingTest):
+
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _ocspFile = 'server.ocsp'
+ _caCert = 'ca.pem'
+ _caKey = 'ca.key'
+ _tlsServerPort = 8443
+ _config_template = """
+ newServer{address="127.0.0.1:%s"}
+
+ -- generate an OCSP response file for our certificate, valid one day
+ generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)
+ addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="gnutls", ocspResponses={"%s"}})
+ """
+ _config_params = ['_testServerPort', '_serverCert', '_caCert', '_caKey', '_ocspFile', '_tlsServerPort', '_serverCert', '_serverKey', '_ocspFile']
+
+ def testOCSPStapling(self):
+ """
+ OCSP Stapling: TLS (GnuTLS)
+ """
+ output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
+ self.assertIn('OCSP Response Status: successful (0x0)', output)
+
+class TestOCSPStaplingTLSOpenSSL(DNSDistOCSPStaplingTest):
+
+ _serverKey = 'server.key'
+ _serverCert = 'server.chain'
+ _serverName = 'tls.tests.dnsdist.org'
+ _ocspFile = 'server.ocsp'
+ _caCert = 'ca.pem'
+ _caKey = 'ca.key'
+ _tlsServerPort = 8443
+ _config_template = """
+ newServer{address="127.0.0.1:%s"}
+
+ -- generate an OCSP response file for our certificate, valid one day
+ generateOCSPResponse('%s', '%s', '%s', '%s', 1, 0)
+ addTLSLocal("127.0.0.1:%s", "%s", "%s", { provider="openssl", ocspResponses={"%s"}})
+ """
+ _config_params = ['_testServerPort', '_serverCert', '_caCert', '_caKey', '_ocspFile', '_tlsServerPort', '_serverCert', '_serverKey', '_ocspFile']
+
+ def testOCSPStapling(self):
+ """
+ OCSP Stapling: TLS (OpenSSL)
+ """
+ output = self.checkOCSPStaplingStatus('127.0.0.1', self._tlsServerPort, self._serverName, self._caCert)
+ self.assertIn('OCSP Response Status: successful (0x0)', output)