Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dnsdist: Add support dumping TLS keys via keyLogFile #8442

Merged
merged 2 commits into from
Oct 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion pdns/dnsdist-lua.cc
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ static void parseTLSConfig(TLSConfig& config, const std::string& context, boost:
config.d_ocspFiles.push_back(file.second);
}
}

if (vars->count("keyLogFile")) {
config.d_keyLogFile = boost::get<const string>((*vars)["keyLogFile"]);
}
}

#endif // defined(HAVE_DNS_OVER_TLS) || defined(HAVE_DNS_OVER_HTTPS)
Expand Down Expand Up @@ -1828,7 +1832,7 @@ void setupLuaConfig(bool client)
ret << (fmt % "#" % "Address" % "HTTP" % "HTTP/1" % "HTTP/2" % "GET" % "POST" % "Bad" % "Errors" % "Redirects" % "Valid" % "# ticket keys" % "Rotation delay" % "Next rotation") << endl;
size_t counter = 0;
for (const auto& ctx : g_dohlocals) {
ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_httpconnects % ctx->d_http1Stats.d_nbQueries % ctx->d_http1Stats.d_nbQueries % ctx->d_getqueries % ctx->d_postqueries % ctx->d_badrequests % ctx->d_errorresponses % ctx->d_redirectresponses % ctx->d_validresponses % ctx->getTicketsKeysCount() % ctx->d_tlsConfig.d_ticketsKeyRotationDelay % ctx->getNextTicketsKeyRotation()) << endl;
ret << (fmt % counter % ctx->d_local.toStringWithPort() % ctx->d_httpconnects % ctx->d_http1Stats.d_nbQueries % ctx->d_http1Stats.d_nbQueries % ctx->d_getqueries % ctx->d_postqueries % ctx->d_badrequests % ctx->d_errorresponses % ctx->d_redirectresponses % ctx->d_validresponses % ctx->getTicketsKeysCount() % ctx->getTicketsKeyRotationDelay() % ctx->getNextTicketsKeyRotation()) << endl;
counter++;
}
g_outputBuffer = ret.str();
Expand Down
4 changes: 3 additions & 1 deletion pdns/dnsdistdist/docs/reference/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ Listen Sockets
* ``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. Default is 20480. Setting this value to 0 disables stored session entirely.
* ``preferServerCiphers``: bool - Whether to prefer the order of ciphers set by the server instead of the one set by the client. Default is false, meaning that the order of the client is used.
* ``keyLogFile``: str - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.

.. function:: addTLSLocal(address, certFile(s), keyFile(s) [, options])

Expand All @@ -146,7 +147,7 @@ Listen Sockets
.. versionchanged:: 1.3.3
``numberOfStoredSessions`` option added.
.. versionchanged:: 1.4.0
``ciphersTLS13``, ``minTLSVersion``, ``ocspResponses`` and ``preferServerCiphers`` options added.
``ciphersTLS13``, ``minTLSVersion``, ``ocspResponses``, ``preferServerCiphers``, ``keyLogFile`` options added.

Listen on the specified address and TCP port for incoming DNS over TLS connections, presenting the specified X.509 certificate.

Expand All @@ -173,6 +174,7 @@ Listen Sockets
* ``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.
* ``minTLSVersion``: str - Minimum version of the TLS protocol to support. Possible values are 'tls1.0', 'tls1.1', 'tls1.2' and 'tls1.3'. Default is to require at least TLS 1.0. Note that this value is ignored when the GnuTLS provider is in use, and the ``ciphers`` option should be set accordingly instead. For example, 'NORMAL:!VERS-TLS1.0:!VERS-TLS1.1' will disable TLS 1.0 and 1.1.
* ``preferServerCiphers``: bool - Whether to prefer the order of ciphers set by the server instead of the one set by the client. Default is false, meaning that the order of the client is used.
* ``keyLogFile``: str - Write the TLS keys in the specified file so that an external program can decrypt TLS exchanges, in the format described in https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format.

.. function:: setLocal(address[, options])

Expand Down
224 changes: 146 additions & 78 deletions pdns/dnsdistdist/doh.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class DOHAcceptContext
DOHAcceptContext()
{
memset(&d_h2o_accept_ctx, 0, sizeof(d_h2o_accept_ctx));
d_rotatingTicketsKey.clear();
}
DOHAcceptContext(const DOHAcceptContext&) = delete;
DOHAcceptContext& operator=(const DOHAcceptContext&) = delete;
Expand Down Expand Up @@ -89,12 +90,84 @@ class DOHAcceptContext
}
}

time_t getNextTicketsKeyRotation() const
{
return d_ticketsKeyNextRotation;
}

size_t getTicketsKeysCount() const
{
size_t res = 0;
if (d_ticketKeys) {
res = d_ticketKeys->getKeysCount();
}
return res;
}

