Skip to content
Snippets Groups Projects
Commit a405b874 authored by Daniel Kahn Gillmor's avatar Daniel Kahn Gillmor Committed by Ondřej Surý
Browse files

Use ephemeral X.509 credentials if none are configured

If kresd is configured to listen using TLS, but it has no credentials,
it should fall back to generating ephemeral credentials and using
them.

It stores the ephemerally-generated secret key in the same directory
as the cache, using the name "ephemeral_key.pem".  If the cache
persists, then the key will too, even if the daemon dies.  This means
that any set of daemons that share a cache will also share an
ephemeral secret key.

The ephemeral X.509 certificate that corresponds to the key will be
automatically generated (self-signed), will have a lifetime of about
90 days (matching Let's Encrypt policy).  The ephemeral cert is
never written to disk; it is always dynamically-generated by kresd.

This should make it very easy to get DNS-over-TLS working in
opportunistic mode.
parent 4c4ff26f
Branches
Tags
1 merge request!112Create and use ephemeral TLS credentials if none are explicitly configured
......@@ -6,6 +6,7 @@ kresd_SOURCES := \
daemon/bindings.c \
daemon/ffimodule.c \
daemon/tls.c \
daemon/tls_ephemeral_credentials.c \
daemon/main.c
kresd_DIST := daemon/lua/kres.lua daemon/lua/kres-gen.lua daemon/lua/trust_anchors.lua
......
......@@ -120,8 +120,13 @@ struct tls_ctx_t *tls_new(struct worker_ctx *worker)
struct network *net = &worker->engine->net;
if (!net->tls_credentials) {
kr_log_error("[tls] x509 credentials are missing; no TLS\n");
return NULL;
net->tls_credentials = tls_get_ephemeral_credentials(worker->engine);
if (!net->tls_credentials) {
kr_log_error("[tls] X.509 credentials are missing, and ephemeral credentials failed; no TLS\n");
return NULL;
}
kr_log_error("[tls] Using ephemeral TLS credentials:\n");
tls_credentials_log_pins(net->tls_credentials);
}
time_t now = time(NULL);
......@@ -487,6 +492,9 @@ void tls_credentials_free(struct tls_credentials *tls_credentials) {
if (tls_credentials->tls_key) {
free(tls_credentials->tls_key);
}
if (tls_credentials->ephemeral_servicename) {
free(tls_credentials->ephemeral_servicename);
}
free(tls_credentials);
}
......
......@@ -31,6 +31,7 @@ struct tls_credentials {
char *tls_key;
gnutls_certificate_credentials_t credentials;
time_t valid_until;
char *ephemeral_servicename;
};
/*! Toggle verbose logging from TLS context. */
......@@ -63,3 +64,6 @@ void tls_credentials_free(struct tls_credentials *tls_credentials);
/*! Log DNS-over-TLS OOB key-pin form of current credentials:
* https://tools.ietf.org/html/rfc7858#appendix-A */
void tls_credentials_log_pins(struct tls_credentials *tls_credentials);
/*! Generate new ephemeral TLS credentials. */
struct tls_credentials * tls_get_ephemeral_credentials(struct engine *engine);
/*
* Copyright (C) 2016 American Civil Liberties Union (ACLU)
*
* Initial Author: Daniel Kahn Gillmor <dkg@fifthhorseman.net>
*
* This program is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <sys/file.h>
#include <unistd.h>
#include <gnutls/gnutls.h>
#include <gnutls/x509.h>
#include <gnutls/crypto.h>
#include "daemon/worker.h"
#include "daemon/tls.h"
#define EPHEMERAL_PRIVKEY_FILENAME "ephemeral_key.pem"
#define INVALID_HOSTNAME "dns-over-tls.invalid"
#define EPHEMERAL_CERT_EXPIRATION_SECONDS 60*60*24*90
/* This is an attempt to grab an exclusive, advisory, non-blocking
* lock based on a filename. At the moment it's POSIX-only, but it
* should be abstract enough of an interface to make an implementation
* for non-posix systems if anyone cares. */
typedef int lock;
static bool _lock_is_invalid(lock lock)
{
return lock == -1;
}
/* a blocking lock on a given filename */
static lock _lock_filename(const char *fname)
{
lock lockfd = open(fname, O_RDONLY|O_CREAT, 0400);
if (lockfd == -1)
return lockfd;
/* this should be a non-blocking lock */
if (flock(lockfd, LOCK_EX | LOCK_NB) != 0) {
close(lockfd);
return -1;
}
return lockfd; /* for cleanup later */
}
static void _lock_unlock(lock *lock, const char *fname)
{
if (lock && !_lock_is_invalid(*lock)) {
flock(*lock, LOCK_UN);
close(*lock);
*lock = -1;
unlink(fname); /* ignore errors */
}
}
static gnutls_x509_privkey_t get_ephemeral_privkey ()
{
gnutls_x509_privkey_t privkey = NULL;
int err;
gnutls_datum_t data = { .data = NULL, .size = 0 };
lock lock;
int datafd = -1;
/* Take a lock to ensure that two daemons started concurrently
* with a shared cache don't both create the same privkey: */
lock = _lock_filename(EPHEMERAL_PRIVKEY_FILENAME ".lock");
if (_lock_is_invalid(lock)) {
kr_log_error("[tls] unable to lock lockfile " EPHEMERAL_PRIVKEY_FILENAME ".lock\n");
goto done;
}
if ((err = gnutls_x509_privkey_init (&privkey)) < 0) {
kr_log_error("[tls] gnutls_x509_privkey_init() failed: %d (%s)\n",
err, gnutls_strerror_name(err));
goto done;
}
/* read from cache file (we assume that we've chdir'ed
* already, so we're just looking for the file in the
* cachedir. */
datafd = open(EPHEMERAL_PRIVKEY_FILENAME, O_RDONLY);
if (datafd != -1) {
struct stat stat;
ssize_t bytes_read;
if (fstat(datafd, &stat)) {
kr_log_error("[tls] unable to stat ephemeral private key " EPHEMERAL_PRIVKEY_FILENAME "\n");
goto bad_data;
}
data.data = gnutls_malloc(stat.st_size);
if (data.data == NULL) {
kr_log_error("[tls] unable to allocate memory for reading ephemeral private key\n");
goto bad_data;
}
data.size = stat.st_size;
bytes_read = read(datafd, data.data, stat.st_size);
if (bytes_read != stat.st_size) {
kr_log_error("[tls] unable to read ephemeral private key\n");
goto bad_data;
}
if ((err = gnutls_x509_privkey_import (privkey, &data, GNUTLS_X509_FMT_PEM)) < 0) {
kr_log_error("[tls] gnutls_x509_privkey_import() failed: %d (%s)\n",
err, gnutls_strerror_name(err));
goto bad_data;
}
if (0) {
bad_data:
close(datafd);
datafd = -1;
gnutls_free(data.data);
data.data = NULL;
}
}
if (datafd == -1) {
/* if loading failed, then generate ... */
if ((err = gnutls_x509_privkey_generate(privkey, GNUTLS_PK_ECDSA, GNUTLS_CURVE_TO_BITS(GNUTLS_ECC_CURVE_SECP256R1), 0)) < 0) {
kr_log_error("[tls] gnutls_x509_privkey_init() failed: %d (%s)\n",
err, gnutls_strerror_name(err));
gnutls_x509_privkey_deinit(privkey);
goto done;
}
/* ... and save */
kr_log_info("[tls] Stashing ephemeral private key in " EPHEMERAL_PRIVKEY_FILENAME "\n");
if ((err = gnutls_x509_privkey_export2(privkey, GNUTLS_X509_FMT_PEM, &data)) < 0) {
kr_log_error("[tls] gnutls_x509_privkey_export2() failed: %d (%s), not storing\n",
err, gnutls_strerror_name(err));
} else {
datafd = open(EPHEMERAL_PRIVKEY_FILENAME, O_WRONLY|O_CREAT, 0600);
if (datafd == -1) {
kr_log_error("[tls] failed to open " EPHEMERAL_PRIVKEY_FILENAME " to store the ephemeral key\n");
} else {
ssize_t bytes_written;
bytes_written = write(datafd, data.data, data.size);
if (bytes_written != data.size)
kr_log_error("[tls] failed to write %d octets to " EPHEMERAL_PRIVKEY_FILENAME " (%ld written)\n",
data.size, bytes_written);
}
}
}
done:
_lock_unlock(&lock, EPHEMERAL_PRIVKEY_FILENAME ".lock");
if (datafd != -1)
close(datafd);
return privkey;
}
static gnutls_x509_crt_t get_ephemeral_cert(gnutls_x509_privkey_t privkey, const char *servicename, time_t invalid_before, time_t valid_until)
{
gnutls_x509_crt_t cert = NULL;
int err;
/* need a random buffer of bytes */
uint8_t serial[16];
gnutls_rnd(GNUTLS_RND_NONCE, serial, sizeof(serial));
/* clear the left-most bit to avoid signedness confusion: */
serial[0] &= 0x8f;
size_t namelen = strlen(servicename);
#define gtx(fn, ...) \
if ((err = fn ( __VA_ARGS__ )) != GNUTLS_E_SUCCESS) { \
kr_log_error("[tls] " #fn "() failed: %d (%s)\n", \
err, gnutls_strerror_name(err)); \
goto bad; }
gtx(gnutls_x509_crt_init, &cert);
gtx(gnutls_x509_crt_set_activation_time, cert, invalid_before);
gtx(gnutls_x509_crt_set_ca_status, cert, 0);
gtx(gnutls_x509_crt_set_expiration_time, cert, valid_until);
gtx(gnutls_x509_crt_set_key, cert, privkey);
gtx(gnutls_x509_crt_set_key_purpose_oid, cert, GNUTLS_KP_TLS_WWW_CLIENT, 0);
gtx(gnutls_x509_crt_set_key_purpose_oid, cert, GNUTLS_KP_TLS_WWW_SERVER, 0);
gtx(gnutls_x509_crt_set_key_usage, cert, GNUTLS_KEY_DIGITAL_SIGNATURE);
gtx(gnutls_x509_crt_set_serial, cert, serial, sizeof(serial));
gtx(gnutls_x509_crt_set_subject_alt_name, cert, GNUTLS_SAN_DNSNAME, servicename, namelen, GNUTLS_FSAN_SET);
gtx(gnutls_x509_crt_set_dn_by_oid,cert, GNUTLS_OID_X520_COMMON_NAME, 0, servicename, namelen);
gtx(gnutls_x509_crt_set_version, cert, 3);
gtx(gnutls_x509_crt_sign2,cert, cert, privkey, GNUTLS_DIG_SHA256, 0); /* self-sign, since it doesn't look like we can just stub-sign */
#undef gtx
return cert;
bad:
gnutls_x509_crt_deinit(cert);
return NULL;
}
struct tls_credentials * tls_get_ephemeral_credentials(struct engine *engine)
{
struct tls_credentials *creds = NULL;
gnutls_x509_privkey_t privkey = NULL;
gnutls_x509_crt_t cert = NULL;
int err;
time_t now = time(NULL);
creds = calloc(1, sizeof(*creds));
if (!creds) {
kr_log_error("[tls] failed to allocate memory for ephemeral credentials\n");
return NULL;
}
if ((err = gnutls_certificate_allocate_credentials(&(creds->credentials))) < 0) {
kr_log_error("[tls] failed to allocate memory for ephemeral credentials\n");
goto failure;
}
creds->valid_until = now + EPHEMERAL_CERT_EXPIRATION_SECONDS;
creds->ephemeral_servicename = strdup(engine_get_hostname(engine));
if (creds->ephemeral_servicename == NULL) {
kr_log_error("[tls] could not get server's hostname, using '" INVALID_HOSTNAME "' instead\n");
creds->ephemeral_servicename = strdup(INVALID_HOSTNAME);
}
privkey = get_ephemeral_privkey();
if (!privkey)
goto failure;
cert = get_ephemeral_cert(privkey, creds->ephemeral_servicename, now - 60*15, creds->valid_until);
if (!cert)
goto failure;
if ((err = gnutls_certificate_set_x509_key(creds->credentials, &cert, 1, privkey)) < 0) {
kr_log_error("[tls] failed to set up ephemeral credentials\n");
goto failure;
}
gnutls_x509_privkey_deinit(privkey);
gnutls_x509_crt_deinit(cert);
return creds;
failure:
gnutls_x509_privkey_deinit(privkey);
gnutls_x509_crt_deinit(cert);
tls_credentials_free(creds);
return NULL;
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment