aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Zaborowski <andrew.zaborowski@intel.com>2022-10-26 15:15:56 +0200
committerDenis Kenzior <denkenz@gmail.com>2022-10-28 13:22:42 -0500
commit4afb6523478d937b0dca870df89d765e69048755 (patch)
treebeeb4bf2849424c37decdb3e5e8ce28b636d2672
parent615afaaf96e80742d80793b671c20ff005765616 (diff)
tls: Add support for caching client session states
If a session cache is configured using l_tls_set_session_cache(), save session states to a set of 7 key/value pairs in a given l_settings object, in a given group. This only implements the client side as the semantics on the server side will be different.
-rw-r--r--ell/ell.sym1
-rw-r--r--ell/tls-private.h10
-rw-r--r--ell/tls.c192
-rw-r--r--ell/tls.h7
4 files changed, 208 insertions, 2 deletions
diff --git a/ell/ell.sym b/ell/ell.sym
index 5fe8d6ea..6c836e1c 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -518,6 +518,7 @@ global:
l_tls_set_auth_data;
l_tls_set_version_range;
l_tls_set_domain_mask;
+ l_tls_set_session_cache;
l_tls_alert_to_str;
l_tls_set_debug;
l_tls_set_cert_dump_path;
diff --git a/ell/tls-private.h b/ell/tls-private.h
index 8941e90d..6f09f6a5 100644
--- a/ell/tls-private.h
+++ b/ell/tls-private.h
@@ -218,6 +218,12 @@ struct l_tls {
struct tls_cipher_suite **cipher_suite_pref_list;
+ struct l_settings *session_settings;
+ char *session_group;
+ uint64_t session_lifetime;
+ l_tls_session_update_cb_t session_update_cb;
+ void *session_update_user_data;
+
bool in_callback;
bool pending_destroy;
@@ -251,6 +257,10 @@ struct l_tls {
const struct tls_named_group *negotiated_curve;
const struct tls_named_group *negotiated_ff_group;
+ uint8_t session_id[32];
+ size_t session_id_size;
+ bool session_id_new;
+
/* SecurityParameters current and pending */
struct {
diff --git a/ell/tls.c b/ell/tls.c
index fa3df5d3..ff4fa5bd 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -46,6 +46,9 @@
#include "strv.h"
#include "missing.h"
#include "string.h"
+#include "settings.h"
+#include "time.h"
+#include "time-private.h"
bool tls10_prf(const void *secret, size_t secret_len,
const char *label,
@@ -204,6 +207,9 @@ static void tls_reset_handshake(struct l_tls *tls)
TLS_SET_STATE(TLS_HANDSHAKE_WAIT_START);
tls->cert_requested = 0;
tls->cert_sent = 0;
+
+ tls->session_id_size = 0;
+ tls->session_id_new = false;
}
static void tls_cleanup_handshake(struct l_tls *tls)
@@ -820,6 +826,31 @@ parse_error:
return false;
}
+static void tls_forget_cached_client_session(struct l_tls *tls)
+{
+ /* Note: might want to l_settings_remove_group() instead. */
+ l_settings_remove_key(tls->session_settings, tls->session_group,
+ "TLSSessionID");
+ l_settings_remove_key(tls->session_settings, tls->session_group,
+ "TLSSessionMasterSecret");
+ l_settings_remove_key(tls->session_settings, tls->session_group,
+ "TLSSessionVersion");
+ l_settings_remove_key(tls->session_settings, tls->session_group,
+ "TLSSessionCipherSuite");
+ l_settings_remove_key(tls->session_settings, tls->session_group,
+ "TLSSessionCompressionMethod");
+ l_settings_remove_key(tls->session_settings, tls->session_group,
+ "TLSSessionExpiryTime");
+ l_settings_remove_key(tls->session_settings, tls->session_group,
+ "TLSSessionPeerIdentity");
+
+ if (tls->session_update_cb) {
+ tls->in_callback = true;
+ tls->session_update_cb(tls->session_update_user_data);
+ tls->in_callback = false;
+ }
+}
+
#define SWITCH_ENUM_TO_STR(val) \
case (val): \
return L_STRINGIFY(val);
@@ -868,6 +899,28 @@ static void tls_send_alert(struct l_tls *tls, bool fatal,
void tls_disconnect(struct l_tls *tls, enum l_tls_alert_desc desc,
enum l_tls_alert_desc local_desc)
{
+ bool forget_session = false;
+
+ if (!tls->server && (desc || local_desc) &&
+ tls->session_settings && tls->session_id_size &&
+ !tls->session_id_new)
+ /*
+ * RFC5246 Section 7.2: "Alert messages with a level of fatal
+ * result in the immediate termination of the connection. In
+ * this case, other connections corresponding to the session
+ * may continue, but the session identifier MUST be
+ * invalidated, preventing the failed session from being used
+ * to establish new connections."
+ *
+ * and 7.2.1: "Note that as of TLS 1.1, failure to properly
+ * close a connection no longer requires that a session not
+ * be resumed."
+ *
+ * I.e. we need to remove the session from the cache here but
+ * not on l_tls_close().
+ */
+ forget_session = true;
+
tls_send_alert(tls, true, desc);
tls_reset_handshake(tls);
@@ -879,6 +932,13 @@ void tls_disconnect(struct l_tls *tls, enum l_tls_alert_desc desc,
tls->negotiated_version = 0;
tls->ready = false;
+ if (forget_session) {
+ tls_forget_cached_client_session(tls);
+
+ if (tls->pending_destroy)
+ return;
+ }
+
tls->disconnected(local_desc ?: desc, local_desc && !desc,
tls->user_data);
}
@@ -1814,6 +1874,15 @@ static void tls_handle_server_hello(struct l_tls *tls,
compression_method_id = buf[35 + session_id_size + 2];
len -= session_id_size + 2 + 1;
+ if (session_id_size > 32)
+ goto decode_error;
+
+ if (session_id_size && tls->session_settings) {
+ tls->session_id_new = true;
+ tls->session_id_size = session_id_size;
+ memcpy(tls->session_id, buf + 35, session_id_size);
+ }
+
extensions_seen = l_queue_new();
result = tls_handle_hello_extensions(tls, buf + 38 + session_id_size,
len, extensions_seen);
@@ -2350,7 +2419,9 @@ error:
static void tls_finished(struct l_tls *tls)
{
- char *peer_identity = NULL;
+ _auto_(l_free) char *peer_identity = NULL;
+ uint64_t peer_cert_expiry;
+ bool session_update = false;
if (tls->peer_authenticated) {
peer_identity = tls_get_peer_identity_str(tls->peer_cert);
@@ -2359,6 +2430,65 @@ static void tls_finished(struct l_tls *tls)
"tls_get_peer_identity_str failed");
return;
}
+
+ if (tls->session_id_new &&
+ !l_cert_get_valid_times(tls->peer_cert, NULL,
+ &peer_cert_expiry)) {
+ TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0,
+ "l_cert_get_valid_times failed");
+ return;
+ }
+ }
+
+ if (!tls->server && tls->session_settings && tls->session_id_new) {
+ _auto_(l_free) char *session_id_str =
+ l_util_hexstring(tls->session_id, tls->session_id_size);
+ uint64_t expiry = tls->session_lifetime ?
+ time_realtime_now() + tls->session_lifetime : 0;
+
+ if (tls->peer_authenticated &&
+ (!expiry || peer_cert_expiry < expiry))
+ expiry = peer_cert_expiry;
+
+ l_settings_set_bytes(tls->session_settings, tls->session_group,
+ "TLSSessionID", tls->session_id,
+ tls->session_id_size);
+ l_settings_set_bytes(tls->session_settings, tls->session_group,
+ "TLSSessionMasterSecret",
+ tls->pending.master_secret, 48);
+ l_settings_set_int(tls->session_settings, tls->session_group,
+ "TLSSessionVersion",
+ tls->negotiated_version);
+ l_settings_set_bytes(tls->session_settings, tls->session_group,
+ "TLSSessionCipherSuite",
+ tls->pending.cipher_suite->id, 2);
+ l_settings_set_uint(tls->session_settings, tls->session_group,
+ "TLSSessionCompressionMethod",
+ tls->pending.compression_method->id);
+
+ if (expiry)
+ l_settings_set_uint64(tls->session_settings,
+ tls->session_group,
+ "TLSSessionExpiryTime", expiry);
+ else
+ /* We may be overwriting an older session's data */
+ l_settings_remove_key(tls->session_settings,
+ tls->session_group,
+ "TLSSessionExpiryTime");
+
+ if (tls->peer_authenticated)
+ l_settings_set_string(tls->session_settings,
+ tls->session_group,
+ "TLSSessionPeerIdentity",
+ peer_identity);
+ else
+ /* We may be overwriting an older session's data */
+ l_settings_remove_key(tls->session_settings,
+ tls->session_group,
+ "TLSSessionPeerIdentity");
+
+ TLS_DEBUG("Saving new session %s to cache", session_id_str);
+ session_update = true;
}
/* Free up the resources used in the handshake */
@@ -2367,10 +2497,18 @@ static void tls_finished(struct l_tls *tls)
TLS_SET_STATE(TLS_HANDSHAKE_DONE);
tls->ready = true;
+ if (session_update && tls->session_update_cb) {
+ tls->in_callback = true;
+ tls->session_update_cb(tls->session_update_user_data);
+ tls->in_callback = false;
+
+ if (tls->pending_destroy)
+ return;
+ }
+
tls->in_callback = true;
tls->ready_handle(peer_identity, tls->user_data);
tls->in_callback = false;
- l_free(peer_identity);
tls_cleanup_handshake(tls);
}
@@ -2643,6 +2781,7 @@ LIB_EXPORT struct l_tls *l_tls_new(bool server,
tls->cipher_suite_pref_list = tls_cipher_suite_pref;
tls->min_version = TLS_MIN_VERSION;
tls->max_version = TLS_MAX_VERSION;
+ tls->session_lifetime = 24 * 3600 * L_USEC_PER_SEC;
/* If we're the server wait for the Client Hello already */
if (tls->server)
@@ -2669,6 +2808,7 @@ LIB_EXPORT void l_tls_free(struct l_tls *tls)
l_tls_set_auth_data(tls, NULL, NULL);
l_tls_set_domain_mask(tls, NULL);
l_tls_set_cert_dump_path(tls, NULL);
+ l_tls_set_session_cache(tls, NULL, NULL, 0, NULL, NULL);
tls_reset_handshake(tls);
tls_cleanup_handshake(tls);
@@ -3042,6 +3182,54 @@ LIB_EXPORT void l_tls_set_domain_mask(struct l_tls *tls, char **mask)
tls->subject_mask = l_strv_copy(mask);
}
+/**
+ * l_tls_set_session_cache:
+ * @tls: TLS object being configured
+ * @settings: l_settings object to read and write session data from/to or
+ * NULL to disable caching session states. The object must remain valid
+ * until this method is called with a different value.
+ * @group: group name inside @settings
+ * @lifetime: a CLOCK_REALTIME-based microsecond resolution lifetime for
+ * cached sessions. The RFC recommends 24 hours.
+ * @update_cb: a callback to be invoked whenever the settings in @settings
+ * have been updated and may need to be written to persistent storage if
+ * desired, or NULL.
+ * @user_data: user data pointer to pass to @update_cb.
+ *
+ * Enables caching and resuming session states as described in RFC 5246 for
+ * faster setup. l_tls will maintain the required session state data in
+ * @settings including removing expired or erroneous sessions.
+ *
+ * A client's cache contains at most one session state since the client
+ * must request one specific Session ID from the server when resuming a
+ * session. The resumption will only work while the state is cached by
+ * both the server and the client so clients should keep separate @settings
+ * objects or use separate groups inside one object for every discrete
+ * server they may want to connect to.
+ *
+ * Multiple l_tls clients connecting to the same server can share one cache
+ * to allow reusing an established session that is still active (actual
+ * concurrency is not supported as there's no locking.)
+ */
+LIB_EXPORT void l_tls_set_session_cache(struct l_tls *tls,
+ struct l_settings *settings,
+ const char *group_name,
+ uint64_t lifetime,
+ l_tls_session_update_cb_t update_cb,
+ void *user_data)
+{
+ if (unlikely(!tls))
+ return;
+
+ tls->session_settings = settings;
+ tls->session_lifetime = lifetime;
+ tls->session_update_cb = update_cb;
+ tls->session_update_user_data = user_data;
+
+ l_free(tls->session_group);
+ tls->session_group = l_strdup(group_name);
+}
+
LIB_EXPORT const char *l_tls_alert_to_str(enum l_tls_alert_desc desc)
{
switch (desc) {
diff --git a/ell/tls.h b/ell/tls.h
index a4fd414b..92d8b9ef 100644
--- a/ell/tls.h
+++ b/ell/tls.h
@@ -36,6 +36,7 @@ struct l_tls;
struct l_key;
struct l_certchain;
struct l_queue;
+struct l_settings;
enum l_tls_alert_desc {
TLS_ALERT_CLOSE_NOTIFY = 0,
@@ -72,6 +73,7 @@ typedef void (*l_tls_disconnect_cb_t)(enum l_tls_alert_desc reason,
bool remote, void *user_data);
typedef void (*l_tls_debug_cb_t)(const char *str, void *user_data);
typedef void (*l_tls_destroy_cb_t)(void *user_data);
+typedef void (*l_tls_session_update_cb_t)(void *user_data);
/*
* app_data_handler gets called with newly received decrypted data.
@@ -127,6 +129,11 @@ void l_tls_set_version_range(struct l_tls *tls,
void l_tls_set_domain_mask(struct l_tls *tls, char **mask);
+void l_tls_set_session_cache(struct l_tls *tls, struct l_settings *settings,
+ const char *group_name, uint64_t lifetime,
+ l_tls_session_update_cb_t update_cb,
+ void *user_data);
+
const char *l_tls_alert_to_str(enum l_tls_alert_desc desc);
enum l_checksum_type;