ell.lists.linux.dev archive mirror
 help / color / mirror / Atom feed
* [PATCH 1/6] time: Add time_realtime_now
@ 2022-10-26 13:15 Andrew Zaborowski
  2022-10-26 13:15 ` [PATCH 2/6] cert: Add l_cert_get_valid_times Andrew Zaborowski
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Andrew Zaborowski @ 2022-10-26 13:15 UTC (permalink / raw)
  To: ell

---
 ell/time-private.h | 1 +
 ell/time.c         | 8 ++++++++
 2 files changed, 9 insertions(+)

diff --git a/ell/time-private.h b/ell/time-private.h
index 83c23dd..e107503 100644
--- a/ell/time-private.h
+++ b/ell/time-private.h
@@ -24,3 +24,4 @@ uint64_t _time_pick_interval_secs(uint32_t min_secs, uint32_t max_secs);
 uint64_t _time_fuzz_msecs(uint64_t ms);
 uint64_t _time_fuzz_secs(uint32_t secs, uint32_t max_offset);
 uint64_t _time_realtime_to_boottime(const struct timeval *ts);
+uint64_t time_realtime_now(void);
diff --git a/ell/time.c b/ell/time.c
index 41e5725..2eac6c4 100644
--- a/ell/time.c
+++ b/ell/time.c
@@ -58,6 +58,14 @@ LIB_EXPORT uint64_t l_time_now(void)
 	return _time_from_timespec(&now);
 }
 
+uint64_t time_realtime_now(void)
+{
+	struct timespec now;
+
+	clock_gettime(CLOCK_REALTIME, &now);
+	return _time_from_timespec(&now);
+}
+
 /**
  * l_time_after
  *
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 2/6] cert: Add l_cert_get_valid_times
  2022-10-26 13:15 [PATCH 1/6] time: Add time_realtime_now Andrew Zaborowski
@ 2022-10-26 13:15 ` Andrew Zaborowski
  2022-10-26 13:15 ` [PATCH 3/6] tls: Fix an RFC reference Andrew Zaborowski
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Andrew Zaborowski @ 2022-10-26 13:15 UTC (permalink / raw)
  To: ell

Add utility to extract the Not Before and Not After timestamps from
certificates.
---
 ell/asn1-private.h |   2 +
 ell/cert.c         | 185 +++++++++++++++++++++++++++++++++++++++++++++
 ell/cert.h         |   2 +
 ell/ell.sym        |   1 +
 4 files changed, 190 insertions(+)

diff --git a/ell/asn1-private.h b/ell/asn1-private.h
index d794515..36d8de3 100644
--- a/ell/asn1-private.h
+++ b/ell/asn1-private.h
@@ -34,6 +34,8 @@
 #define ASN1_ID_UTF8STRING	ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x0c)
 #define ASN1_ID_PRINTABLESTRING	ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x13)
 #define ASN1_ID_IA5STRING	ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x16)
+#define ASN1_ID_UTCTIME		ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x17)
+#define ASN1_ID_GENERALIZEDTIME	ASN1_ID(ASN1_CLASS_UNIVERSAL, 0, 0x18)
 
 struct asn1_oid {
 	uint8_t asn1_len;
diff --git a/ell/cert.c b/ell/cert.c
index a158142..b4f5df7 100644
--- a/ell/cert.c
+++ b/ell/cert.c
@@ -25,6 +25,7 @@
 #include <string.h>
 #include <stdio.h>
 #include <errno.h>
+#include <time.h>
 
 #include "private.h"
 #include "useful.h"
@@ -33,6 +34,8 @@
 #include "asn1-private.h"
 #include "cipher.h"
 #include "pem-private.h"
+#include "time.h"
+#include "utf8.h"
 #include "cert.h"
 #include "cert-private.h"
 #include "tls.h"
@@ -186,6 +189,188 @@ LIB_EXPORT const uint8_t *l_cert_get_dn(struct l_cert *cert, size_t *out_len)
 						-1);
 }
 
+static uint64_t cert_parse_asn1_time(const uint8_t *data, size_t len,
+					uint8_t tag)
+{
+	struct tm tm = {};
+	int tz_hours;
+	int tz_mins;
+	int century;
+	int msecs = 0;
+	time_t tt;
+	unsigned int i;
+
+	for (i = 0; i < len && i < 15; i++)
+		if (unlikely(!l_ascii_isdigit(data[i])))
+			break;
+
+	if (tag == ASN1_ID_UTCTIME) {
+		if (unlikely(!L_IN_SET(i, 10, 12)))
+			return L_TIME_INVALID;
+
+		century = 19;
+	} else if (tag == ASN1_ID_GENERALIZEDTIME) {
+		if (unlikely(!L_IN_SET(i, 10, 12, 14)))
+			return L_TIME_INVALID;
+
+		century = (data[0] - '0') * 10 + (data[1] - '0');
+		if (century < 19)
+				return L_TIME_INVALID;
+
+		if (len >= i + 4 && data[i] == '.') {
+			if (unlikely(!l_ascii_isdigit(data[i + 1]) ||
+						!l_ascii_isdigit(data[i + 2]) ||
+						!l_ascii_isdigit(data[i + 3])))
+				return L_TIME_INVALID;
+
+			i++;
+			msecs += (data[i++] - '0') * 100;
+			msecs += (data[i++] - '0') * 10;
+			msecs += (data[i++] - '0');
+		}
+
+		data += 2;
+		len -= 2;
+		i -= 2;
+	} else
+		return L_TIME_INVALID;
+
+	if (unlikely((len != i + 1 || data[i] != 'Z') &&
+			(len != i + 5 || data[i] != '+' || data[i] != '-')))
+		return L_TIME_INVALID;
+
+	tm.tm_year = (data[0] - '0') * 10 + (data[1] - '0');
+	tm.tm_mon = (data[2] - '0') * 10 + (data[3] - '0');
+	tm.tm_mday = (data[4] - '0') * 10 + (data[5] - '0');
+	tm.tm_hour = (data[6] - '0') * 10 + (data[7] - '0');
+
+	if (unlikely(tm.tm_mon < 1 || tm.tm_mon > 12 ||
+				tm.tm_mday < 1 || tm.tm_mday > 31 ||
+				tm.tm_hour > 23))
+		return L_TIME_INVALID;
+
+	if (i >= 10) {
+		tm.tm_min = (data[8] - '0') * 10 + (data[9] - '0');
+		if (unlikely(tm.tm_min > 59))
+			return L_TIME_INVALID;
+	}
+
+	if (i >= 12) {
+		tm.tm_sec = (data[10] - '0') * 10 + (data[11] - '0');
+		if (unlikely(tm.tm_sec > 59))
+			return L_TIME_INVALID;
+	}
+
+	/* RFC5280 Section 4.1.2.5.1 */
+	if (tag == ASN1_ID_UTCTIME && tm.tm_year < 50)
+		century = 20;
+
+	tm.tm_year += (century - 19) * 100;
+
+	/* Month number is 1-based in UTCTime and 0-based in struct tm */
+	tm.tm_mon -= 1;
+
+	tt = timegm(&tm);
+	if (unlikely(tt == (time_t) -1))
+		return L_TIME_INVALID;
+
+	if (len == i + 5) {
+		data += i;
+
+		for (i = 1; i < 5; i++)
+			if (unlikely(!l_ascii_isdigit(data[i])))
+				return L_TIME_INVALID;
+
+		tz_hours = (data[1] - '0') * 10 + (data[2] - '0');
+		tz_mins = (data[3] - '0') * 10 + (data[4] - '0');
+
+		if (unlikely(tz_hours > 14 || tz_mins > 59))
+			return L_TIME_INVALID;
+
+		/* The sign converts UTC to local so invert it */
+		if (data[0] == '+')
+			tt -= tz_hours * 3600 + tz_mins * 60;
+		else
+			tt += tz_hours * 3600 + tz_mins * 60;
+	}
+
+	return (uint64_t) tt * L_USEC_PER_SEC + msecs * L_USEC_PER_MSEC;
+}
+
+LIB_EXPORT bool l_cert_get_valid_times(struct l_cert *cert,
+					uint64_t *out_not_before_time,
+					uint64_t *out_not_after_time)
+{
+	const uint8_t *validity;
+	const uint8_t *not_before;
+	const uint8_t *not_after;
+	size_t seq_size;
+	size_t not_before_size;
+	size_t not_after_size;
+	uint8_t not_before_tag;
+	uint8_t not_after_tag;
+	uint64_t not_before_time = 0;
+	uint64_t not_after_time = 0;
+
+	if (unlikely(!cert))
+		return false;
+
+	validity = asn1_der_find_elem_by_path(cert->asn1, cert->asn1_len,
+						ASN1_ID_SEQUENCE, &seq_size,
+						X509_CERTIFICATE_POS,
+						X509_TBSCERTIFICATE_POS,
+						X509_TBSCERT_VALIDITY_POS,
+						-1);
+	if (unlikely(!validity))
+		return false;
+
+	not_before = asn1_der_find_elem(validity, seq_size, 0, &not_before_tag,
+					&not_before_size);
+	if (!not_before)
+		return false;
+
+	seq_size -= not_before_size + (not_before - validity);
+	validity = not_before + not_before_size;
+	not_after = asn1_der_find_elem(validity, seq_size, 0, &not_after_tag,
+					&not_after_size);
+	if (!not_after)
+		return false;
+
+	if (out_not_before_time) {
+		not_before_time = cert_parse_asn1_time(not_before,
+							not_before_size,
+							not_before_tag);
+		if (not_before_time == L_TIME_INVALID)
+			return false;
+	}
+
+	if (out_not_after_time) {
+		/*
+		 * RFC5280 Section 4.1.2.5: "To indicate that a certificate
+		 * has no well-defined expiration date, the notAfter SHOULD
+		 * be assigned the GeneralizedTime value of 99991231235959Z."
+		 */
+		if (not_after_size == 15 &&
+				!memcmp(not_after, "99991231235959Z", 15))
+			not_after_time = 0;
+		else {
+			not_after_time = cert_parse_asn1_time(not_after,
+								not_after_size,
+								not_after_tag);
+			if (not_after_time == L_TIME_INVALID)
+				return false;
+		}
+	}
+
+	if (out_not_before_time)
+		*out_not_before_time = not_before_time;
+
+	if (out_not_after_time)
+		*out_not_after_time = not_after_time;
+
+	return true;
+}
+
 const uint8_t *cert_get_extension(struct l_cert *cert,
 					const struct asn1_oid *ext_id,
 					bool *out_critical, size_t *out_len)
diff --git a/ell/cert.h b/ell/cert.h
index f637588..db116e0 100644
--- a/ell/cert.h
+++ b/ell/cert.h
@@ -48,6 +48,8 @@ DEFINE_CLEANUP_FUNC(l_cert_free);
 
 const uint8_t *l_cert_get_der_data(struct l_cert *cert, size_t *out_len);
 const uint8_t *l_cert_get_dn(struct l_cert *cert, size_t *out_len);
+bool l_cert_get_valid_times(struct l_cert *cert, uint64_t *out_not_before_time,
+				uint64_t *out_not_after_time);
 enum l_cert_key_type l_cert_get_pubkey_type(struct l_cert *cert);
 struct l_key *l_cert_get_pubkey(struct l_cert *cert);
 
diff --git a/ell/ell.sym b/ell/ell.sym
index 6df9024..5fe8d6e 100644
--- a/ell/ell.sym
+++ b/ell/ell.sym
@@ -553,6 +553,7 @@ global:
 	l_cert_free;
 	l_cert_get_der_data;
 	l_cert_get_dn;
+	l_cert_get_valid_times;
 	l_cert_get_pubkey_type;
 	l_cert_get_pubkey;
 	l_certchain_free;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 3/6] tls: Fix an RFC reference
  2022-10-26 13:15 [PATCH 1/6] time: Add time_realtime_now Andrew Zaborowski
  2022-10-26 13:15 ` [PATCH 2/6] cert: Add l_cert_get_valid_times Andrew Zaborowski