void rotateTicketsKey(time_t now)
{
if (!d_ticketKeys) {
return;
}

d_ticketKeys->rotateTicketsKey(now);

if (d_ticketsKeyRotationDelay > 0) {
d_ticketsKeyNextRotation = now + d_ticketsKeyRotationDelay;
}
}

void loadTicketsKeys(const std::string& keyFile)
{
if (!d_ticketKeys) {
return;
}
d_ticketKeys->loadTicketsKeys(keyFile);

if (d_ticketsKeyRotationDelay > 0) {
d_ticketsKeyNextRotation = time(nullptr) + d_ticketsKeyRotationDelay;
}
}

void 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;
}
}
}

std::map<int, std::string> d_ocspResponses;
std::unique_ptr<OpenSSLTLSTicketKeysRing> d_ticketKeys{nullptr};
std::unique_ptr<FILE, int(*)(FILE*)> d_keyLogFile{nullptr, fclose};
ClientState* d_cs{nullptr};
time_t d_ticketsKeyRotationDelay{0};

private:
h2o_accept_ctx_t d_h2o_accept_ctx;
std::atomic<uint64_t> d_refcnt{1};
time_t d_ticketsKeyNextRotation{0};
std::atomic_flag d_rotatingTicketsKey;
};

// we create one of these per thread, and pass around a pointer to it
Expand Down Expand Up @@ -940,131 +1013,124 @@ static int ocsp_stapling_callback(SSL* ssl, void* arg)

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) {
DOHAcceptContext* ctx = reinterpret_cast<DOHAcceptContext*>(libssl_get_ticket_key_callback_data(s));
if (ctx == nullptr || !ctx->d_ticketKeys) {
return -1;
}

df->handleTicketsKeyRotation();
ctx->handleTicketsKeyRotation();

auto ret = libssl_ticket_key_callback(s, *df->d_ticketKeys, keyName, iv, ectx, hctx, enc);
auto ret = libssl_ticket_key_callback(s, *ctx->d_ticketKeys, keyName, iv, ectx, hctx, enc);
if (enc == 0) {
if (ret == 0) {
++df->d_dsc->cs->tlsUnknownTicketKey;
++ctx->d_cs->tlsUnknownTicketKey;
}
else if (ret == 2) {
++df->d_dsc->cs->tlsInactiveTicketKey;
++ctx->d_cs->tlsInactiveTicketKey;
}
}

return ret;
}

