From 87deea1928f3eb95464dcb5f1e0a1a24108ae67c Mon Sep 17 00:00:00 2001 From: maddeleine <59030281+maddeleine@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:10:22 -0700 Subject: [PATCH] feat: Changes ticket encryption scheme to be nonce-reuse resistant (#4663) --- bindings/rust/s2n-tls/src/config.rs | 8 ++ tests/unit/s2n_resume_test.c | 126 ++++++++++++++++++--------- tests/unit/s2n_session_ticket_test.c | 44 +++++++--- tls/s2n_resume.c | 61 ++++++++++++- tls/s2n_resume.h | 15 +++- tls/s2n_server_new_session_ticket.c | 2 +- 6 files changed, 197 insertions(+), 59 deletions(-) diff --git a/bindings/rust/s2n-tls/src/config.rs b/bindings/rust/s2n-tls/src/config.rs index 59e33175915..c7e6353347e 100644 --- a/bindings/rust/s2n-tls/src/config.rs +++ b/bindings/rust/s2n-tls/src/config.rs @@ -754,6 +754,14 @@ impl Builder { Ok(self) } + /// Requires that session tickets are only used when forward secrecy is possible + pub fn require_ticket_forward_secrecy(&mut self, enable: bool) -> Result<&mut Self, Error> { + unsafe { + s2n_config_require_ticket_forward_secrecy(self.as_mut_ptr(), enable).into_result() + }?; + Ok(self) + } + pub fn build(mut self) -> Result { if self.load_system_certs { unsafe { diff --git a/tests/unit/s2n_resume_test.c b/tests/unit/s2n_resume_test.c index c2dfc70baaf..b0c10223fbb 100644 --- a/tests/unit/s2n_resume_test.c +++ b/tests/unit/s2n_resume_test.c @@ -1291,13 +1291,8 @@ int main(int argc, char **argv) }; }; - /* s2n_resume_encrypt_session_ticket */ + /* s2n_resume_encrypt/decrypt_session_ticket */ { - /* Session ticket keys. Taken from test vectors in https://tools.ietf.org/html/rfc5869 */ - uint8_t ticket_key_name[16] = "2016.07.26.15\0"; - S2N_BLOB_FROM_HEX(ticket_key, - "077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5"); - /* Check error is thrown when no ticket key is available */ { DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), @@ -1312,16 +1307,11 @@ int main(int argc, char **argv) DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); EXPECT_NOT_NULL(config); - /* Adds a valid ticket encryption key */ - EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(config, 1)); - uint64_t current_time = 0; - EXPECT_SUCCESS(config->wall_clock(config->sys_clock_ctx, ¤t_time)); - EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(config, ticket_key_name, strlen((char *) ticket_key_name), - ticket_key.data, ticket_key.size, current_time / ONE_SEC_IN_NANOS)); - DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free); EXPECT_NOT_NULL(conn); + + EXPECT_OK(s2n_resumption_test_ticket_key_setup(config)); EXPECT_SUCCESS(s2n_connection_set_config(conn, config)); struct s2n_stuffer output = { 0 }; @@ -1332,15 +1322,10 @@ int main(int argc, char **argv) { struct s2n_connection *conn = NULL; struct s2n_config *config = NULL; - uint64_t current_time = 0; EXPECT_NOT_NULL(conn = s2n_connection_new(S2N_SERVER)); EXPECT_NOT_NULL(config = s2n_config_new()); - EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(config, 1)); - EXPECT_SUCCESS(config->wall_clock(config->sys_clock_ctx, ¤t_time)); - EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(config, ticket_key_name, strlen((char *) ticket_key_name), - ticket_key.data, ticket_key.size, current_time / ONE_SEC_IN_NANOS)); - + EXPECT_OK(s2n_resumption_test_ticket_key_setup(config)); EXPECT_SUCCESS(s2n_connection_set_config(conn, config)); conn->actual_protocol_version = S2N_TLS12; conn->handshake.handshake_type = NEGOTIATED; @@ -1372,16 +1357,10 @@ int main(int argc, char **argv) { struct s2n_connection *conn = NULL; struct s2n_config *config = NULL; - uint64_t current_time = 0; EXPECT_NOT_NULL(conn = s2n_connection_new(S2N_SERVER)); EXPECT_NOT_NULL(config = s2n_config_new()); - /* Setting up session resumption encryption key */ - EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(config, 1)); - EXPECT_SUCCESS(config->wall_clock(config->sys_clock_ctx, ¤t_time)); - EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(config, ticket_key_name, strlen((char *) ticket_key_name), - ticket_key.data, ticket_key.size, current_time / ONE_SEC_IN_NANOS)); - + EXPECT_OK(s2n_resumption_test_ticket_key_setup(config)); EXPECT_SUCCESS(s2n_connection_set_config(conn, config)); conn->actual_protocol_version = S2N_TLS13; @@ -1414,16 +1393,10 @@ int main(int argc, char **argv) { struct s2n_connection *conn = NULL; struct s2n_config *config = NULL; - uint64_t current_time = 0; EXPECT_NOT_NULL(conn = s2n_connection_new(S2N_SERVER)); EXPECT_NOT_NULL(config = s2n_config_new()); - /* Setting up session resumption encryption key */ - EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(config, 1)); - EXPECT_SUCCESS(config->wall_clock(config->sys_clock_ctx, ¤t_time)); - EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(config, ticket_key_name, strlen((char *) ticket_key_name), - ticket_key.data, ticket_key.size, current_time / ONE_SEC_IN_NANOS)); - + EXPECT_OK(s2n_resumption_test_ticket_key_setup(config)); EXPECT_SUCCESS(s2n_connection_set_config(conn, config)); conn->actual_protocol_version = S2N_TLS13; @@ -1452,6 +1425,82 @@ int main(int argc, char **argv) EXPECT_SUCCESS(s2n_config_free(config)); }; + /* Check error is thrown when wrong version number is read */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); + EXPECT_NOT_NULL(config); + + EXPECT_OK(s2n_resumption_test_ticket_key_setup(config)); + EXPECT_SUCCESS(s2n_connection_set_config(conn, config)); + conn->actual_protocol_version = S2N_TLS12; + conn->handshake.handshake_type = NEGOTIATED; + + EXPECT_OK(s2n_resume_encrypt_session_ticket(conn, &conn->client_ticket_to_decrypt)); + EXPECT_NOT_EQUAL(s2n_stuffer_data_available(&conn->client_ticket_to_decrypt), 0); + + /* Modify the version number of the ticket */ + uint8_t *version_num = conn->client_ticket_to_decrypt.blob.data; + EXPECT_EQUAL(*version_num, S2N_PRE_ENCRYPTED_STATE_V1); + *version_num = S2N_PRE_ENCRYPTED_STATE_V1 + 100; + + EXPECT_ERROR_WITH_ERRNO(s2n_resume_decrypt_session_ticket(conn, &conn->client_ticket_to_decrypt), + S2N_ERR_SAFETY); + + /* The correct version number should succeed */ + *version_num = S2N_PRE_ENCRYPTED_STATE_V1; + EXPECT_SUCCESS(s2n_stuffer_reread(&conn->client_ticket_to_decrypt)); + EXPECT_OK(s2n_resume_decrypt_session_ticket(conn, &conn->client_ticket_to_decrypt)); + } + + /* Check error is thrown when info bytes used to generate the ticket key are incorrect */ + { + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(conn); + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); + EXPECT_NOT_NULL(config); + + EXPECT_OK(s2n_resumption_test_ticket_key_setup(config)); + EXPECT_SUCCESS(s2n_connection_set_config(conn, config)); + conn->actual_protocol_version = S2N_TLS12; + conn->handshake.handshake_type = NEGOTIATED; + + DEFER_CLEANUP(struct s2n_stuffer valid_ticket = { 0 }, s2n_stuffer_free); + EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&valid_ticket, 0)); + EXPECT_OK(s2n_resume_encrypt_session_ticket(conn, &valid_ticket)); + uint32_t ticket_size = s2n_stuffer_data_available(&valid_ticket); + + /* Copy ticket so that we don't modify the original ticket */ + EXPECT_SUCCESS(s2n_stuffer_copy(&valid_ticket, &conn->client_ticket_to_decrypt, + ticket_size)); + + /* We assert that everything up to the info bytes is as expected since this test checks + * a failure condition. This will cause this test to fail earlier if we change the + * serialization format in the future. */ + uint8_t version_number = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint8(&conn->client_ticket_to_decrypt, &version_number)); + EXPECT_EQUAL(version_number, S2N_PRE_ENCRYPTED_STATE_V1); + uint8_t key_name[S2N_TICKET_KEY_NAME_LEN] = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_read_bytes(&conn->client_ticket_to_decrypt, key_name, sizeof(key_name))); + EXPECT_BYTEARRAY_EQUAL(key_name, "2016.07.26.15\0\0", S2N_TICKET_KEY_NAME_LEN); + uint8_t *info_ptr = s2n_stuffer_raw_read(&conn->client_ticket_to_decrypt, S2N_TICKET_INFO_SIZE); + EXPECT_NOT_NULL(info_ptr); + + /* Zero out the info bytes on the ticket.*/ + memset(info_ptr, 0, S2N_TICKET_INFO_SIZE); + EXPECT_SUCCESS(s2n_stuffer_reread(&conn->client_ticket_to_decrypt)); + + EXPECT_ERROR_WITH_ERRNO(s2n_resume_decrypt_session_ticket(conn, &conn->client_ticket_to_decrypt), + S2N_ERR_DECRYPT); + + /* The correct info bytes should succeed */ + EXPECT_SUCCESS(s2n_stuffer_reread(&valid_ticket)); + EXPECT_OK(s2n_resume_decrypt_session_ticket(conn, &valid_ticket)); + } + /* Check session ticket can never be encrypted with a zero-filled ticket key */ { DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free); @@ -1460,10 +1509,7 @@ int main(int argc, char **argv) DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); EXPECT_NOT_NULL(config); - /* Add a valid ticket key to the store */ - EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(config, 1)); - EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(config, ticket_key_name, strlen((char *) ticket_key_name), - ticket_key.data, ticket_key.size, 0)); + EXPECT_OK(s2n_resumption_test_ticket_key_setup(config)); EXPECT_SUCCESS(s2n_connection_set_config(conn, config)); /* Manually zero out key bytes */ @@ -1484,13 +1530,9 @@ int main(int argc, char **argv) const char test_app_proto[] = "https"; /* Setting up session resumption encryption key */ - uint64_t current_time = 0; struct s2n_config *config = s2n_config_new(); EXPECT_NOT_NULL(config); - EXPECT_SUCCESS(s2n_config_set_session_tickets_onoff(config, 1)); - EXPECT_SUCCESS(config->wall_clock(config->sys_clock_ctx, ¤t_time)); - EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(config, ticket_key_name, strlen((char *) ticket_key_name), - ticket_key.data, ticket_key.size, current_time / ONE_SEC_IN_NANOS)); + EXPECT_OK(s2n_resumption_test_ticket_key_setup(config)); struct s2n_connection *conn = s2n_connection_new(S2N_SERVER); EXPECT_NOT_NULL(conn); diff --git a/tests/unit/s2n_session_ticket_test.c b/tests/unit/s2n_session_ticket_test.c index 5ec935e4f94..dde2c244e78 100644 --- a/tests/unit/s2n_session_ticket_test.c +++ b/tests/unit/s2n_session_ticket_test.c @@ -25,6 +25,7 @@ #define S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS (S2N_TICKET_ENCRYPT_DECRYPT_KEY_LIFETIME_IN_NANOS + S2N_TICKET_DECRYPT_KEY_LIFETIME_IN_NANOS) / ONE_SEC_IN_NANOS #define S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES S2N_STATE_FORMAT_LEN + S2N_SESSION_TICKET_SIZE_LEN +#define S2N_TICKET_KEY_NAME_LOCATION S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES + S2N_TICKET_VERSION_SIZE #define ONE_SEC_DELAY 1 #define S2N_CLOCK_SYS CLOCK_REALTIME @@ -229,7 +230,8 @@ int main(int argc, char **argv) /* Verify that the client received NST */ serialized_session_state_length = s2n_connection_get_session_length(client_conn); EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), serialized_session_state_length); - EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name1, s2n_array_len(ticket_key_name1)); + EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name1, s2n_array_len(ticket_key_name1)); /* Verify the lifetime hint from the server */ EXPECT_EQUAL(s2n_connection_get_session_ticket_lifetime_hint(client_conn), S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS); @@ -393,7 +395,8 @@ int main(int argc, char **argv) EXPECT_EQUAL(s2n_connection_get_session_ticket_lifetime_hint(client_conn), S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS); /* Verify that the new NST is encrypted using second ST */ - EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name2, s2n_array_len(ticket_key_name2)); + EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name2, s2n_array_len(ticket_key_name2)); EXPECT_SUCCESS(s2n_shutdown_test_server_and_client(server_conn, client_conn)); @@ -504,7 +507,8 @@ int main(int argc, char **argv) /* Verify that the client received NST */ serialized_session_state_length = s2n_connection_get_session_length(client_conn); EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), serialized_session_state_length); - EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name1, s2n_array_len(ticket_key_name1)); + EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name1, s2n_array_len(ticket_key_name1)); /* Verify the lifetime hint from the server */ EXPECT_EQUAL(s2n_connection_get_session_ticket_lifetime_hint(client_conn), S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS); @@ -574,7 +578,8 @@ int main(int argc, char **argv) /* Verify that the client received NST */ serialized_session_state_length = s2n_connection_get_session_length(client_conn); EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), serialized_session_state_length); - EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name2, s2n_array_len(ticket_key_name2)); + EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name2, s2n_array_len(ticket_key_name2)); /* Verify the lifetime hint from the server */ EXPECT_EQUAL(s2n_connection_get_session_ticket_lifetime_hint(client_conn), S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS); @@ -640,7 +645,8 @@ int main(int argc, char **argv) /* Verify that the client received NST */ serialized_session_state_length = s2n_connection_get_session_length(client_conn); EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), serialized_session_state_length); - EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name1, s2n_array_len(ticket_key_name1)); + EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name1, s2n_array_len(ticket_key_name1)); /* Verify the lifetime hint from the server */ EXPECT_EQUAL(s2n_connection_get_session_ticket_lifetime_hint(client_conn), S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS); @@ -686,7 +692,9 @@ int main(int argc, char **argv) /* Verify that client_ticket is empty */ EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), 1 + 1 + client_conn->session_id_len + S2N_TLS12_STATE_SIZE_IN_BYTES); EXPECT_EQUAL(memcmp(serialized_session_state, &s2n_state_with_session_id, 1), 0); - EXPECT_NOT_EQUAL(memcmp(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name2, s2n_array_len(ticket_key_name2)), 0); + EXPECT_NOT_EQUAL(memcmp(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name2, s2n_array_len(ticket_key_name2)), + 0); /* Verify the lifetime hint from the server */ EXPECT_FAILURE_WITH_ERRNO(s2n_connection_get_session_ticket_lifetime_hint(client_conn), S2N_ERR_SESSION_TICKET_NOT_SUPPORTED); @@ -809,7 +817,8 @@ int main(int argc, char **argv) /* Verify that the client received NST which is encrypted using a key which is at it's peak encryption */ serialized_session_state_length = s2n_connection_get_session_length(client_conn); EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), serialized_session_state_length); - EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name1, s2n_array_len(ticket_key_name1)); + EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name1, s2n_array_len(ticket_key_name1)); /* Verify the lifetime hint from the server */ EXPECT_EQUAL(s2n_connection_get_session_ticket_lifetime_hint(client_conn), S2N_SESSION_STATE_CONFIGURABLE_LIFETIME_IN_SECS); @@ -893,7 +902,8 @@ int main(int argc, char **argv) serialized_session_state_length = s2n_connection_get_session_length(client_conn); EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), serialized_session_state_length); - int result = memcmp(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name2, + int result = memcmp(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name2, s2n_array_len(ticket_key_name2)); if (result == 0) { expected_key_chosen = true; @@ -967,7 +977,8 @@ int main(int argc, char **argv) /* Verify that the client received NST which is encrypted using a key which is at it's peak encryption */ serialized_session_state_length = s2n_connection_get_session_length(client_conn); EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), serialized_session_state_length); - EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name2, s2n_array_len(ticket_key_name2)); + EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name2, s2n_array_len(ticket_key_name2)); /* Verify the lifetime hint from the server */ EXPECT_EQUAL(s2n_connection_get_session_ticket_lifetime_hint(client_conn), 86400 + 18000); @@ -1027,7 +1038,8 @@ int main(int argc, char **argv) /* Verify that the client received NST which is encrypted using a key which is at it's peak encryption */ serialized_session_state_length = s2n_connection_get_session_length(client_conn); EXPECT_EQUAL(s2n_connection_get_session(client_conn, serialized_session_state, serialized_session_state_length), serialized_session_state_length); - EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_PARTIAL_SESSION_STATE_INFO_IN_BYTES, ticket_key_name2, s2n_array_len(ticket_key_name2)); + EXPECT_BYTEARRAY_EQUAL(serialized_session_state + S2N_TICKET_KEY_NAME_LOCATION, + ticket_key_name2, s2n_array_len(ticket_key_name2)); /* Verify that the keys are stored from oldest to newest */ EXPECT_OK(s2n_set_get(server_config->ticket_keys, 0, (void **) &ticket_key)); @@ -1097,9 +1109,13 @@ int main(int argc, char **argv) EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1), ticket_key1, s2n_array_len(ticket_key1), 0)); EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config)); - /* Setup stuffers value containing the valid key name, valid iv and invalid encrypted blob */ + /* Setup stuffers value containing the valid version number, valid key name, valid info, valid iv and invalid encrypted blob */ + POSIX_GUARD(s2n_stuffer_write_uint8(&server_conn->client_ticket_to_decrypt, S2N_PRE_ENCRYPTED_STATE_V1)); POSIX_GUARD(s2n_stuffer_write_bytes(&server_conn->client_ticket_to_decrypt, ticket_key_name1, s2n_array_len(ticket_key_name1))); + uint8_t valid_info[S2N_TICKET_INFO_SIZE] = { 0 }; + POSIX_GUARD(s2n_stuffer_write_bytes(&server_conn->client_ticket_to_decrypt, valid_info, sizeof(valid_info))); + uint8_t valid_iv[S2N_TLS_GCM_IV_LEN] = { 0 }; POSIX_GUARD(s2n_stuffer_write_bytes(&server_conn->client_ticket_to_decrypt, valid_iv, sizeof(valid_iv))); @@ -1123,9 +1139,13 @@ int main(int argc, char **argv) EXPECT_SUCCESS(s2n_config_add_ticket_crypto_key(server_config, ticket_key_name1, s2n_array_len(ticket_key_name1), ticket_key1, s2n_array_len(ticket_key1), 0)); EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config)); - /* Setup stuffers value containing the invalid key name, valid iv and invalid encrypted blob */ + /* Setup stuffers value containing the valid version number, invalid key name, valid iv, valid info, and invalid encrypted blob */ + POSIX_GUARD(s2n_stuffer_write_uint8(&server_conn->client_ticket_to_decrypt, S2N_PRE_ENCRYPTED_STATE_V1)); POSIX_GUARD(s2n_stuffer_write_bytes(&server_conn->client_ticket_to_decrypt, ticket_key_name2, s2n_array_len(ticket_key_name2))); + uint8_t valid_info[S2N_TICKET_INFO_SIZE] = { 0 }; + POSIX_GUARD(s2n_stuffer_write_bytes(&server_conn->client_ticket_to_decrypt, valid_info, sizeof(valid_info))); + uint8_t valid_iv[S2N_TLS_GCM_IV_LEN] = { 0 }; POSIX_GUARD(s2n_stuffer_write_bytes(&server_conn->client_ticket_to_decrypt, valid_iv, sizeof(valid_iv))); diff --git a/tls/s2n_resume.c b/tls/s2n_resume.c index 1bccc857b2c..c6e0e32af4d 100644 --- a/tls/s2n_resume.c +++ b/tls/s2n_resume.c @@ -782,6 +782,39 @@ struct s2n_ticket_key *s2n_find_ticket_key(struct s2n_config *config, const uint return NULL; } +struct s2n_unique_ticket_key { + struct s2n_blob initial_key; + uint8_t info[S2N_AES256_KEY_LEN]; + uint8_t output_key[S2N_AES256_KEY_LEN]; +}; + +/* Ensures that a session ticket encryption key is used only once per ticket. + * + * The AES-GCM encryption scheme breaks if the same nonce is used with the same key more than once. + * As the number of TLS connections increases per second, it becomes more probable that the same + * random nonce will be generated twice and used with the same ticket key. + * To avoid this we generate a unique session ticket encryption key for each ticket. + **/ +static S2N_RESULT s2n_resume_generate_unique_ticket_key(struct s2n_unique_ticket_key *key) +{ + RESULT_ENSURE_REF(key); + + struct s2n_blob out_key_blob = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&out_key_blob, key->output_key, sizeof(key->output_key))); + struct s2n_blob info_blob = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&info_blob, key->info, sizeof(key->info))); + struct s2n_blob salt = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&salt, NULL, 0)); + + DEFER_CLEANUP(struct s2n_hmac_state hmac = { 0 }, s2n_hmac_free); + /* TODO: There may be an optimization here to reuse existing hmac memory instead of + * creating an entirely new hmac. See: https://github.com/aws/s2n-tls/issues/3206 */ + RESULT_GUARD_POSIX(s2n_hmac_new(&hmac)); + RESULT_GUARD_POSIX(s2n_hkdf(&hmac, S2N_HMAC_SHA256, &salt, &key->initial_key, &info_blob, &out_key_blob)); + + return S2N_RESULT_OK; +} + S2N_RESULT s2n_resume_encrypt_session_ticket(struct s2n_connection *conn, struct s2n_stuffer *to) { RESULT_ENSURE_REF(conn); @@ -791,9 +824,17 @@ S2N_RESULT s2n_resume_encrypt_session_ticket(struct s2n_connection *conn, struct /* No keys loaded by the user or the keys are either in decrypt-only or expired state */ RESULT_ENSURE(key != NULL, S2N_ERR_NO_TICKET_ENCRYPT_DECRYPT_KEY); + /* Generate unique per-ticket encryption key */ + struct s2n_unique_ticket_key ticket_key = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&ticket_key.initial_key, key->aes_key, sizeof(key->aes_key))); + struct s2n_blob info_blob = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&info_blob, ticket_key.info, sizeof(ticket_key.info))); + RESULT_GUARD(s2n_get_public_random_data(&info_blob)); + RESULT_GUARD(s2n_resume_generate_unique_ticket_key(&ticket_key)); + /* Initialize AES key */ struct s2n_blob aes_key_blob = { 0 }; - RESULT_GUARD_POSIX(s2n_blob_init(&aes_key_blob, key->aes_key, sizeof(key->aes_key))); + RESULT_GUARD_POSIX(s2n_blob_init(&aes_key_blob, ticket_key.output_key, sizeof(ticket_key.output_key))); DEFER_CLEANUP(struct s2n_session_key aes_ticket_key = { 0 }, s2n_session_key_free); RESULT_GUARD_POSIX(s2n_session_key_alloc(&aes_ticket_key)); RESULT_GUARD(s2n_aes256_gcm.init(&aes_ticket_key)); @@ -813,9 +854,15 @@ S2N_RESULT s2n_resume_encrypt_session_ticket(struct s2n_connection *conn, struct RESULT_GUARD_POSIX(s2n_stuffer_write_bytes(&aad, key->implicit_aad, sizeof(key->implicit_aad))); RESULT_GUARD_POSIX(s2n_stuffer_write_bytes(&aad, key->key_name, sizeof(key->key_name))); + /* Write version number */ + RESULT_GUARD_POSIX(s2n_stuffer_write_uint8(to, S2N_PRE_ENCRYPTED_STATE_V1)); + /* Write key name */ RESULT_GUARD_POSIX(s2n_stuffer_write_bytes(to, key->key_name, sizeof(key->key_name))); + /* Write parameter needed to generate unique ticket key */ + RESULT_GUARD_POSIX(s2n_stuffer_write_bytes(to, ticket_key.info, sizeof(ticket_key.info))); + /* Write IV */ uint8_t iv_data[S2N_TLS_GCM_IV_LEN] = { 0 }; struct s2n_blob iv = { 0 }; @@ -850,6 +897,11 @@ static S2N_RESULT s2n_resume_decrypt_session(struct s2n_connection *conn, struct RESULT_ENSURE_REF(conn->config); RESULT_ENSURE_REF(key_intro_time); + /* Read version number */ + uint8_t version = 0; + RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(from, &version)); + RESULT_ENSURE_EQ(version, S2N_PRE_ENCRYPTED_STATE_V1); + /* Read key name */ uint8_t key_name[S2N_TICKET_KEY_NAME_LEN] = { 0 }; RESULT_GUARD_POSIX(s2n_stuffer_read_bytes(from, key_name, sizeof(key_name))); @@ -858,6 +910,11 @@ static S2N_RESULT s2n_resume_decrypt_session(struct s2n_connection *conn, struct /* Key has expired; do full handshake */ RESULT_ENSURE(key != NULL, S2N_ERR_KEY_USED_IN_SESSION_TICKET_NOT_FOUND); + struct s2n_unique_ticket_key ticket_key = { 0 }; + RESULT_GUARD_POSIX(s2n_blob_init(&ticket_key.initial_key, key->aes_key, sizeof(key->aes_key))); + RESULT_GUARD_POSIX(s2n_stuffer_read_bytes(from, ticket_key.info, sizeof(ticket_key.info))); + RESULT_GUARD(s2n_resume_generate_unique_ticket_key(&ticket_key)); + /* Read IV */ uint8_t iv_data[S2N_TLS_GCM_IV_LEN] = { 0 }; struct s2n_blob iv = { 0 }; @@ -866,7 +923,7 @@ static S2N_RESULT s2n_resume_decrypt_session(struct s2n_connection *conn, struct /* Initialize AES key */ struct s2n_blob aes_key_blob = { 0 }; - RESULT_GUARD_POSIX(s2n_blob_init(&aes_key_blob, key->aes_key, sizeof(key->aes_key))); + RESULT_GUARD_POSIX(s2n_blob_init(&aes_key_blob, ticket_key.output_key, sizeof(ticket_key.output_key))); DEFER_CLEANUP(struct s2n_session_key aes_ticket_key = { 0 }, s2n_session_key_free); RESULT_GUARD_POSIX(s2n_session_key_alloc(&aes_ticket_key)); RESULT_GUARD(s2n_aes256_gcm.init(&aes_ticket_key)); diff --git a/tls/s2n_resume.h b/tls/s2n_resume.h index 36b06de1f14..2a65e651fc5 100644 --- a/tls/s2n_resume.h +++ b/tls/s2n_resume.h @@ -35,8 +35,10 @@ #define ONE_SEC_IN_NANOS 1000000000 #define ONE_MILLISEC_IN_NANOS 1000000 #define ONE_WEEK_IN_SEC 604800 -#define S2N_TLS12_TICKET_SIZE_IN_BYTES (S2N_TICKET_KEY_NAME_LEN + S2N_TLS_GCM_IV_LEN \ - + S2N_TLS12_STATE_SIZE_IN_BYTES + S2N_TLS_GCM_TAG_LEN) +#define S2N_TICKET_INFO_SIZE 32 +#define S2N_TICKET_VERSION_SIZE 1 +#define S2N_TLS12_TICKET_SIZE_IN_BYTES (S2N_TICKET_VERSION_SIZE + S2N_TICKET_KEY_NAME_LEN \ + + S2N_TICKET_INFO_SIZE + S2N_TLS_GCM_IV_LEN + S2N_TLS12_STATE_SIZE_IN_BYTES + S2N_TLS_GCM_TAG_LEN) #define S2N_TICKET_ENCRYPT_DECRYPT_KEY_LIFETIME_IN_NANOS 7200000000000 /* 2 hours */ #define S2N_TICKET_DECRYPT_KEY_LIFETIME_IN_NANOS 46800000000000 /* 13 hours */ @@ -95,6 +97,15 @@ typedef enum { S2N_SERIALIZED_FORMAT_TLS12_V3, } s2n_serial_format_version; +/* Used to specify the format of the ticket schema before encryption. + * + * This makes it easier to make changes to the ticket schema in the future + * as it allows us to interpret and parse all ticket schemas. + **/ +typedef enum { + S2N_PRE_ENCRYPTED_STATE_V1 = 1, +} s2n_pre_encrypted_state; + int s2n_allowed_to_cache_connection(struct s2n_connection *conn); int s2n_resume_from_cache(struct s2n_connection *conn); S2N_RESULT s2n_store_to_cache(struct s2n_connection *conn); diff --git a/tls/s2n_server_new_session_ticket.c b/tls/s2n_server_new_session_ticket.c index 1c7bb404cec..f0cb82ab473 100644 --- a/tls/s2n_server_new_session_ticket.c +++ b/tls/s2n_server_new_session_ticket.c @@ -37,7 +37,7 @@ * * This constant is enforced via unit tests. */ -#define S2N_TLS13_MAX_FIXED_NEW_SESSION_TICKET_SIZE 79 +#define S2N_TLS13_MAX_FIXED_NEW_SESSION_TICKET_SIZE 112 int s2n_server_nst_recv(struct s2n_connection *conn) {