aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew Zaborowski <andrew.zaborowski@intel.com>2022-10-26 15:15:57 +0200
committerDenis Kenzior <denkenz@gmail.com>2022-10-28 13:26:36 -0500
commitcfab297d3bd3d8a59f3e4021f1da8e507fca4a95 (patch)
treee734ddc1815399fe485de12534688eba8659a7eb
parent4afb6523478d937b0dca870df89d765e69048755 (diff)
tls: Client session resumption
In client mode load a stored session state from the cache in l_tls_start and attempt to resume that session.
-rw-r--r--ell/tls-private.h3
-rw-r--r--ell/tls-suites.c4
-rw-r--r--ell/tls.c316
3 files changed, 286 insertions, 37 deletions
diff --git a/ell/tls-private.h b/ell/tls-private.h
index 6f09f6a5..71566660 100644
--- a/ell/tls-private.h
+++ b/ell/tls-private.h
@@ -260,6 +260,9 @@ struct l_tls {
uint8_t session_id[32];
size_t session_id_size;
bool session_id_new;
+ uint8_t session_cipher_suite_id[2];
+ uint8_t session_compression_method_id;
+ char *session_peer_identity;
/* SecurityParameters current and pending */
diff --git a/ell/tls-suites.c b/ell/tls-suites.c
index ee4e7ee6..8cbcb63e 100644
--- a/ell/tls-suites.c
+++ b/ell/tls-suites.c
@@ -352,8 +352,8 @@ static bool tls_send_rsa_client_key_xchg(struct l_tls *tls)
}
/* Must match the version in tls_send_client_hello */
- pre_master_secret[0] = (uint8_t) (tls->max_version >> 8);
- pre_master_secret[1] = (uint8_t) (tls->max_version >> 0);
+ pre_master_secret[0] = (uint8_t) (tls->client_version >> 8);
+ pre_master_secret[1] = (uint8_t) (tls->client_version >> 0);
l_getrandom(pre_master_secret + 2, 46);
diff --git a/ell/tls.c b/ell/tls.c
index ff4fa5bd..88ac3d76 100644
--- a/ell/tls.c
+++ b/ell/tls.c
@@ -210,6 +210,7 @@ static void tls_reset_handshake(struct l_tls *tls)
tls->session_id_size = 0;
tls->session_id_new = false;
+ l_free(l_steal_ptr(tls->session_peer_identity));
}
static void tls_cleanup_handshake(struct l_tls *tls)
@@ -851,6 +852,155 @@ static void tls_forget_cached_client_session(struct l_tls *tls)
}
}
+static bool tls_load_cached_client_session(struct l_tls *tls)
+{
+ /*
+ * The following settings are required:
+ * TLSSessionID,
+ * TLSSessionMasterSecret,
+ * TLSSessionVersion,
+ * TLSSessionCipherSuite,
+ * TLSSessionCompressionMethod,
+ * and these two are optional:
+ * TLSSessionExpiryTime,
+ * TLSSessionPeerIdentity.
+ */
+ _auto_(l_free) uint8_t *session_id = NULL;
+ size_t session_id_size;
+ _auto_(l_free) char *session_id_str = NULL;
+ _auto_(l_free) uint8_t *master_secret = NULL;
+ int version;
+ _auto_(l_free) uint8_t *cipher_suite_id = NULL;
+ struct tls_cipher_suite *cipher_suite;
+ unsigned int compression_method_id;
+ _auto_(l_free) char *peer_identity = NULL;
+ size_t size;
+ const char *error;
+
+ tls->session_id_size = 0;
+ tls->session_id_new = false;
+
+ if (!tls->session_settings ||
+ !l_settings_has_key(tls->session_settings,
+ tls->session_group,
+ "TLSSessionID"))
+ /* No session cached, no error */
+ return false;
+
+ session_id = l_settings_get_bytes(tls->session_settings,
+ tls->session_group,
+ "TLSSessionID",
+ &session_id_size);
+ if (unlikely(!session_id ||
+ session_id_size < 1 || session_id_size > 32))
+ goto warn_corrupt;
+
+ session_id_str =
+ l_util_hexstring(tls->session_id, tls->session_id_size);
+
+ if (l_settings_has_key(tls->session_settings, tls->session_group,
+ "TLSSessionExpiryTime")) {
+ uint64_t expiry_time;
+
+ if (unlikely(!l_settings_get_uint64(tls->session_settings,
+ tls->session_group,
+ "TLSSessionExpiryTime",
+ &expiry_time)))
+ goto warn_corrupt;
+
+ if (time_realtime_now() > expiry_time) {
+ TLS_DEBUG("Cached session %s is expired, removing it, "
+ "will start a new session",
+ session_id_str);
+ goto forget;
+ }
+ }
+
+ if (unlikely(!l_settings_get_int(tls->session_settings,
+ tls->session_group,
+ "TLSSessionVersion",
+ &version) ||
+ version < TLS_MIN_VERSION || version > TLS_MAX_VERSION))
+ goto warn_corrupt;
+
+ master_secret = l_settings_get_bytes(tls->session_settings,
+ tls->session_group,
+ "TLSSessionMasterSecret",
+ &size);
+ if (unlikely(!master_secret || size != 48))
+ goto warn_corrupt;
+
+ cipher_suite_id = l_settings_get_bytes(tls->session_settings,
+ tls->session_group,
+ "TLSSessionCipherSuite",
+ &size);
+ if (unlikely(!cipher_suite_id || size != 2 ||
+ !(cipher_suite =
+ tls_find_cipher_suite(cipher_suite_id))))
+ goto warn_corrupt;
+
+ /*
+ * While we could attempt to resume a session even though we're now
+ * configured with, say, a different certificate type than what we
+ * had when we cached that session, that is too questionable of a
+ * scenario to support it. We don't specifically check that all of
+ * the authentication data is the same, e.g. we don't save the
+ * certificate serial number or path, but ensure the cached cipher
+ * suite is compatible with current authentication data.
+ *
+ * We filter the cipher suites in our Client Hello to only offer the
+ * ones compatible with current configuration so if we also include
+ * a Session ID from a session who's cipher suite is not one of those
+ * listed in that same Client Hello, the server is likely to notice
+ * and either start a new session or send a fatal Alert.
+ *
+ * It is up to the user to keep multiple cache instances if it needs
+ * to save multiple sessions.
+ */
+ if (unlikely(!tls_cipher_suite_is_compatible(tls, cipher_suite,
+ &error))) {
+ TLS_DEBUG("Cached session %s cipher suite not compatible: %s",
+ session_id_str, error);
+ goto forget;
+ }
+
+ if (unlikely(!l_settings_get_uint(tls->session_settings,
+ tls->session_group,
+ "TLSSessionCompressionMethod",
+ &compression_method_id) ||
+ !tls_find_compression_method(compression_method_id)))
+ goto warn_corrupt;
+
+ if (l_settings_has_key(tls->session_settings, tls->session_group,
+ "TLSSessionPeerIdentity")) {
+ peer_identity = l_settings_get_string(tls->session_settings,
+ tls->session_group,
+ "TLSSessionPeerIdentity");
+ if (unlikely(!peer_identity || !cipher_suite->signature))
+ goto warn_corrupt;
+ }
+
+ tls->session_id_size = session_id_size;
+ memcpy(tls->session_id, session_id, session_id_size);
+ tls->session_id_new = false;
+ tls->client_version = version;
+ memcpy(tls->pending.master_secret, master_secret, 48);
+ memcpy(tls->session_cipher_suite_id, cipher_suite_id, 2);
+ tls->session_compression_method_id = compression_method_id;
+ l_free(tls->session_peer_identity);
+ tls->session_peer_identity = l_steal_ptr(peer_identity);
+ return true;
+
+warn_corrupt:
+ TLS_DEBUG("Cached session %s data is corrupt or has unsupported "
+ "parameters, removing it, will start a new session",
+ session_id_str ?: "<unknonwn>");
+
+forget:
+ tls_forget_cached_client_session(tls);
+ return false;
+}
+
#define SWITCH_ENUM_TO_STR(val) \
case (val): \
return L_STRINGIFY(val);
@@ -1063,14 +1213,19 @@ static bool tls_send_client_hello(struct l_tls *tls)
/* Fill in the Client Hello body */
- *ptr++ = (uint8_t) (tls->max_version >> 8);
- *ptr++ = (uint8_t) (tls->max_version >> 0);
+ *ptr++ = (uint8_t) (tls->client_version >> 8);
+ *ptr++ = (uint8_t) (tls->client_version >> 0);
tls_write_random(tls->pending.client_random);
memcpy(ptr, tls->pending.client_random, 32);
ptr += 32;
- *ptr++ = 0; /* No SessionID */
+ if (tls->session_id_size) {
+ *ptr++ = tls->session_id_size;
+ memcpy(ptr, tls->session_id, tls->session_id_size);
+ ptr += tls->session_id_size;
+ } else
+ *ptr++ = 0;
len_ptr = ptr;
ptr += 2;
@@ -1317,22 +1472,10 @@ static void tls_send_server_hello_done(struct l_tls *tls)
TLS_HANDSHAKE_HEADER_SIZE);
}
-void tls_generate_master_secret(struct l_tls *tls,
- const uint8_t *pre_master_secret,
- int pre_master_secret_len)
+static void tls_update_key_block(struct l_tls *tls)
{
uint8_t seed[64];
- int key_block_size;
-
- memcpy(seed + 0, tls->pending.client_random, 32);
- memcpy(seed + 32, tls->pending.server_random, 32);
-
- tls_prf_get_bytes(tls, pre_master_secret, pre_master_secret_len,
- "master secret", seed, 64,
- tls->pending.master_secret, 48);
-
- /* Directly generate the key block while we're at it */
- key_block_size = 0;
+ int key_block_size = 0;
if (tls->pending.cipher_suite->encryption)
key_block_size += 2 *
@@ -1360,8 +1503,25 @@ void tls_generate_master_secret(struct l_tls *tls,
tls_prf_get_bytes(tls, tls->pending.master_secret, 48,
"key expansion", seed, 64,
tls->pending.key_block, key_block_size);
+ explicit_bzero(seed, 64);
+}
+void tls_generate_master_secret(struct l_tls *tls,
+ const uint8_t *pre_master_secret,
+ int pre_master_secret_len)
+{
+ uint8_t seed[64];
+
+ memcpy(seed + 0, tls->pending.client_random, 32);
+ memcpy(seed + 32, tls->pending.server_random, 32);
+
+ tls_prf_get_bytes(tls, pre_master_secret, pre_master_secret_len,
+ "master secret", seed, 64,
+ tls->pending.master_secret, 48);
explicit_bzero(seed, 64);
+
+ /* Directly generate the key block while we're at it */
+ tls_update_key_block(tls);
}
static void tls_get_handshake_hash(struct l_tls *tls,
@@ -1856,11 +2016,14 @@ static void tls_handle_server_hello(struct l_tls *tls,
int i;
struct l_queue *extensions_seen;
bool result;
+ uint16_t version;
+ bool resuming = false;
/* Do we have enough for ProtocolVersion + Random + SessionID len ? */
if (len < 2 + 32 + 1)
goto decode_error;
+ version = l_get_be16(buf);
memcpy(tls->pending.server_random, buf + 2, 32);
session_id_size = buf[34];
len -= 35;
@@ -1877,6 +2040,32 @@ static void tls_handle_server_hello(struct l_tls *tls,
if (session_id_size > 32)
goto decode_error;
+ if (tls->session_id_size) {
+ _auto_(l_free) char *session_id_str =
+ l_util_hexstring(tls->session_id, tls->session_id_size);
+
+ if (session_id_size == tls->session_id_size &&
+ !memcmp(buf + 35, tls->session_id,
+ session_id_size)) {
+ TLS_DEBUG("Negotiated resumption of cached session %s",
+ session_id_str);
+ resuming = true;
+
+ /*
+ * Skip parsing extensions as none of the ones we
+ * support are used in session resumption. We could
+ * as well signal an error if the ServerHello has any
+ * extensions, for now ignore them.
+ */
+ goto check_version;
+ }
+
+ TLS_DEBUG("Server decided not to resume cached session %s, "
+ "sent %s session ID", session_id_str,
+ session_id_size ? "a new" : "no");
+ tls->session_id_size = 0;
+ }
+
if (session_id_size && tls->session_settings) {
tls->session_id_new = true;
tls->session_id_size = session_id_size;
@@ -1891,18 +2080,17 @@ static void tls_handle_server_hello(struct l_tls *tls,
if (!result)
return;
- tls->negotiated_version = l_get_be16(buf);
-
- if (tls->negotiated_version < tls->min_version ||
- tls->negotiated_version > tls->max_version) {
- TLS_DISCONNECT(tls->negotiated_version < tls->min_version ?
+check_version:
+ if (version < tls->min_version || version > tls->max_version) {
+ TLS_DISCONNECT(version < tls->min_version ?
TLS_ALERT_PROTOCOL_VERSION :
TLS_ALERT_ILLEGAL_PARAM, 0,
- "Unsupported version %02x",
- tls->negotiated_version);
+ "Unsupported version %02x", version);
return;
}
+ tls->negotiated_version = version;
+
/* Stop maintaining handshake message hashes other than MD1 and SHA. */
if (tls->negotiated_version < L_TLS_V12)
for (i = 0; i < __HANDSHAKE_HASH_COUNT; i++)
@@ -1958,7 +2146,30 @@ static void tls_handle_server_hello(struct l_tls *tls,
TLS_DEBUG("Negotiated %s", tls->pending.compression_method->name);
- if (tls->pending.cipher_suite->signature)
+ if (resuming) {
+ /*
+ * Now that we've validated the Server Hello parameters and
+ * know that they're supported by this version of ell and
+ * consistent with the current configuration, ensure that
+ * they're identical with the ones in the cached session
+ * being resumed. This serves as a sanity check for
+ * rare situations like a corrupt session cache file or
+ * a file written by a newer ell version.
+ */
+ if (tls->negotiated_version != tls->client_version ||
+ memcmp(cipher_suite_id,
+ tls->session_cipher_suite_id, 2) ||
+ compression_method_id !=
+ tls->session_compression_method_id) {
+ TLS_DISCONNECT(TLS_ALERT_HANDSHAKE_FAIL, 0,
+ "Session parameters don't match");
+ return;
+ }
+
+ tls_update_key_block(tls);
+
+ TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CHANGE_CIPHER_SPEC);
+ } else if (tls->pending.cipher_suite->signature)
TLS_SET_STATE(TLS_HANDSHAKE_WAIT_CERTIFICATE);
else
TLS_SET_STATE(TLS_HANDSHAKE_WAIT_KEY_EXCHANGE);
@@ -2419,18 +2630,22 @@ error:
static void tls_finished(struct l_tls *tls)
{
- _auto_(l_free) char *peer_identity = NULL;
+ _auto_(l_free) char *peer_cert_identity = NULL;
+ char *peer_identity = NULL;
uint64_t peer_cert_expiry;
+ bool resuming = tls->session_id_size && !tls->session_id_new;
bool session_update = false;
- if (tls->peer_authenticated) {
- peer_identity = tls_get_peer_identity_str(tls->peer_cert);
- if (!peer_identity) {
+ if (tls->peer_authenticated && !resuming) {
+ peer_cert_identity = tls_get_peer_identity_str(tls->peer_cert);
+ if (!peer_cert_identity) {
TLS_DISCONNECT(TLS_ALERT_INTERNAL_ERROR, 0,
"tls_get_peer_identity_str failed");
return;
}
+ peer_identity = peer_cert_identity;
+
if (tls->session_id_new &&
!l_cert_get_valid_times(tls->peer_cert, NULL,
&peer_cert_expiry)) {
@@ -2438,7 +2653,8 @@ static void tls_finished(struct l_tls *tls)
"l_cert_get_valid_times failed");
return;
}
- }
+ } else if (tls->peer_authenticated && resuming)
+ peer_identity = tls->session_peer_identity;
if (!tls->server && tls->session_settings && tls->session_id_new) {
_auto_(l_free) char *session_id_str =
@@ -2516,6 +2732,8 @@ static void tls_finished(struct l_tls *tls)
static void tls_handle_handshake(struct l_tls *tls, int type,
const uint8_t *buf, size_t len)
{
+ bool resuming;
+
TLS_DEBUG("Handling a %s of %zi bytes",
tls_handshake_type_to_str(type), len);
@@ -2703,7 +2921,9 @@ static void tls_handle_handshake(struct l_tls *tls, int type,
if (!tls_verify_finished(tls, buf, len))
break;
- if (tls->server) {
+ resuming = tls->session_id_size && !tls->session_id_new;
+
+ if (tls->server || (!tls->server && resuming)) {
const char *error;
tls_send_change_cipher_spec(tls);
@@ -2717,9 +2937,9 @@ static void tls_handle_handshake(struct l_tls *tls, int type,
}
/*
- * On the client, the server's certificate is now verified
- * regardless of the key exchange method, based on the
- * following logic:
+ * When starting a new session on the client, the server's
+ * certificate is now verified regardless of the key exchange
+ * method, based on the following logic:
*
* - tls->ca_certs is non-NULL so tls_handle_certificate
* (always called on the client) must have veritifed the
@@ -2744,9 +2964,14 @@ static void tls_handle_handshake(struct l_tls *tls, int type,
* able to sign the client random together with the
* ServerKeyExchange parameters using its certified key
* pair.
+ *
+ * If we're resuming a cached session, we have authenticated
+ * this server before and the successful decryption of this
+ * message confirms the server identity hasn't changed.
*/
if (!tls->server && tls->cipher_suite[0]->signature &&
- tls->ca_certs)
+ ((!resuming && tls->ca_certs) ||
+ (resuming && tls->session_peer_identity)))
tls->peer_authenticated = true;
tls_finished(tls);
@@ -3010,6 +3235,27 @@ LIB_EXPORT bool l_tls_start(struct l_tls *tls)
if (!tls_init_handshake_hash(tls))
return false;
+ /*
+ * If we're going to try resuming a cached session, send the Client
+ * Hello with the version we think is supported.
+ *
+ * RFC5246 Appendix E.1:
+ * "Whenever a client already knows the highest protocol version known
+ * to a server (for example, when resuming a session), it SHOULD
+ * initiate the connection in that native protocol."
+ *
+ * Don't directly set tls->{min,max}_version as that would make the
+ * handshake fail if the server decides to start a new session with
+ * a new version instead of resuming, which it is allowed to do.
+ */
+ tls->client_version = tls->max_version;
+ tls_load_cached_client_session(tls);
+
+ if (tls->pending_destroy) {
+ l_tls_free(tls);
+ return false;
+ }
+
if (!tls_send_client_hello(tls))
return false;