static std::unique_ptr<SSL_CTX, void(*)(SSL_CTX*)> getTLSContext(DOHFrontend& df,
std::map<int, std::string>& ocspResponses)
static void setupTLSContext(DOHAcceptContext& acceptCtx,
TLSConfig& tlsConfig,
TLSErrorCounters& counters)
{
try {
if (df.d_tlsConfig.d_ciphers.empty()) {
df.d_tlsConfig.d_ciphers = DOH_DEFAULT_CIPHERS;
}
if (tlsConfig.d_ciphers.empty()) {
tlsConfig.d_ciphers = DOH_DEFAULT_CIPHERS;
}

auto ctx = libssl_init_server_context(df.d_tlsConfig, ocspResponses);
auto ctx = libssl_init_server_context(tlsConfig, acceptCtx.d_ocspResponses);

if (df.d_tlsConfig.d_enableTickets && df.d_tlsConfig.d_numberOfTicketsKeys > 0) {
df.d_ticketKeys = std::unique_ptr<OpenSSLTLSTicketKeysRing>(new OpenSSLTLSTicketKeysRing(df.d_tlsConfig.d_numberOfTicketsKeys));
SSL_CTX_set_tlsext_ticket_key_cb(ctx.get(), &ticket_key_callback);
libssl_set_ticket_key_callback_data(ctx.get(), &df);
}
if (tlsConfig.d_enableTickets && tlsConfig.d_numberOfTicketsKeys > 0) {
acceptCtx.d_ticketKeys = std::unique_ptr<OpenSSLTLSTicketKeysRing>(new OpenSSLTLSTicketKeysRing(tlsConfig.d_numberOfTicketsKeys));
SSL_CTX_set_tlsext_ticket_key_cb(ctx.get(), &ticket_key_callback);
libssl_set_ticket_key_callback_data(ctx.get(), &acceptCtx);
}

if (!ocspResponses.empty()) {
SSL_CTX_set_tlsext_status_cb(ctx.get(), &ocsp_stapling_callback);
SSL_CTX_set_tlsext_status_arg(ctx.get(), &ocspResponses);
}
if (!acceptCtx.d_ocspResponses.empty()) {
SSL_CTX_set_tlsext_status_cb(ctx.get(), &ocsp_stapling_callback);
SSL_CTX_set_tlsext_status_arg(ctx.get(), &acceptCtx.d_ocspResponses);
}

libssl_set_error_counters_callback(ctx, &df.d_tlsCounters);
libssl_set_error_counters_callback(ctx, &counters);

h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols);
if (!tlsConfig.d_keyLogFile.empty()) {
acceptCtx.d_keyLogFile = libssl_set_key_log_file(ctx, tlsConfig.d_keyLogFile);
}

if (df.d_tlsConfig.d_ticketKeyFile.empty()) {
df.handleTicketsKeyRotation();
}
else {
df.loadTicketsKeys(df.d_tlsConfig.d_ticketKeyFile);
}
h2o_ssl_register_alpn_protocols(ctx.get(), h2o_http2_alpn_protocols);

return ctx;
if (tlsConfig.d_ticketKeyFile.empty()) {
acceptCtx.handleTicketsKeyRotation();
}
catch (const std::runtime_error& e) {
throw std::runtime_error("Error setting up TLS context for DoH listener on '" + df.d_local.toStringWithPort() + "': " + e.what());
else {
acceptCtx.loadTicketsKeys(tlsConfig.d_ticketKeyFile);
}

auto nativeCtx = acceptCtx.get();
nativeCtx->ssl_ctx = ctx.release();
acceptCtx.release();
}

static void setupAcceptContext(DOHAcceptContext& ctx, DOHServerConfig& dsc, bool setupTLS)
{
auto nativeCtx = ctx.get();
nativeCtx->ctx = &dsc.h2o_ctx;
nativeCtx->hosts = dsc.h2o_config.hosts;
if (setupTLS && !dsc.df->d_tlsConfig.d_certKeyPairs.empty()) {
auto tlsCtx = getTLSContext(*dsc.df,
ctx.d_ocspResponses);
ctx.d_ticketsKeyRotationDelay = dsc.df->d_tlsConfig.d_ticketsKeyRotationDelay;

nativeCtx->ssl_ctx = tlsCtx.release();
if (setupTLS && !dsc.df->d_tlsConfig.d_certKeyPairs.empty()) {
try {
setupTLSContext(ctx,
dsc.df->d_tlsConfig,
dsc.df->d_tlsCounters);
}
catch (const std::runtime_error& e) {
throw std::runtime_error("Error setting up TLS context for DoH listener on '" + dsc.df->d_local.toStringWithPort() + "': " + e.what());
}
}
ctx.d_cs = dsc.cs;
ctx.release();
}

void DOHFrontend::rotateTicketsKey(time_t now)
{
if (!d_ticketKeys) {
return;
}

d_ticketKeys->rotateTicketsKey(now);

if (d_tlsConfig.d_ticketsKeyRotationDelay > 0) {
d_ticketsKeyNextRotation = now + d_tlsConfig.d_ticketsKeyRotationDelay;
if (d_dsc && d_dsc->accept_ctx) {
d_dsc->accept_ctx->rotateTicketsKey(now);
}
}

void DOHFrontend::loadTicketsKeys(const std::string& keyFile)
{
if (!d_ticketKeys) {
return;
}
d_ticketKeys->loadTicketsKeys(keyFile);

if (d_tlsConfig.d_ticketsKeyRotationDelay > 0) {
d_ticketsKeyNextRotation = time(nullptr) + d_tlsConfig.d_ticketsKeyRotationDelay;
if (d_dsc && d_dsc->accept_ctx) {
d_dsc->accept_ctx->loadTicketsKeys(keyFile);
}
}

void DOHFrontend::handleTicketsKeyRotation()
{
if (d_tlsConfig.d_ticketsKeyRotationDelay == 0) {
return;
if (d_dsc && d_dsc->accept_ctx) {
d_dsc->accept_ctx->handleTicketsKeyRotation();
}
}

time_t now = time(nullptr);
if (now > d_ticketsKeyNextRotation) {
if (d_rotatingTicketsKey.test_and_set()) {
/* someone is already rotating */
return;
}
try {
rotateTicketsKey(now);
time_t DOHFrontend::getNextTicketsKeyRotation() const
{
if (d_dsc && d_dsc->accept_ctx) {
return d_dsc->accept_ctx->getNextTicketsKeyRotation();
}
return 0;
}

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;
}
size_t DOHFrontend::getTicketsKeysCount() const
{
size_t res = 0;
if (d_dsc && d_dsc->accept_ctx) {
res = d_dsc->accept_ctx->getTicketsKeysCount();
}
return res;
}

void DOHFrontend::reloadCertificates()
Expand All @@ -1083,12 +1149,14 @@ void DOHFrontend::setup()
d_dsc = std::make_shared<DOHServerConfig>(d_idleTimeout);

if (!d_tlsConfig.d_certKeyPairs.empty()) {
auto tlsCtx = getTLSContext(*this,
d_dsc->accept_ctx->d_ocspResponses);

auto accept_ctx = d_dsc->accept_ctx->get();
accept_ctx->ssl_ctx = tlsCtx.release();
d_dsc->accept_ctx->release();
try {
setupTLSContext(*d_dsc->accept_ctx,
d_tlsConfig,
d_tlsCounters);
}
catch (const std::runtime_error& e) {
throw std::runtime_error("Error setting up TLS context for DoH listener on '" + d_local.toStringWithPort() + "': " + e.what());
}
}
}

Expand Down
Loading