From b15a1bafb8ef1f4436ad2168eb795db3f85723ae Mon Sep 17 00:00:00 2001 From: Mark Rousskov Date: Wed, 4 Oct 2023 10:09:20 -0400 Subject: [PATCH] Add support for exporting symmetric keys from connections (#4230) This is currently only supported for TLS 1.3 and is standard compliant, using the TLS-Exporter function defined by RFC 8446: https://www.rfc-editor.org/rfc/rfc8446#section-7.5 --- api/s2n.h | 14 +++ bindings/rust/s2n-tls/src/connection.rs | 26 +++++ crypto/s2n_hkdf.c | 5 +- crypto/s2n_hkdf.h | 10 ++ crypto/s2n_tls13_keys.c | 7 +- crypto/s2n_tls13_keys.h | 2 + tests/unit/s2n_self_talk_key_log_test.c | 1 + tests/unit/s2n_self_talk_quic_support_test.c | 2 +- tests/unit/s2n_tls13_key_schedule_test.c | 2 +- tests/unit/s2n_tls13_secrets_rfc8448_test.c | 44 ++++++++ tests/unit/s2n_tls13_secrets_test.c | 56 ++++++++++ tls/s2n_key_log.c | 5 + tls/s2n_quic_support.h | 1 + tls/s2n_tls13_secrets.c | 110 ++++++++++++++++++- tls/s2n_tls13_secrets.h | 2 + 15 files changed, 275 insertions(+), 12 deletions(-) diff --git a/api/s2n.h b/api/s2n.h index 5f840c2823c..462972e86bf 100644 --- a/api/s2n.h +++ b/api/s2n.h @@ -2870,6 +2870,20 @@ S2N_API extern int s2n_connection_client_cert_used(struct s2n_connection *conn); */ S2N_API extern const char *s2n_connection_get_cipher(struct s2n_connection *conn); +/** + * Provides access to the TLS-Exporter functionality. + * + * See https://datatracker.ietf.org/doc/html/rfc5705 and https://www.rfc-editor.org/rfc/rfc8446. + * + * @note This is currently only available with TLS 1.3 connections which have finished a handshake. + * + * @param conn A pointer to the connection + * @returns A POSIX error signal. If an error was returned, the value contained in `output` should be considered invalid. + */ +S2N_API extern int s2n_connection_tls_exporter(struct s2n_connection *conn, + const uint8_t *label, uint32_t label_length, const uint8_t *context, uint32_t context_length, + uint8_t *output, uint32_t output_length); + /** * Returns the IANA value for the connection's negotiated cipher suite. * diff --git a/bindings/rust/s2n-tls/src/connection.rs b/bindings/rust/s2n-tls/src/connection.rs index 0b16b323b81..4e5f8b91ea3 100644 --- a/bindings/rust/s2n-tls/src/connection.rs +++ b/bindings/rust/s2n-tls/src/connection.rs @@ -809,6 +809,32 @@ impl Connection { hash_alg => Some(hash_alg.try_into()?), }) } + + /// Provides access to the TLS-Exporter functionality. + /// + /// See https://datatracker.ietf.org/doc/html/rfc5705 and https://www.rfc-editor.org/rfc/rfc8446. + /// + /// This is currently only available with TLS 1.3 connections which have finished a handshake. + pub fn tls_exporter( + &self, + label: &[u8], + context: &[u8], + output: &mut [u8], + ) -> Result<(), Error> { + unsafe { + s2n_connection_tls_exporter( + self.connection.as_ptr(), + label.as_ptr(), + label.len().try_into().map_err(|_| Error::INVALID_INPUT)?, + context.as_ptr(), + context.len().try_into().map_err(|_| Error::INVALID_INPUT)?, + output.as_mut_ptr(), + output.len().try_into().map_err(|_| Error::INVALID_INPUT)?, + ) + .into_result() + .map(|_| ()) + } + } } struct Context { diff --git a/crypto/s2n_hkdf.c b/crypto/s2n_hkdf.c index 2778fbcef60..c5cb1874fd6 100644 --- a/crypto/s2n_hkdf.c +++ b/crypto/s2n_hkdf.c @@ -257,10 +257,7 @@ int s2n_hkdf_expand_label(struct s2n_hmac_state *hmac, s2n_hmac_algorithm alg, c struct s2n_blob hkdf_label_blob = { 0 }; struct s2n_stuffer hkdf_label = { 0 }; - /* RFC8446 specifies that labels must be 12 characters or less, to avoid - ** incurring two hash rounds. - */ - POSIX_ENSURE_LTE(label->size, 12); + POSIX_ENSURE_LTE(label->size, S2N_MAX_HKDF_EXPAND_LABEL_LENGTH); POSIX_GUARD(s2n_blob_init(&hkdf_label_blob, hkdf_label_buf, sizeof(hkdf_label_buf))); POSIX_GUARD(s2n_stuffer_init(&hkdf_label, &hkdf_label_blob)); diff --git a/crypto/s2n_hkdf.h b/crypto/s2n_hkdf.h index c8072528b35..8dc53636e77 100644 --- a/crypto/s2n_hkdf.h +++ b/crypto/s2n_hkdf.h @@ -20,6 +20,16 @@ #include "crypto/s2n_hmac.h" #include "utils/s2n_blob.h" +/* + * Label structure is `opaque label<7..255> = "tls13 " + Label` per RFC8446. + * So, we have 255-sizeof("tls13 ") = 249, the maximum label length. + * + * Note that all labels defined by RFC 8446 are <12 characters, which + * avoids an extra hash iteration. However, the exporter functionality + * (s2n_connection_tls_exporter) allows for longer labels. + */ +#define S2N_MAX_HKDF_EXPAND_LABEL_LENGTH 249 + int s2n_hkdf(struct s2n_hmac_state *hmac, s2n_hmac_algorithm alg, const struct s2n_blob *salt, const struct s2n_blob *key, const struct s2n_blob *info, struct s2n_blob *output); diff --git a/crypto/s2n_tls13_keys.c b/crypto/s2n_tls13_keys.c index 3b5c2840804..91d7aae5e00 100644 --- a/crypto/s2n_tls13_keys.c +++ b/crypto/s2n_tls13_keys.c @@ -37,7 +37,7 @@ * [x] server_handshake_traffic_secret * [x] client_application_traffic_secret_0 * [x] server_application_traffic_secret_0 - * [ ] exporter_master_secret + * [x] exporter_master_secret * [x] resumption_master_secret * * The TLS 1.3 key generation can be divided into 3 phases @@ -79,6 +79,11 @@ S2N_BLOB_LABEL(s2n_tls13_label_session_ticket_secret, "resumption") S2N_BLOB_LABEL(s2n_tls13_label_traffic_secret_key, "key") S2N_BLOB_LABEL(s2n_tls13_label_traffic_secret_iv, "iv") +/* + * TLS 1.3 Exporter label + */ +S2N_BLOB_LABEL(s2n_tls13_label_exporter, "exporter") + /* * TLS 1.3 Finished label */ diff --git a/crypto/s2n_tls13_keys.h b/crypto/s2n_tls13_keys.h index 5bd7455dc2c..ac96ceb51fe 100644 --- a/crypto/s2n_tls13_keys.h +++ b/crypto/s2n_tls13_keys.h @@ -70,6 +70,8 @@ extern const struct s2n_blob s2n_tls13_label_resumption_master_secret; extern const struct s2n_blob s2n_tls13_label_finished; +extern const struct s2n_blob s2n_tls13_label_exporter; + /* Traffic secret labels */ extern const struct s2n_blob s2n_tls13_label_traffic_secret_key; diff --git a/tests/unit/s2n_self_talk_key_log_test.c b/tests/unit/s2n_self_talk_key_log_test.c index 84e873cc4c0..319d7d6dcf7 100644 --- a/tests/unit/s2n_self_talk_key_log_test.c +++ b/tests/unit/s2n_self_talk_key_log_test.c @@ -56,6 +56,7 @@ S2N_RESULT s2n_test_check_tls13(struct s2n_stuffer *stuffer) RESULT_ENSURE_REF(strstr(out, "SERVER_HANDSHAKE_TRAFFIC_SECRET ")); RESULT_ENSURE_REF(strstr(out, "CLIENT_TRAFFIC_SECRET_0 ")); RESULT_ENSURE_REF(strstr(out, "SERVER_TRAFFIC_SECRET_0 ")); + RESULT_ENSURE_REF(strstr(out, "EXPORTER_SECRET ")); return S2N_RESULT_OK; } diff --git a/tests/unit/s2n_self_talk_quic_support_test.c b/tests/unit/s2n_self_talk_quic_support_test.c index 9e94226a8bb..7099cadc7af 100644 --- a/tests/unit/s2n_self_talk_quic_support_test.c +++ b/tests/unit/s2n_self_talk_quic_support_test.c @@ -18,7 +18,7 @@ #include "tls/s2n_quic_support.h" #define S2N_MODE_COUNT 2 -#define S2N_SECRET_TYPE_COUNT 5 +#define S2N_SECRET_TYPE_COUNT 6 static const uint8_t CLIENT_TRANSPORT_PARAMS[] = "client transport params"; static const uint8_t SERVER_TRANSPORT_PARAMS[] = "server transport params"; diff --git a/tests/unit/s2n_tls13_key_schedule_test.c b/tests/unit/s2n_tls13_key_schedule_test.c index fb7d3d4a55b..0cae551c952 100644 --- a/tests/unit/s2n_tls13_key_schedule_test.c +++ b/tests/unit/s2n_tls13_key_schedule_test.c @@ -24,7 +24,7 @@ #include "tls/s2n_tls13_secrets.c" #define NO_TRIGGER_MSG APPLICATION_DATA -#define TRAFFIC_SECRET_COUNT 5 +#define TRAFFIC_SECRET_COUNT 6 static uint8_t empty_secret[S2N_TLS13_SECRET_MAX_LEN] = { 0 }; diff --git a/tests/unit/s2n_tls13_secrets_rfc8448_test.c b/tests/unit/s2n_tls13_secrets_rfc8448_test.c index 6c26edc05f8..12a8783ae58 100644 --- a/tests/unit/s2n_tls13_secrets_rfc8448_test.c +++ b/tests/unit/s2n_tls13_secrets_rfc8448_test.c @@ -498,6 +498,50 @@ int main(int argc, char **argv) secret.data, secret.size); } }; + + /* Derive EXPORTER_MASTER_SECRET */ + { + /** + *= https://www.rfc-editor.org/rfc/rfc8448.html#section-3 + *= type=test + *# {client} derive secret "tls13 exp master" (same as server) + * + *= https://www.rfc-editor.org/rfc/rfc8448.html#section-3 + *= type=test + *# {server} derive secret "tls13 exp master": + *# + *# PRK (32 octets): 18 df 06 84 3d 13 a0 8b f2 a4 49 84 4c 5f 8a 47 + *# 80 01 bc 4d 4c 62 79 84 d5 a4 1d a8 d0 40 29 19 + *# + *# hash (32 octets): 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a + *# 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + *# + ** + *= https://www.rfc-editor.org/rfc/rfc8448.html#section-3 + *= type=test + *# info (52 octets): 00 20 10 74 6c 73 31 33 20 65 78 70 20 6d 61 73 + *# 74 65 72 20 96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a 00 + *# 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13 + *# + *# expanded (32 octets): fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 + *# 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50 + */ + S2N_BLOB_FROM_HEX(hash, "96 08 10 2a 0f 1c cc 6d b6 25 0b 7b 7e 41 7b 1a \ + 00 0e aa da 3d aa e4 77 7a 76 86 c9 ff 83 df 13"); + S2N_BLOB_FROM_HEX(secret, "fe 22 f8 81 17 6e da 18 eb 8f 44 52 9e 67 \ + 92 c5 0c 9a 3f 89 45 2f 68 d8 ae 31 1b 43 09 d3 cf 50"); + + for (size_t i = 0; i < s2n_array_len(modes); i++) { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(modes[i]), s2n_connection_ptr_free); + conn->secure->cipher_suite = cipher_suite; + EXPECT_OK(s2n_connection_set_test_master_secret(conn, &master_secret)); + EXPECT_OK(s2n_connection_set_test_transcript_hash(conn, SERVER_FINISHED, &hash)); + + EXPECT_OK(s2n_derive_exporter_master_secret(conn, &derived_secret)); + EXPECT_EQUAL(derived_secret.size, secret.size); + EXPECT_BYTEARRAY_EQUAL(derived_secret.data, secret.data, secret.size); + } + }; }; /* Resumed 0-RTT Handshake */ diff --git a/tests/unit/s2n_tls13_secrets_test.c b/tests/unit/s2n_tls13_secrets_test.c index 4b86c0e09c6..32779e94cb4 100644 --- a/tests/unit/s2n_tls13_secrets_test.c +++ b/tests/unit/s2n_tls13_secrets_test.c @@ -281,6 +281,7 @@ int main(int argc, char **argv) EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.server_handshake_secret, test_secret.data, test_secret.size); EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.client_app_secret, test_secret.data, test_secret.size); EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.server_app_secret, test_secret.data, test_secret.size); + EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.exporter_master_secret, test_secret.data, test_secret.size); EXPECT_MEMCPY_SUCCESS(conn->secrets.version.tls13.resumption_master_secret, test_secret.data, test_secret.size); EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.extract_secret, empty_secret, sizeof(empty_secret)); @@ -289,6 +290,7 @@ int main(int argc, char **argv) EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.server_handshake_secret, empty_secret, sizeof(empty_secret)); EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.client_app_secret, empty_secret, sizeof(empty_secret)); EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.server_app_secret, empty_secret, sizeof(empty_secret)); + EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.exporter_master_secret, empty_secret, sizeof(empty_secret)); EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.resumption_master_secret, empty_secret, sizeof(empty_secret)); EXPECT_OK(s2n_tls13_secrets_clean(conn)); @@ -299,6 +301,7 @@ int main(int argc, char **argv) EXPECT_BYTEARRAY_EQUAL(conn->secrets.version.tls13.server_handshake_secret, empty_secret, sizeof(empty_secret)); EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.client_app_secret, empty_secret, sizeof(empty_secret)); EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.server_app_secret, empty_secret, sizeof(empty_secret)); + EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.exporter_master_secret, empty_secret, sizeof(empty_secret)); EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.resumption_master_secret, empty_secret, sizeof(empty_secret)); }; }; @@ -488,5 +491,58 @@ int main(int argc, char **argv) }; }; + /* s2n_connection_export_secret */ + { + /* Derives exporter secret on SERVER_FINISHED */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + conn->secure->cipher_suite = &s2n_tls13_aes_128_gcm_sha256; + conn->actual_protocol_version = S2N_TLS13; + EXPECT_OK(s2n_connection_set_test_master_secret(conn, &test_secret)); + EXPECT_BYTEARRAY_EQUAL(conn->secrets.version.tls13.exporter_master_secret, + empty_secret, sizeof(empty_secret)); + + while (s2n_conn_get_current_message_type(conn) != SERVER_FINISHED) { + conn->handshake.message_number++; + } + EXPECT_OK(s2n_tls13_secrets_update(conn)); + + EXPECT_BYTEARRAY_NOT_EQUAL(conn->secrets.version.tls13.exporter_master_secret, + empty_secret, sizeof(empty_secret)); + + /* + * s2n_connection_tls_exporter requires us to finish the handshake. + * The above is needed since s2n_tls13_secrets_update will only + * initialize when it sees the SERVER_FINISHED frame. + */ + EXPECT_OK(s2n_skip_handshake(conn)); + + uint8_t output[32] = { 0 }; + int result = s2n_connection_tls_exporter( + conn, + (const uint8_t *) "label", + sizeof("label") - 1, + (const uint8_t *) "context", + sizeof("context") - 1, + output, + sizeof(output)); + EXPECT_SUCCESS(result); + /* + * If updating this value, it's a good idea to make sure the update + * matches OpenSSL's SSL_export_keying_material. The easiest known + * way of doing that is building a simple client/server pair and + * calling the s2n and OpenSSL APIs after a handshake on both + * sides; you should get identical results with identical + * label/context parameters. This particular value though is not + * checked as its dependent on the s2n-specific test master secret. + */ + S2N_BLOB_FROM_HEX(expected, "3a 72 eb 08 10 a3 69 f3 06 f2 77 11 70 ad d5 76 bd 21 15 \ + 46 d4 c8 fb 80 1a 93 04 1e ac 59 aa 47"); + EXPECT_EQUAL(sizeof(output), expected.size); + EXPECT_BYTEARRAY_EQUAL(output, expected.data, expected.size); + }; + }; + END_TEST(); } diff --git a/tls/s2n_key_log.c b/tls/s2n_key_log.c index 0ea0ed182f3..75ef3db618f 100644 --- a/tls/s2n_key_log.c +++ b/tls/s2n_key_log.c @@ -84,6 +84,7 @@ S2N_RESULT s2n_key_log_tls13_secret(struct s2n_connection *conn, const struct s2 const uint8_t server_handshake_label[] = "SERVER_HANDSHAKE_TRAFFIC_SECRET "; const uint8_t client_traffic_label[] = "CLIENT_TRAFFIC_SECRET_0 "; const uint8_t server_traffic_label[] = "SERVER_TRAFFIC_SECRET_0 "; + const uint8_t exporter_secret_label[] = "EXPORTER_SECRET "; const uint8_t *label = NULL; uint8_t label_size = 0; @@ -109,6 +110,10 @@ S2N_RESULT s2n_key_log_tls13_secret(struct s2n_connection *conn, const struct s2 label = server_traffic_label; label_size = sizeof(server_traffic_label) - 1; break; + case S2N_EXPORTER_SECRET: + label = exporter_secret_label; + label_size = sizeof(exporter_secret_label) - 1; + break; default: /* Ignore the secret types we don't understand */ return S2N_RESULT_OK; diff --git a/tls/s2n_quic_support.h b/tls/s2n_quic_support.h index a6ffe682351..04e8165e4e5 100644 --- a/tls/s2n_quic_support.h +++ b/tls/s2n_quic_support.h @@ -56,6 +56,7 @@ typedef enum { S2N_SERVER_HANDSHAKE_TRAFFIC_SECRET, S2N_CLIENT_APPLICATION_TRAFFIC_SECRET, S2N_SERVER_APPLICATION_TRAFFIC_SECRET, + S2N_EXPORTER_SECRET, } s2n_secret_type_t; /* diff --git a/tls/s2n_tls13_secrets.c b/tls/s2n_tls13_secrets.c index 3914a04461a..12a3b79f9fe 100644 --- a/tls/s2n_tls13_secrets.c +++ b/tls/s2n_tls13_secrets.c @@ -219,6 +219,20 @@ static S2N_RESULT s2n_tls13_compute_finished_key(struct s2n_connection *conn, return S2N_RESULT_OK; } +static S2N_RESULT s2n_call_secret_callbacks(struct s2n_connection *conn, + const struct s2n_blob *secret, s2n_secret_type_t secret_type) +{ + RESULT_ENSURE_REF(conn); + RESULT_ENSURE_REF(secret); + + if (conn->secret_cb && (s2n_connection_is_quic_enabled(conn) || s2n_in_unit_test())) { + RESULT_GUARD_POSIX(conn->secret_cb(conn->secret_cb_context, conn, secret_type, + secret->data, secret->size)); + } + s2n_result_ignore(s2n_key_log_tls13_secret(conn, secret, secret_type)); + return S2N_RESULT_OK; +} + static S2N_RESULT s2n_trigger_secret_callbacks(struct s2n_connection *conn, const struct s2n_blob *secret, s2n_extract_secret_type_t secret_type, s2n_mode mode) { @@ -232,11 +246,7 @@ static S2N_RESULT s2n_trigger_secret_callbacks(struct s2n_connection *conn, }; s2n_secret_type_t callback_secret_type = conversions[secret_type][mode]; - if (conn->secret_cb && (s2n_connection_is_quic_enabled(conn) || s2n_in_unit_test())) { - RESULT_GUARD_POSIX(conn->secret_cb(conn->secret_cb_context, conn, callback_secret_type, - secret->data, secret->size)); - } - s2n_result_ignore(s2n_key_log_tls13_secret(conn, secret, callback_secret_type)); + RESULT_GUARD(s2n_call_secret_callbacks(conn, secret, callback_secret_type)); return S2N_RESULT_OK; } @@ -520,6 +530,31 @@ S2N_RESULT s2n_derive_resumption_master_secret(struct s2n_connection *conn) return S2N_RESULT_OK; } +/** + *= https://tools.ietf.org/rfc/rfc8446#section-7.1 + *# | + *# +-----> Derive-Secret(., "exp master", + *# | ClientHello...server Finished) + *# | = exporter_master_secret + */ +S2N_RESULT s2n_derive_exporter_master_secret(struct s2n_connection *conn, struct s2n_blob *secret) +{ + RESULT_ENSURE_REF(conn); + /* Secret derivation requires these fields to be non-null. */ + RESULT_ENSURE_REF(conn->secure); + RESULT_ENSURE_REF(conn->secure->cipher_suite); + + RESULT_GUARD(s2n_derive_secret_with_context(conn, + S2N_MASTER_SECRET, + &s2n_tls13_label_exporter_master_secret, + SERVER_FINISHED, + secret)); + + RESULT_GUARD(s2n_call_secret_callbacks(conn, secret, S2N_EXPORTER_SECRET)); + + return S2N_RESULT_OK; +} + static s2n_result (*extract_methods[])(struct s2n_connection *conn) = { [S2N_EARLY_SECRET] = &s2n_extract_early_secret_for_schedule, [S2N_HANDSHAKE_SECRET] = &s2n_extract_handshake_secret, @@ -636,6 +671,8 @@ S2N_RESULT s2n_tls13_secrets_update(struct s2n_connection *conn) S2N_CLIENT, &CONN_SECRET(conn, client_app_secret))); RESULT_GUARD(s2n_tls13_derive_secret(conn, S2N_MASTER_SECRET, S2N_SERVER, &CONN_SECRET(conn, server_app_secret))); + RESULT_GUARD(s2n_derive_exporter_master_secret(conn, + &CONN_SECRET(conn, exporter_master_secret))); break; case CLIENT_FINISHED: RESULT_GUARD(s2n_calculate_transcript_digest(conn)); @@ -671,3 +708,66 @@ S2N_RESULT s2n_tls13_secrets_get(struct s2n_connection *conn, s2n_extract_secret RESULT_ENSURE_GT(secret->size, 0); return S2N_RESULT_OK; } + +/* + *= https://www.rfc-editor.org/rfc/rfc8446#section-7.5 + *# The exporter value is computed as: + *# + *# TLS-Exporter(label, context_value, key_length) = + *# HKDF-Expand-Label(Derive-Secret(Secret, label, ""), + *# "exporter", Hash(context_value), key_length) + */ +int s2n_connection_tls_exporter(struct s2n_connection *conn, + const uint8_t *label_in, uint32_t label_length, + const uint8_t *context, uint32_t context_length, + uint8_t *output_in, uint32_t output_length) +{ + POSIX_ENSURE_REF(conn); + POSIX_ENSURE_REF(output_in); + POSIX_ENSURE_REF(label_in); + POSIX_ENSURE_REF(context); + POSIX_ENSURE(s2n_connection_get_protocol_version(conn) == S2N_TLS13, S2N_ERR_INVALID_STATE); + + POSIX_ENSURE(is_handshake_complete(conn), S2N_ERR_HANDSHAKE_NOT_COMPLETE); + POSIX_ENSURE_REF(conn->secure); + POSIX_ENSURE_REF(conn->secure->cipher_suite); + s2n_hmac_algorithm hmac_alg = conn->secure->cipher_suite->prf_alg; + + uint8_t label_bytes[S2N_MAX_HKDF_EXPAND_LABEL_LENGTH] = { 0 }; + struct s2n_blob label = { 0 }; + POSIX_ENSURE_LTE(label_length, sizeof(label_bytes)); + POSIX_CHECKED_MEMCPY(label_bytes, label_in, label_length); + POSIX_GUARD(s2n_blob_init(&label, label_bytes, label_length)); + + uint8_t derived_secret_bytes[S2N_MAX_DIGEST_LEN] = { 0 }; + struct s2n_blob derived_secret = { 0 }; + POSIX_ENSURE_LTE(s2n_get_hash_len(CONN_HMAC_ALG(conn)), S2N_MAX_DIGEST_LEN); + POSIX_GUARD(s2n_blob_init(&derived_secret, + derived_secret_bytes, s2n_get_hash_len(CONN_HMAC_ALG(conn)))); + POSIX_GUARD_RESULT(s2n_derive_secret(hmac_alg, &CONN_SECRET(conn, exporter_master_secret), + &label, &EMPTY_CONTEXT(hmac_alg), &derived_secret)); + + DEFER_CLEANUP(struct s2n_hmac_state hmac_state = { 0 }, s2n_hmac_free); + POSIX_GUARD(s2n_hmac_new(&hmac_state)); + + DEFER_CLEANUP(struct s2n_hash_state hash = { 0 }, s2n_hash_free); + POSIX_GUARD(s2n_hash_new(&hash)); + + s2n_hash_algorithm hash_alg = { 0 }; + POSIX_GUARD(s2n_hmac_hash_alg(hmac_alg, &hash_alg)); + uint8_t digest_bytes[S2N_MAX_DIGEST_LEN] = { 0 }; + struct s2n_blob digest = { 0 }; + POSIX_ENSURE_LTE(s2n_get_hash_len(CONN_HMAC_ALG(conn)), S2N_MAX_DIGEST_LEN); + POSIX_GUARD(s2n_blob_init(&digest, digest_bytes, s2n_get_hash_len(CONN_HMAC_ALG(conn)))); + + POSIX_GUARD(s2n_hash_init(&hash, hash_alg)); + POSIX_GUARD(s2n_hash_update(&hash, context, context_length)); + POSIX_GUARD(s2n_hash_digest(&hash, digest.data, digest.size)); + + struct s2n_blob output = { 0 }; + POSIX_GUARD(s2n_blob_init(&output, output_in, output_length)); + POSIX_GUARD(s2n_hkdf_expand_label(&hmac_state, hmac_alg, + &derived_secret, &s2n_tls13_label_exporter, &digest, &output)); + + return S2N_SUCCESS; +} diff --git a/tls/s2n_tls13_secrets.h b/tls/s2n_tls13_secrets.h index b99f76df8fd..5f6cd140be2 100644 --- a/tls/s2n_tls13_secrets.h +++ b/tls/s2n_tls13_secrets.h @@ -40,6 +40,7 @@ struct s2n_tls13_secrets { uint8_t client_app_secret[S2N_TLS13_SECRET_MAX_LEN]; uint8_t server_app_secret[S2N_TLS13_SECRET_MAX_LEN]; uint8_t resumption_master_secret[S2N_TLS13_SECRET_MAX_LEN]; + uint8_t exporter_master_secret[S2N_TLS13_SECRET_MAX_LEN]; s2n_extract_secret_type_t extract_secret_type; }; @@ -53,3 +54,4 @@ S2N_RESULT s2n_tls13_secrets_clean(struct s2n_connection *conn); S2N_RESULT s2n_derive_binder_key(struct s2n_psk *psk, struct s2n_blob *output); S2N_RESULT s2n_derive_resumption_master_secret(struct s2n_connection *conn); +S2N_RESULT s2n_derive_exporter_master_secret(struct s2n_connection *conn, struct s2n_blob *output);