diff --git a/api/s2n.h b/api/s2n.h index 5f840c2823c..9615df73243 100644 --- a/api/s2n.h +++ b/api/s2n.h @@ -2870,6 +2870,19 @@ 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, uint8_t *output, uint32_t output_length, + const uint8_t *label_, uint32_t label_length, const uint8_t *context, uint32_t context_length); + /** * Returns the IANA value for the connection's negotiated cipher suite. * 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_tls13_secrets_rfc8448_test.c b/tests/unit/s2n_tls13_secrets_rfc8448_test.c index 6c26edc05f8..0a529521627 100644 --- a/tests/unit/s2n_tls13_secrets_rfc8448_test.c +++ b/tests/unit/s2n_tls13_secrets_rfc8448_test.c @@ -498,6 +498,52 @@ 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 + *# {server} derive secret "tls13 exp master": + + * + *= https://www.rfc-editor.org/rfc/rfc8448.html#section-3 + *= type=test + *# {client} derive secret "tls13 res 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)); + EXPECT_EQUAL(derived_secret.size, secret.size); + EXPECT_BYTEARRAY_EQUAL(conn->secrets.version.tls13.exporter_master_secret, + 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..3c94f9911cf 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,41 @@ 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.resumption_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)); + + uint8_t output[32]; + ssize_t result = s2n_connection_tls_exporter( + conn, + output, + sizeof(output), + (uint8_t *) "label", + sizeof("label"), + (uint8_t *) "context", + sizeof("context")); + EXPECT_SUCCESS(result); + S2N_BLOB_FROM_HEX(expected, "e8 39 08 03 29 9f 40 c5 51 04 46 74 ff \ + 37 42 2f 3a 0c e5 8c 45 f3 87 99 f3 e1 29 5c ce 6f f8 ca"); + EXPECT_BYTEARRAY_EQUAL(output, expected.data, expected.size); + }; + }; + END_TEST(); } diff --git a/tls/s2n_tls13_secrets.c b/tls/s2n_tls13_secrets.c index 3914a04461a..35beb81f1fe 100644 --- a/tls/s2n_tls13_secrets.c +++ b/tls/s2n_tls13_secrets.c @@ -520,6 +520,28 @@ 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) +{ + 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, + &CONN_SECRET(conn, exporter_master_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 +658,7 @@ 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)); break; case CLIENT_FINISHED: RESULT_GUARD(s2n_calculate_transcript_digest(conn)); @@ -671,3 +694,53 @@ S2N_RESULT s2n_tls13_secrets_get(struct s2n_connection *conn, s2n_extract_secret RESULT_ENSURE_GT(secret->size, 0); return S2N_RESULT_OK; } + +int s2n_connection_tls_exporter( + struct s2n_connection *conn, + uint8_t *output_, + uint32_t output_length, + const uint8_t *label_, + uint32_t label_length, + const uint8_t *context, + uint32_t context_length) +{ + POSIX_ENSURE_REF(conn); + POSIX_ENSURE_REF(output_); + POSIX_ENSURE_REF(label_); + POSIX_ENSURE_REF(context); + POSIX_ENSURE(s2n_connection_get_protocol_version(conn) == S2N_TLS13, S2N_ERR_INVALID_STATE); + + s2n_hmac_algorithm hmac_alg = conn->secure->cipher_suite->prf_alg; + + uint8_t size = 0; + if (s2n_hmac_digest_size(hmac_alg, &size) != S2N_SUCCESS) { + size = 0; + } + POSIX_ENSURE_GT(size, 0); + struct s2n_blob exporter_master_secret = (struct s2n_blob){ .data = conn->secrets.version.tls13.exporter_master_secret, .size = size }; + const struct s2n_blob label = (struct s2n_blob){ .data = (uint8_t *) label_, .size = label_length }; + + uint8_t derived_secret_bytes[S2N_TLS13_SECRET_MAX_LEN] = { 0 }; + struct s2n_blob derived_secret = { 0 }; + POSIX_GUARD(s2n_blob_init(&derived_secret, derived_secret_bytes, S2N_TLS13_SECRET_MAX_LEN)); + POSIX_GUARD_RESULT(s2n_derive_secret(hmac_alg, &exporter_master_secret, &label, &CONN_HASH(conn, transcript_hash_digest), &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; + POSIX_GUARD(s2n_hmac_hash_alg(hmac_alg, &hash_alg)); + struct s2n_blob digest = EMPTY_CONTEXT(hmac_alg); + + POSIX_GUARD(s2n_hash_init(&hash, hash_alg)); + POSIX_GUARD(s2n_hash_digest(&hash, digest.data, digest.size)); + + struct s2n_blob output = (struct s2n_blob){ .data = output_, .size = output_length }; + POSIX_GUARD(s2n_hkdf_expand_label(&hmac_state, hmac_alg, + &derived_secret, &s2n_tls13_label_exporter, &digest, &output)); + + return 0; +} diff --git a/tls/s2n_tls13_secrets.h b/tls/s2n_tls13_secrets.h index b99f76df8fd..9caf1b9f0fe 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);