]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
daemon/tls: session resumption with tickets (client & server side)
authorGrigorii Demidov <grigorii.demidov@nic.cz>
Mon, 21 May 2018 15:55:35 +0000 (17:55 +0200)
committerPetr Špaček <petr.spacek@nic.cz>
Wed, 13 Jun 2018 13:47:00 +0000 (15:47 +0200)
daemon/bindings.c
daemon/network.c
daemon/network.h
daemon/tls.c
daemon/tls.h
daemon/worker.c

index 570b9ed9187c729520e3468452cc11fd2d6eb788..dee24804af2c3edd8e23a76ffff93e36fd15973d 100644 (file)
@@ -642,6 +642,48 @@ static int net_tls_padding(lua_State *L)
        return 1;
 }
 
+/* Configure client-side TLS session ticket key generation.
+ *
+ * note  Don't call from CLI when there are forked kresd instances as it
+ *       will break synchronous ticket key regeneration.
+ *
+ * Expected parameters from lua
+ * salt  salt string used for session ticket key generation.
+ *       It's guaranteed that all forked kresd instances
+ *       with same salt string will always use the same session ticket key
+ *       without additional synchronization.
+ *       If salt string is empty, kresd won't use session tickets at server side
+ *       and therefore won't support session resumption.
+ */
+
+static int net_tls_sticket_key_salt_string(lua_State *L)
+{
+
+       if (lua_gettop(L) != 1 || !lua_isstring(L, 1)) {
+               lua_pushstring(L, "net.tls_client_sticket takes one parameter: (\"salt string\")");
+               lua_error(L);
+       }
+
+       struct engine *engine = engine_luaget(L);
+       struct network *net = &engine->net;
+       const char *salt = lua_tostring(L, 1);
+       size_t salt_len = strlen(salt);
+       if (net->tls_session_ticket_ctx != NULL) {
+               tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx);
+               net->tls_session_ticket_ctx = NULL;
+       }
+       if (salt_len) {
+               net->tls_session_ticket_ctx = tls_session_ticket_ctx_create(net->loop, salt, salt_len);
+               if (net->tls_session_ticket_ctx == NULL) {
+                       lua_pushstring(L, "net.tls_client_sticket - can't create session ticket context");
+                       lua_error(L);
+               }
+       }
+
+       lua_pushboolean(L, true);
+       return 1;
+}
+
 static int net_outgoing(lua_State *L, int family)
 {
        struct worker_ctx *worker = wrk_luaget(L);
@@ -712,6 +754,7 @@ int lib_net(lua_State *L)
                { "tls_server",   net_tls },
                { "tls_client",   net_tls_client },
                { "tls_padding",  net_tls_padding },
+               { "tls_sticket_salt_string", net_tls_sticket_key_salt_string },
                { "outgoing_v4",  net_outgoing_v4 },
                { "outgoing_v6",  net_outgoing_v6 },
                { NULL, NULL }
index 4a047ab664cd89325ef1b7549e4655f1ce923e68..70c4781221edf2882984f6e03e7aead75d2e6768 100644 (file)
@@ -52,6 +52,7 @@ void network_init(struct network *net, uv_loop_t *loop)
                net->loop = loop;
                net->endpoints = map_make(NULL);
                net->tls_client_params = map_make(NULL);
+               net->tls_session_ticket_ctx = NULL;
        }
 }
 
@@ -109,6 +110,9 @@ void network_deinit(struct network *net)
                tls_credentials_free(net->tls_credentials);
                tls_client_params_free(&net->tls_client_params);
                net->tls_credentials = NULL;
+               if (net->tls_session_ticket_ctx != NULL) {
+                       tls_session_ticket_ctx_destroy(net->tls_session_ticket_ctx);
+               }
        }
 }
 
index c562b6413856fbae66fb45992be5c873d51f4f9c..ca50c204f97d0c027a25e01c59723bb7886ea4ee 100644 (file)
@@ -47,6 +47,7 @@ struct network {
        map_t endpoints;
        struct tls_credentials *tls_credentials;
        map_t tls_client_params;
+       struct tls_session_ticket_ctx *tls_session_ticket_ctx;
 };
 
 void network_init(struct network *net, uv_loop_t *loop);