@ 2022-10-26 13:15 ` Andrew Zaborowski
  2022-10-26 13:15 ` [PATCH 4/6] tls: Add support for caching client session states Andrew Zaborowski
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Andrew Zaborowski @ 2022-10-26 13:15 UTC (permalink / raw)
  To: ell

---
 ell/tls-extensions.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ell/tls-extensions.c b/ell/tls-extensions.c
index 6154df0..7796825 100644
--- a/ell/tls-extensions.c
+++ b/ell/tls-extensions.c
@@ -785,7 +785,7 @@ ssize_t tls_parse_signature_algorithms(struct l_tls *tls,
 	return ptr + len - buf;
 }
 
-/* RFC 5462, Section 7.4.1.4.1 */
+/* RFC 5246, Section 7.4.1.4.1 */
 static ssize_t tls_signature_algorithms_client_write(struct l_tls *tls,
 						uint8_t *buf, size_t len)
 {
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 4/6] tls: Add support for caching client session states
  2022-10-26 13:15 [PATCH 1/6] time: Add time_realtime_now Andrew Zaborowski
  2022-10-26 13:15 ` [PATCH 2/6] cert: Add l_cert_get_valid_times Andrew Zaborowski
  2022-10-26 13:15 ` [PATCH 3/6] tls: Fix an RFC reference Andrew Zaborowski
@ 2022-10-26 13:15 ` Andrew Zaborowski
  2022-10-26 13:15 ` [PATCH 5/6] tls: Client session resumption Andrew Zaborowski
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Andrew Zaborowski @ 2022-10-26 13:15 UTC (permalink / raw)
  To: ell

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.
---
 ell/ell.sym       |   1 +
 ell/tls-private.h |  10 +++
 ell/tls.c         | 192 +++++++++++++++++++++++++++++++++++++++++++++-
 ell/tls.h         |   7 ++
 4 files changed, 208 insertions(+), 2 deletions(-)

diff --git a/ell/ell.sym b/ell/ell.sym
index 5fe8d6e..6c836e1 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 8941e90..6f09f6a 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 fa3df5d..ff4fa5b 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 a4fd414..92d8b9e 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;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 5/6] tls: Client session resumption
  2022-10-26 13:15 [PATCH 1/6] time: Add time_realtime_now Andrew Zaborowski
                   ` (2 preceding siblings ...)
  2022-10-26 13:15 ` [PATCH 4/6] tls: Add support for caching client session states Andrew Zaborowski
@ 2022-10-26 13:15 ` Andrew Zaborowski
  2022-10-26 13:15 ` [PATCH 6/6] examples: Cache sessions in https-client-test Andrew Zaborowski
  2022-10-28 18:27 ` [PATCH 1/6] time: Add time_realtime_now Denis Kenzior
  5 siblings, 0 replies; 7+ messages in thread
From: Andrew Zaborowski @ 2022-10-26 13:15 UTC (permalink / raw)
  To: ell

In client mode load a stored session state from the cache in l_tls_start
and attempt to resume that session.
---
 ell/tls-private.h |   3 +
 ell/tls-suites.c  |   4 +-
 ell/tls.c         | 316 +++++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 286 insertions(+), 37 deletions(-)

diff --git a/ell/tls-private.h b/ell/tls-private.h
index 6f09f6a..7156666 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 ee4e7ee..8cbcb63 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 ff4fa5b..88ac3d7 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;
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH 6/6] examples: Cache sessions in https-client-test
  2022-10-26 13:15 [PATCH 1/6] time: Add time_realtime_now Andrew Zaborowski
                   ` (3 preceding siblings ...)
  2022-10-26 13:15 ` [PATCH 5/6] tls: Client session resumption Andrew Zaborowski
@ 2022-10-26 13:15 ` Andrew Zaborowski
  2022-10-28 18:27 ` [PATCH 1/6] time: Add time_realtime_now Denis Kenzior
  5 siblings, 0 replies; 7+ messages in thread
From: Andrew Zaborowski @ 2022-10-26 13:15 UTC (permalink / raw)
  To: ell

If the environment variable TLS_CACHE is set, use
l_tls_set_session_cache() to enable session resumption.
---
 examples/https-client-test.c | 48 ++++++++++++++++++++++++++++++++++++
 1 file changed, 48 insertions(+)

diff --git a/examples/https-client-test.c b/examples/https-client-test.c
index b0c24b4..2c6939a 100644
--- a/examples/https-client-test.c
+++ b/examples/https-client-test.c
@@ -32,13 +32,18 @@
 #include <unistd.h>
 #include <errno.h>
 #include <arpa/inet.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 
 #include <ell/ell.h>
+#include <ell/useful.h>
 
 static struct l_io *io;
 static struct l_tls *tls;
 static const char *hostname;
 static bool ready;
+static struct l_settings *session_cache;
+static char *session_cache_path;
 
 static void https_io_disconnect(struct l_io *io, void *user_data)
 {
@@ -127,6 +132,27 @@ static void https_tls_debug_cb(const char *str, void *user_data)
 	printf("%s\n", str);
 }
 
+static void https_tls_session_cache_update_cb(void *user_data)
+{
+	size_t len;
+	char *data = l_settings_to_data(session_cache, &len);
+	_auto_(close) int fd = L_TFR(creat(session_cache_path, 0600));
+
+	if (!data) {
+		fprintf(stderr, "l_settings_to_data() failed\n");
+		return;
+	}
+
+	if (fd < 0) {
+		fprintf(stderr, "can't open %s: %s\n",
+			session_cache_path, strerror(errno));
+		return;
+	}
+
+	if (L_TFR(write(fd, data, len)) < (ssize_t) len)
+		fprintf(stderr, "short write to %s\n", session_cache_path);
+}
+
 int main(int argc, char *argv[])
 {
 	struct hostent *he;
@@ -200,6 +226,23 @@ int main(int argc, char *argv[])
 		l_free(str);
 	}
 
+	if (getenv("TLS_CACHE")) {
+		const char *homedir = getenv("HOME");
+
+		if (!homedir)
+			homedir = "/tmp";
+
+		session_cache_path =
+			l_strdup_printf("%s/.ell-https-client-test", homedir);
+		session_cache = l_settings_new();
+		l_settings_load_from_file(session_cache, session_cache_path);
+
+		l_tls_set_session_cache(tls, session_cache, hostname,
+					24 * 3600 * L_USEC_PER_SEC,
+					https_tls_session_cache_update_cb,
+					NULL);
+	}
+
 	if (argc >= 3) {
 		ca_cert = l_pem_load_certificate_list(argv[2]);
 		if (!ca_cert) {
@@ -244,6 +287,11 @@ int main(int argc, char *argv[])
 	l_io_destroy(io);
 	l_tls_free(tls);
 
+	if (session_cache) {
+		l_settings_free(session_cache);
+		l_free(session_cache_path);
+	}
+
 	l_main_exit();
 
 	return 0;
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [PATCH 1/6] time: Add time_realtime_now
  2022-10-26 13:15 [PATCH 1/6] time: Add time_realtime_now Andrew Zaborowski
                   ` (4 preceding siblings ...)
  2022-10-26 13:15 ` [PATCH 6/6] examples: Cache sessions in https-client-test Andrew Zaborowski
@ 2022-10-28 18:27 ` Denis Kenzior
  5 siblings, 0 replies; 7+ messages in thread
From: Denis Kenzior @ 2022-10-28 18:27 UTC (permalink / raw)
  To: Andrew Zaborowski, ell

Hi Andrew,

On 10/26/22 08:15, Andrew Zaborowski wrote:
> ---
>   ell/time-private.h | 1 +
>   ell/time.c         | 8 ++++++++
>   2 files changed, 9 insertions(+)
> 

All applied, thanks.

Regards,
-Denis


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2022-10-28 18:27 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-26 13:15 [PATCH 1/6] time: Add time_realtime_now Andrew Zaborowski
2022-10-26 13:15 ` [PATCH 2/6] cert: Add l_cert_get_valid_times Andrew Zaborowski
2022-10-26 13:15 ` [PATCH 3/6] tls: Fix an RFC reference Andrew Zaborowski
2022-10-26 13:15 ` [PATCH 4/6] tls: Add support for caching client session states Andrew Zaborowski
2022-10-26 13:15 ` [PATCH 5/6] tls: Client session resumption Andrew Zaborowski
2022-10-26 13:15 ` [PATCH 6/6] examples: Cache sessions in https-client-test Andrew Zaborowski
2022-10-28 18:27 ` [PATCH 1/6] time: Add time_realtime_now Denis Kenzior

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).