Skip to content
Snippets Groups Projects
Commit c5ad975f authored by Ondřej Surý's avatar Ondřej Surý
Browse files

Merge branch 'ephemeral-tls-2' into 'master'

Create and use ephemeral TLS credentials if none are explicitly configured

See merge request !112
parents 7a9bb19a 15a57ea3
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
......
......@@ -174,6 +174,7 @@ int engine_set_hostname(struct engine *engine, const char *hostname) {
free(engine->hostname);
}
engine->hostname = new_hostname;
network_new_hostname(&engine->net, engine);
return 0;
}
......
......@@ -345,3 +345,19 @@ int network_close(struct network *net, const char *addr, uint16_t port)
return kr_ok();
}
void network_new_hostname(struct network *net, struct engine *engine)
{
if (net->tls_credentials &&
net->tls_credentials->ephemeral_servicename) {
struct tls_credentials *newcreds;
newcreds = tls_get_ephemeral_credentials(engine);
if (newcreds) {
tls_credentials_release(net->tls_credentials);
net->tls_credentials = newcreds;
kr_log_info("[tls] Updated ephemeral X.509 cert with new hostname\n");
} else {
kr_log_error("[tls] Failed to update ephemeral X.509 cert with new hostname, using existing one\n");
}
}
}
......@@ -22,6 +22,8 @@
#include "lib/generic/array.h"
#include "lib/generic/map.h"
struct engine;
enum endpoint_flag {
NET_DOWN = 0 << 0,
NET_UDP = 1 << 0,
......@@ -53,3 +55,4 @@ int network_listen(struct network *net, const char *addr, uint16_t port, uint32_
int network_close(struct network *net, const char *addr, uint16_t port);
int network_set_tls_cert(struct network *net, const char *cert);
int network_set_tls_key(struct network *net, const char *key);
void network_new_hostname(struct network *net, struct engine *engine);
......@@ -35,6 +35,8 @@
#include "daemon/tls.h"
#include "daemon/io.h"
#define EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE 60*60*24*7
static const char *priorities = "NORMAL";
/* gnutls_record_recv and gnutls_record_send */
......@@ -120,8 +122,36 @@ 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_info("[tls] Using ephemeral TLS credentials:\n");
tls_credentials_log_pins(net->tls_credentials);
}
time_t now = time(NULL);
if (net->tls_credentials->valid_until != GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION) {
if (net->tls_credentials->ephemeral_servicename) {
/* ephemeral cert: refresh if due to expire within a week */
if (now >= net->tls_credentials->valid_until - EPHEMERAL_CERT_EXPIRATION_SECONDS_RENEW_BEFORE) {
struct tls_credentials *newcreds = tls_get_ephemeral_credentials(worker->engine);
if (newcreds) {
tls_credentials_release(net->tls_credentials);
net->tls_credentials = newcreds;
kr_log_info("[tls] Renewed expiring ephemeral X.509 cert\n");
} else {
kr_log_error("[tls] Failed to renew expiring ephemeral X.509 cert, using existing one\n");
}
}
} else {
/* non-ephemeral cert: warn once when certificate expires */
if (now >= net->tls_credentials->valid_until) {
kr_log_error("[tls] X.509 certificate has expired!\n");
net->tls_credentials->valid_until = GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION;
}
}
}
struct tls_ctx_t *tls = calloc(1, sizeof(struct tls_ctx_t));
......@@ -318,7 +348,7 @@ void tls_credentials_log_pins(struct tls_credentials *tls_credentials)
if ((err = gnutls_certificate_get_x509_crt(tls_credentials->credentials, index, &certs, &cert_count)) != GNUTLS_E_SUCCESS) {
if (err != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
kr_log_error("[tls] could not get x509 certificates (%d) %s\n", err, gnutls_strerror_name(err));
kr_log_error("[tls] could not get X.509 certificates (%d) %s\n", err, gnutls_strerror_name(err));
}
return;
}
......@@ -354,6 +384,37 @@ static int str_replace(char **where_ptr, const char *with)
return kr_ok();
}
static time_t _get_end_entity_expiration(gnutls_certificate_credentials_t creds)
{
gnutls_datum_t data;
gnutls_x509_crt_t cert = NULL;
int err;
time_t ret = GNUTLS_X509_NO_WELL_DEFINED_EXPIRATION;
if ((err = gnutls_certificate_get_crt_raw(creds, 0, 0, &data)) < 0) {
kr_log_error("[tls] failed to get cert to check expiration: (%d) %s\n",
err, gnutls_strerror_name(err));
goto done;
}
if ((err = gnutls_x509_crt_init(&cert)) < 0) {
kr_log_error("[tls] failed to initialize cert: (%d) %s\n",
err, gnutls_strerror_name(err));
goto done;
}
if ((err = gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_DER)) < 0) {
kr_log_error("[tls] failed to construct cert while checking expiration: (%d) %s\n",
err, gnutls_strerror_name(err));
goto done;
}
ret = gnutls_x509_crt_get_expiration_time (cert);
done:
/* do not free data; g_c_get_crt_raw() says to treat it as
* constant. */
gnutls_x509_crt_deinit(cert);
return ret;
}
int tls_certificate_set(struct network *net, const char *tls_cert, const char *tls_key)
{
if (!net) {
......@@ -394,10 +455,13 @@ int tls_certificate_set(struct network *net, const char *tls_cert, const char *t
tls_cert, tls_key, err, gnutls_strerror_name(err));
return kr_error(EINVAL);
}
// Exchange the x509 credentials
/* record the expiration date: */
tls_credentials->valid_until = _get_end_entity_expiration(tls_credentials->credentials);
/* Exchange the x509 credentials */
struct tls_credentials *old_credentials = net->tls_credentials;
// Start using the new x509_credentials
/* Start using the new x509_credentials */
net->tls_credentials = tls_credentials;
tls_credentials_log_pins(net->tls_credentials);
......@@ -445,6 +509,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);
}
......
......@@ -30,6 +30,8 @@ struct tls_credentials {
char *tls_cert;
char *tls_key;
gnutls_certificate_credentials_t credentials;
time_t valid_until;
char *ephemeral_servicename;
};
/*! Toggle verbose logging from TLS context. */
......@@ -62,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; */
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