index 3e15622119d738f1b5b09a7f43a5cd5b6022580e..debd0aa6b5237ac863447c6e3dfaf5486b0b9209 100644 (file)
 #define DEBUG_MSG(fmt...)
 #endif
 
+#if GNUTLS_VERSION_NUMBER >= 0x030400
+#define tls_memset gnutls_memset
+#else
+#define tls_memset memset
+#endif
+
+#if GNUTLS_VERSION_NUMBER >= 0x030407
+#define SESSION_TICKET_KEYGEN_HASH GNUTLS_DIG_SHA3_512
+#else
+#define SESSION_TICKET_KEYGEN_HASH GNUTLS_DIG_SHA512
+#endif
+
+#define TLS_SESSION_TICKET_KEY_REGENERATION_INTERVAL 3600000
+
 static char const server_logstring[] = "tls";
 static char const client_logstring[] = "tls_client";
 
 static int client_verify_certificate(gnutls_session_t tls_session);
 
+/* FIXME: review session_ticket_key* again before merge! */
+/** Value from gnutls:lib/ext/session_ticket.c
+ * Beware: changing this needs to change the hashing implementation. */
+#define SESSION_KEY_SIZE 64
+
+/** Fields are internal to session_ticket_key_* functions. */
+struct session_ticket_key {
+       char key[SESSION_KEY_SIZE];
+       uint16_t hash_len;
+       char hash_data[];
+};
+
+struct tls_session_ticket_ctx {
+       size_t epoch;
+       uv_timer_t key_timer;
+       struct session_ticket_key *key;
+};
+
+/** Check invariants, based on gnutls version. */
+static bool session_ticket_key_invariants(void)
+{
+       static int result = 0;
+       if (result) return result > 0;
+       bool ok = true;
+       /* SHA3-512 output size may never change, but let's check it anyway :-) */
+       ok = ok && gnutls_hash_get_len(SESSION_TICKET_KEYGEN_HASH) == SESSION_KEY_SIZE;
+       /* The ticket key size might change in a different gnutls version. */
+       gnutls_datum_t key = { 0, 0 };
+       ok = ok && gnutls_session_ticket_key_generate(&key) == 0
+               && key.size == SESSION_KEY_SIZE;
+       free(key.data);
+       result = ok ? 1 : -1;
+       return ok;
+}
+
+/** Create the internal structures and copy the salt. Beware: salt must be kept secure. */
+static struct session_ticket_key * session_ticket_key_create(const char *salt, size_t salt_len)
+{
+       const size_t hash_len = sizeof(size_t) + salt_len;
+       if (!salt || !salt_len || hash_len > UINT16_MAX || hash_len < salt_len) {
+               assert(!EINVAL);
+               return NULL;
+               /* reasonable salt_len is best enforced in config API */
+       }
+       if (!session_ticket_key_invariants()) {
+               assert(!EFAULT);
+               return NULL;
+       }
+       struct session_ticket_key *key =
+               malloc(offsetof(struct session_ticket_key, hash_data) + hash_len);
+       if (!key) return NULL;
+       key->hash_len = hash_len;
+       memcpy(key->hash_data + sizeof(size_t), salt, salt_len);
+       return key;
+}
+
+/** Recompute the session ticket key, deterministically from epoch and salt. */
+static int session_ticket_key_recompute(struct session_ticket_key *key, size_t epoch)
+{
+       if (!key || key->hash_len <= sizeof(size_t)) {
+               assert(!EINVAL);
+               return kr_error(EINVAL);
+       }
+       memcpy(key->hash_data, &epoch, sizeof(size_t));
+               /* TODO: ^^ support mixing endians? */
+       int ret = gnutls_hash_fast(SESSION_TICKET_KEYGEN_HASH, key->hash_data,
+                                  key->hash_len, key->key);
+       return ret == 0 ? kr_ok() : kr_error(ret);
+}
+
+/** Return reference to a key in the format suitable for gnutls. */
+static inline gnutls_datum_t session_ticket_key_get(struct session_ticket_key *key)
+{
+       assert(key);
+       return (gnutls_datum_t){
+               .size = SESSION_KEY_SIZE,
+               .data = (unsigned char *)key,
+       };
+}
+
+/** Free all resources of the key (securely). */
+static void session_ticket_key_destroy(struct session_ticket_key *key)
+{
+       assert(key);
+       tls_memset(key, 0, offsetof(struct session_ticket_key, hash_data)
+                               + key->hash_len);
+       free(key);
+}
+
+
 /**
  * Set mandatory security settings from
  * https://tools.ietf.org/html/draft-ietf-dprive-dtls-and-tls-profiles-11#section-9
@@ -160,6 +264,13 @@ struct tls_ctx_t *tls_new(struct worker_ctx *worker)
        gnutls_transport_set_pull_function(tls->c.tls_session, kres_gnutls_pull);
        gnutls_transport_set_push_function(tls->c.tls_session, worker_gnutls_push);
        gnutls_transport_set_ptr(tls->c.tls_session, tls);
+
+       if (net->tls_session_ticket_ctx != NULL) {
+               assert(net->tls_session_ticket_ctx->key);
+               gnutls_datum_t session_ticket_key = session_ticket_key_get(net->tls_session_ticket_ctx->key);
+               gnutls_session_ticket_enable_server(tls->c.tls_session, &session_ticket_key);
+       }
+
        return tls;
 }
 
@@ -578,6 +689,10 @@ static int client_paramlist_entry_clear(const char *k, void *v, void *baton)
                gnutls_certificate_free_credentials(entry->credentials);
        }
 
+       if (entry->session_data.data) {
+               gnutls_free(entry->session_data.data);
+       }
+
        free(entry);
 
        return 0;
@@ -927,6 +1042,12 @@ int tls_client_connect_start(struct tls_client_ctx_t *client_ctx,
        ctx->handshake_state = TLS_HS_IN_PROGRESS;
        ctx->session = session;
 
+       struct tls_client_paramlist_entry *tls_params = client_ctx->params;
+       if (tls_params->session_data.data != NULL) {
+               gnutls_session_set_data(ctx->tls_session, tls_params->session_data.data,
+                                       tls_params->session_data.size);
+       }
+
        int ret = gnutls_handshake(ctx->tls_session);
        if (ret == GNUTLS_E_SUCCESS) {
                return kr_ok();
@@ -952,7 +1073,7 @@ int tls_set_hs_state(struct tls_common_ctx *ctx, tls_hs_state_t state)
 }
 
 int tls_client_ctx_set_params(struct tls_client_ctx_t *ctx,
-                             const struct tls_client_paramlist_entry *entry,
+                             struct tls_client_paramlist_entry *entry,
                              struct session *session)
 {
        if (!ctx) {
@@ -963,4 +1084,75 @@ int tls_client_ctx_set_params(struct tls_client_ctx_t *ctx,
        return kr_ok();
 }
 
+static void session_ticket_timer_callback(uv_timer_t *timer)
+{
+       struct tls_session_ticket_ctx *ctx = (struct tls_session_ticket_ctx *)timer->data;
+       struct session_ticket_key *key = ctx->key;
+       assert(key);
+       ctx->epoch += 1;
+       session_ticket_key_recompute(key, ctx->epoch);
+       kr_log_verbose("[tls] TLS session ticket key regeneration\n");
+       uv_timer_again(&ctx->key_timer);
+}
+
+struct tls_session_ticket_ctx* tls_session_ticket_ctx_create(uv_loop_t *loop,
+                                                            const char *salt,
+                                                            size_t salt_len)
+{
+       assert(loop && salt && salt_len);
+
+       struct tls_session_ticket_ctx *ctx = malloc(sizeof(struct tls_session_ticket_ctx));
+       if (ctx == NULL) {
+               return NULL;
+       }
+
+       struct session_ticket_key *key = session_ticket_key_create(salt, salt_len);
+       if (key == NULL) {
+               free(ctx);
+               return NULL;
+       }
+
+       if (uv_timer_init(loop, &ctx->key_timer) != 0) {
+               session_ticket_key_destroy(key);
+               free(ctx);
+               return NULL;
+       }
+
+       ctx->key_timer.data = ctx;
+       int res = uv_timer_start(&ctx->key_timer, session_ticket_timer_callback,
+                                TLS_SESSION_TICKET_KEY_REGENERATION_INTERVAL,
+                                TLS_SESSION_TICKET_KEY_REGENERATION_INTERVAL);
+
+       if (res != 0) {
+               session_ticket_key_destroy(key);
+               free(ctx);
+               return NULL;
+       }
+
+       ctx->key = key;
+       ctx->epoch = 0;
+
+       session_ticket_timer_callback(&ctx->key_timer);
+
+       return ctx;
+}
+
+void tls_session_ticket_ctx_destroy(struct tls_session_ticket_ctx *ctx)
+{
+       if (ctx == NULL) {
+               return;
+       }
+
+       if (ctx->key != NULL) {
+               session_ticket_key_destroy(ctx->key);
+               ctx->key = NULL;
+       }
+
+       ctx->epoch = 0;
+       ctx->key_timer.data = NULL;
+       uv_timer_stop(&ctx->key_timer);
+
+       free(ctx);
+}
+
 #undef DEBUG_MSG
index 00330ef4aacd5cf574b206c4f7cee8a65ca0f70e..eb1454ee901f01e6fc3493a3ee4e4857536544fc 100644 (file)
@@ -42,6 +42,7 @@ struct tls_client_paramlist_entry {
        array_t(const char *) hostnames;
        array_t(const char *) pins;
        gnutls_certificate_credentials_t credentials;
+       gnutls_datum_t session_data;
 };
 
 struct worker_ctx;
@@ -96,9 +97,11 @@ struct tls_client_ctx_t {
         * this field must be always at first position
         */
        struct tls_common_ctx c;
