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);
{ "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 }
#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
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;
}
gnutls_certificate_free_credentials(entry->credentials);
}
+ if (entry->session_data.data) {
+ gnutls_free(entry->session_data.data);
+ }
+
free(entry);
return 0;
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();
}
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) {
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
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 {