-       const struct tls_client_paramlist_entry *params;
+       struct tls_client_paramlist_entry *params;
 };
 
+struct tls_session_ticket_ctx;
+
 /*! Create an empty TLS context in query context */
 struct tls_ctx_t* tls_new(struct worker_ctx *worker);
 
@@ -164,5 +167,12 @@ int tls_client_connect_start(struct tls_client_ctx_t *client_ctx,
                             tls_handshake_cb handshake_cb);
 
 int tls_client_ctx_set_params(struct tls_client_ctx_t *ctx,
-                             const struct tls_client_paramlist_entry *entry,
+                             struct tls_client_paramlist_entry *entry,
                              struct session *session);
+
+/** Create the session ticket context and copy the salt. */
+struct tls_session_ticket_ctx* tls_session_ticket_ctx_create(uv_loop_t *loop,
+                                                            const char *salt,
+                                                            size_t salt_len);
+/** Free all resources of the session ticket context. */
+void tls_session_ticket_ctx_destroy(struct tls_session_ticket_ctx *ctx);
index fb4091d78a3b4dc519aff60fb9055a346d9c0547..b3cb8196dd66db64eddd56b1af55eb14426b220b 100644 (file)
@@ -1097,15 +1097,38 @@ static int session_tls_hs_cb(struct session *session, int status)
        struct worker_ctx *worker = get_worker();
        union inaddr *peer = &session->peer;
        int deletion_res = worker_del_tcp_waiting(worker, &peer->ip);
+       int ret = kr_ok();
 
        if (status) {
                kr_nsrep_update_rtt(NULL, &peer->ip, KR_NS_DEAD,
                                    worker->engine->resolver.cache_rtt,
                                    KR_NS_UPDATE_NORESET);
-               return kr_ok();
+               return ret;
+       }
+
+       /* handshake was completed successfully */
+       struct tls_client_ctx_t *tls_client_ctx = session->tls_client_ctx;
+       struct tls_client_paramlist_entry *tls_params = tls_client_ctx->params;
+       gnutls_session_t tls_session = tls_client_ctx->c.tls_session;
+       if (gnutls_session_is_resumed(tls_session) != 0) {
+               kr_log_verbose("[tls_client] TLS session has resumed\n");
+       } else {
+               kr_log_verbose("[tls_client] TLS session has not resumed\n");
+               /* session wasn't resumed, delete old session data ... */
+               if (tls_params->session_data.data != NULL) {
+                       gnutls_free(tls_params->session_data.data);
+                       tls_params->session_data.data = NULL;
+                       tls_params->session_data.size = 0;
+               }
+               /* ... and get the new session data */
+               gnutls_datum_t tls_session_data = { NULL, 0 };
+               ret = gnutls_session_get_data2(tls_session, &tls_session_data);
+               if (ret == 0) {
+                       tls_params->session_data = tls_session_data;
+               }
        }
 
-       int ret = worker_add_tcp_connected(worker, &peer->ip, session);
+       ret = worker_add_tcp_connected(worker, &peer->ip, session);
        if (deletion_res == kr_ok() && ret == kr_ok()) {
                ret = session_next_waiting_send(session);
        } else {