diff --git a/api/s2n.h b/api/s2n.h index 84e3cf01fb7..5cdb68c8c76 100644 --- a/api/s2n.h +++ b/api/s2n.h @@ -1559,6 +1559,47 @@ S2N_API extern int s2n_client_hello_get_session_id_length(struct s2n_client_hell */ S2N_API extern int s2n_client_hello_get_session_id(struct s2n_client_hello *ch, uint8_t *out, uint32_t *out_length, uint32_t max_length); +/** + * Get the length of the compression methods list sent in the Client Hello. + * + * @param ch A pointer to the Client Hello + * @param out_length An out pointer. Will be set to the length of the compression methods list in bytes. + * @returns S2N_SUCCESS on success. S2N_FAILURE on failure + */ +S2N_API extern int s2n_client_hello_get_compression_methods_length(struct s2n_client_hello *ch, uint32_t *out_length); + +/** + * Retrieves the list of compression methods sent in the Client Hello. + * + * Use `s2n_client_hello_get_compression_methods_length()` + * to retrieve how much memory should be allocated for the buffer in advance. + * + * @note Compression methods were removed in TLS1.3 and therefore the only valid value in this list is the + * "null" compression method when TLS1.3 is negotiated. + * + * @note s2n-tls has never supported compression methods in any TLS version and therefore a + * compression method will never be negotiated or used. + * + * @param ch A pointer to the Client Hello + * @param list A pointer to some memory that s2n will write the compression methods to. This memory MUST be the size of `list_length` + * @param list_length The size of `list`. + * @param out_length An out pointer. s2n will set its value to the size of the compression methods list in bytes. + * @returns S2N_SUCCESS on success. S2N_FAILURE on failure + */ +S2N_API extern int s2n_client_hello_get_compression_methods(struct s2n_client_hello *ch, uint8_t *list, uint32_t list_length, uint32_t *out_length); + +/** + * Access the Client Hello protocol version + * + * @note This field is a legacy field in TLS1.3 and is no longer used to negotiate the + * protocol version of the connection. It will be set to TLS1.2 even if TLS1.3 is negotiated. + * Therefore this method should only be used for logging or fingerprinting. + * + * @param ch A pointer to the client hello struct + * @param out The protocol version in the client hello. + */ +S2N_API extern int s2n_client_hello_get_legacy_protocol_version(struct s2n_client_hello *ch, uint8_t *out); + /** * Retrieves the supported groups received from the client in the supported groups extension. * @@ -2879,6 +2920,19 @@ S2N_API extern int s2n_connection_get_actual_protocol_version(struct s2n_connect */ S2N_API extern int s2n_connection_get_client_hello_version(struct s2n_connection *conn); +/** + * Access the protocol version from the header of the first record that contained the ClientHello message. + * + * @note This field has been deprecated and should not be confused with the client hello + * version. It is often set very low, usually to TLS1.0 for compatibility reasons, + * and should never be set higher than TLS1.2. Therefore this method should only be used + * for logging or fingerprinting. + * + * @param conn A pointer to the client hello struct + * @param out The protocol version in the record header containing the Client Hello. + */ +S2N_API extern int s2n_client_hello_get_legacy_record_version(struct s2n_client_hello *ch, uint8_t *out); + /** * Check if Client Auth was used for a connection. * diff --git a/tests/unit/s2n_client_hello_test.c b/tests/unit/s2n_client_hello_test.c index c1197809102..1ca4aa16233 100644 --- a/tests/unit/s2n_client_hello_test.c +++ b/tests/unit/s2n_client_hello_test.c @@ -42,6 +42,9 @@ #define TLS12_LENGTH_TO_CIPHER_LIST (LENGTH_TO_SESSION_ID + 1) #define TLS13_LENGTH_TO_CIPHER_LIST (TLS12_LENGTH_TO_CIPHER_LIST + S2N_TLS_SESSION_ID_MAX_LEN) +#define COMPRESSION_METHODS 0x00, 0x01, 0x02, 0x03, 0x04 +#define COMPRESSION_METHODS_LEN 0x05 + int s2n_parse_client_hello(struct s2n_connection *conn); int main(int argc, char **argv) @@ -1682,6 +1685,183 @@ int main(int argc, char **argv) }; }; + /* s2n_client_hello_get_compression_methods */ + { + /* Safety */ + { + uint32_t length = 0; + struct s2n_client_hello client_hello = { 0 }; + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_compression_methods_length(NULL, &length), S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_compression_methods_length(&client_hello, NULL), S2N_ERR_NULL); + + uint8_t list = 0; + uint32_t list_length = 0; + uint32_t out_length = 0; + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_compression_methods(NULL, &list, list_length, &out_length), S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_compression_methods(&client_hello, NULL, list_length, &out_length), S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_compression_methods(&client_hello, &list, list_length, NULL), S2N_ERR_NULL); + + /* User did not provide a large enough buffer to write the compression methods */ + uint8_t data[] = { 1, 2, 3, 4, 5 }; + EXPECT_SUCCESS(s2n_blob_init(&client_hello.compression_methods, data, sizeof(data))); + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_compression_methods(&client_hello, &list, list_length, &out_length), + S2N_ERR_INSUFFICIENT_MEM_SIZE); + }; + + /* Retrieves the compression methods list */ + { + DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(server_conn); + EXPECT_NOT_NULL(client_conn); + + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), + s2n_config_ptr_free); + EXPECT_NOT_NULL(config); + + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); + EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config)); + EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config)); + + EXPECT_SUCCESS(s2n_client_hello_send(client_conn)); + EXPECT_SUCCESS(s2n_stuffer_copy(&client_conn->handshake.io, &server_conn->handshake.io, + s2n_stuffer_data_available(&client_conn->handshake.io))); + EXPECT_SUCCESS(s2n_client_hello_recv(server_conn)); + + struct s2n_client_hello *client_hello = s2n_connection_get_client_hello(server_conn); + EXPECT_NOT_NULL(client_hello); + + uint32_t length = 0; + EXPECT_SUCCESS(s2n_client_hello_get_compression_methods_length(client_hello, &length)); + EXPECT_EQUAL(length, 1); + uint8_t list = 0; + uint32_t out_length = 0; + EXPECT_SUCCESS(s2n_client_hello_get_compression_methods(client_hello, &list, sizeof(list), &out_length)); + EXPECT_EQUAL(out_length, 1); + }; + + /* Retrieves compression methods list longer than one byte */ + { + /* Compression methods were deprecated in TLS13 and s2n has never + * supported them. However, it is conceivable that a client could send + * us a list that contains more than the "null" byte. Therefore, we construct + * a fake Client Hello that contains a longer list of compression methods + * for testing. + */ + uint8_t client_hello_message[] = { + /* Protocol version TLS 1.2 */ + 0x03, 0x03, + /* Client random */ + ZERO_TO_THIRTY_ONE, + /* SessionID len - 32 bytes */ + 0x20, + /* Session ID */ + ZERO_TO_THIRTY_ONE, + /* Cipher suites len */ + 0x00, 0x02, + /* Cipher suite - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 */ + 0xC0, 0x2F, + COMPRESSION_METHODS_LEN, + COMPRESSION_METHODS, + /* Extensions len */ + 0x00, 0x00 + }; + + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(server_conn); + + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), + s2n_config_ptr_free); + EXPECT_NOT_NULL(config); + + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); + EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config)); + + EXPECT_SUCCESS(s2n_stuffer_write_bytes(&server_conn->handshake.io, client_hello_message, sizeof(client_hello_message))); + EXPECT_SUCCESS(s2n_client_hello_recv(server_conn)); + + struct s2n_client_hello *client_hello = s2n_connection_get_client_hello(server_conn); + EXPECT_NOT_NULL(client_hello); + + uint32_t length = 0; + EXPECT_SUCCESS(s2n_client_hello_get_compression_methods_length(client_hello, &length)); + EXPECT_EQUAL(length, COMPRESSION_METHODS_LEN); + uint8_t list[5] = { 0 }; + uint32_t out_length = 0; + EXPECT_SUCCESS(s2n_client_hello_get_compression_methods(client_hello, list, sizeof(list), &out_length)); + EXPECT_EQUAL(out_length, COMPRESSION_METHODS_LEN); + + uint8_t compression_data[] = { COMPRESSION_METHODS }; + EXPECT_BYTEARRAY_EQUAL(list, compression_data, out_length); + } + }; + + /* s2n_client_hello_get_legacy_protocol_version */ + { + /* Safety */ + { + uint8_t out = 0; + struct s2n_client_hello client_hello = { 0 }; + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_legacy_protocol_version(NULL, &out), S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_legacy_protocol_version(&client_hello, NULL), S2N_ERR_NULL); + }; + + /* Retrieves the Client Hello protocol version */ + { + DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_NOT_NULL(server_conn); + EXPECT_NOT_NULL(client_conn); + + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), + s2n_config_ptr_free); + EXPECT_NOT_NULL(config); + + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key)); + EXPECT_SUCCESS(s2n_connection_set_config(client_conn, config)); + EXPECT_SUCCESS(s2n_connection_set_config(server_conn, config)); + + EXPECT_SUCCESS(s2n_client_hello_send(client_conn)); + EXPECT_SUCCESS(s2n_stuffer_copy(&client_conn->handshake.io, &server_conn->handshake.io, + s2n_stuffer_data_available(&client_conn->handshake.io))); + EXPECT_SUCCESS(s2n_client_hello_recv(server_conn)); + + struct s2n_client_hello *client_hello = s2n_connection_get_client_hello(server_conn); + EXPECT_NOT_NULL(client_hello); + + uint8_t version = 0; + EXPECT_SUCCESS(s2n_client_hello_get_legacy_protocol_version(client_hello, &version)); + EXPECT_EQUAL(version, S2N_TLS12); + }; + }; + + /* s2n_client_hello_get_legacy_record_version */ + { + /* Safety */ + { + uint8_t out = 0; + struct s2n_client_hello client_hello = { 0 }; + + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_legacy_record_version(NULL, &out), S2N_ERR_NULL); + EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_get_legacy_record_version(&client_hello, NULL), S2N_ERR_NULL); + } + + /* Retrieves record version */ + { + uint8_t out = 0; + struct s2n_client_hello client_hello = { 0 }; + client_hello.legacy_record_version = S2N_TLS12; + client_hello.record_version_recorded = 1; + EXPECT_SUCCESS(s2n_client_hello_get_legacy_record_version(&client_hello, &out)); + EXPECT_EQUAL(out, S2N_TLS12); + } + } + EXPECT_SUCCESS(s2n_cert_chain_and_key_free(chain_and_key)); EXPECT_SUCCESS(s2n_cert_chain_and_key_free(ecdsa_chain_and_key)); END_TEST(); diff --git a/tests/unit/s2n_connection_size_test.c b/tests/unit/s2n_connection_size_test.c index 8364a67012f..9e53914b4cd 100644 --- a/tests/unit/s2n_connection_size_test.c +++ b/tests/unit/s2n_connection_size_test.c @@ -45,7 +45,7 @@ int main(int argc, char **argv) } /* Carefully consider any increases to this number. */ - const uint16_t max_connection_size = 4274; + const uint16_t max_connection_size = 4290; const uint16_t min_connection_size = max_connection_size * 0.9; size_t connection_size = sizeof(struct s2n_connection); diff --git a/tests/unit/s2n_record_test.c b/tests/unit/s2n_record_test.c index bf7e9120fc6..7181bc782f0 100644 --- a/tests/unit/s2n_record_test.c +++ b/tests/unit/s2n_record_test.c @@ -429,6 +429,33 @@ int main(int argc, char **argv) EXPECT_EQUAL(s2n_stuffer_data_available(&conn->header_in), 3); }; + /* Record version is recorded for the first message received (Client Hello) */ + { + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + uint8_t content_type = 0; + uint16_t fragment_length = 0; + uint8_t header[5] = { 0x16, /* Record type */ + 0x03, 0x01, /* Protocol version: TLS10 */ + 0x00, 0x00 }; /* Record size */ + + uint8_t altered_header[5] = { 0x16, /* Record type */ + 0x03, 0x03, /* Protocol version: TLS12 */ + 0x00, 0x00 }; /* Record size */ + + EXPECT_SUCCESS(s2n_stuffer_write_bytes(&server_conn->header_in, header, sizeof(header))); + EXPECT_SUCCESS(s2n_record_header_parse(server_conn, &content_type, &fragment_length)); + /* Record TLS version is retrieved as written in the header */ + EXPECT_EQUAL(server_conn->client_hello.legacy_record_version, S2N_TLS10); + + EXPECT_SUCCESS(s2n_stuffer_wipe(&server_conn->header_in)); + + EXPECT_SUCCESS(s2n_stuffer_write_bytes(&server_conn->header_in, altered_header, sizeof(header))); + EXPECT_SUCCESS(s2n_record_header_parse(server_conn, &content_type, &fragment_length)); + /* Record TLS version is unchanged even though a different TLS version was in the record header */ + EXPECT_EQUAL(server_conn->client_hello.legacy_record_version, S2N_TLS10); + } + EXPECT_SUCCESS(s2n_hmac_free(&check_mac)); EXPECT_SUCCESS(s2n_connection_free(conn)); diff --git a/tls/s2n_client_hello.c b/tls/s2n_client_hello.c index bba2f13b128..1bd72c3c493 100644 --- a/tls/s2n_client_hello.c +++ b/tls/s2n_client_hello.c @@ -373,6 +373,12 @@ S2N_RESULT s2n_client_hello_parse_raw(struct s2n_client_hello *client_hello, /* legacy_version */ RESULT_GUARD_POSIX(s2n_stuffer_read_bytes(in, client_protocol_version, S2N_TLS_PROTOCOL_VERSION_LEN)); + /* Encode the version as a 1 byte representation of the two protocol version bytes, with the + * major version in the tens place and the minor version in the ones place. For example, the + * TLS 1.2 protocol version is 0x0303, which is encoded as S2N_TLS12 (33). + */ + client_hello->legacy_version = (client_protocol_version[0] * 10) + client_protocol_version[1]; + /* random */ RESULT_GUARD_POSIX(s2n_stuffer_erase_and_read_bytes(in, client_random, S2N_TLS_RANDOM_DATA_LEN)); @@ -393,10 +399,12 @@ S2N_RESULT s2n_client_hello_parse_raw(struct s2n_client_hello *client_hello, RESULT_ENSURE(cipher_suites != NULL, S2N_ERR_BAD_MESSAGE); RESULT_GUARD_POSIX(s2n_blob_init(&client_hello->cipher_suites, cipher_suites, cipher_suites_length)); - /* legacy_compression_methods (ignored) */ - uint8_t num_compression_methods = 0; - RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(in, &num_compression_methods)); - RESULT_GUARD_POSIX(s2n_stuffer_skip_read(in, num_compression_methods)); + /* legacy_compression_methods */ + uint8_t compression_methods_len = 0; + RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(in, &compression_methods_len)); + uint8_t *compression_methods = s2n_stuffer_raw_read(in, compression_methods_len); + RESULT_ENSURE(compression_methods != NULL, S2N_ERR_BAD_MESSAGE); + RESULT_GUARD_POSIX(s2n_blob_init(&client_hello->compression_methods, compression_methods, compression_methods_len)); /* extensions */ RESULT_GUARD_POSIX(s2n_extension_list_parse(in, &client_hello->extensions)); @@ -917,6 +925,43 @@ int s2n_client_hello_get_session_id(struct s2n_client_hello *ch, uint8_t *out, u return S2N_SUCCESS; } +int s2n_client_hello_get_compression_methods_length(struct s2n_client_hello *ch, uint32_t *out_length) +{ + POSIX_ENSURE_REF(ch); + POSIX_ENSURE_REF(out_length); + *out_length = ch->compression_methods.size; + return S2N_SUCCESS; +} + +int s2n_client_hello_get_compression_methods(struct s2n_client_hello *ch, uint8_t *list, uint32_t list_length, uint32_t *out_length) +{ + POSIX_ENSURE_REF(ch); + POSIX_ENSURE_REF(list); + POSIX_ENSURE_REF(out_length); + + POSIX_ENSURE(list_length >= ch->compression_methods.size, S2N_ERR_INSUFFICIENT_MEM_SIZE); + POSIX_CHECKED_MEMCPY(list, ch->compression_methods.data, ch->compression_methods.size); + *out_length = ch->compression_methods.size; + return S2N_SUCCESS; +} + +int s2n_client_hello_get_legacy_protocol_version(struct s2n_client_hello *ch, uint8_t *out) +{ + POSIX_ENSURE_REF(ch); + POSIX_ENSURE_REF(out); + *out = ch->legacy_version; + return S2N_SUCCESS; +} + +int s2n_client_hello_get_legacy_record_version(struct s2n_client_hello *ch, uint8_t *out) +{ + POSIX_ENSURE_REF(ch); + POSIX_ENSURE_REF(out); + POSIX_ENSURE(ch->record_version_recorded, S2N_ERR_INVALID_ARGUMENT); + *out = ch->legacy_record_version; + return S2N_SUCCESS; +} + static S2N_RESULT s2n_client_hello_get_raw_extension(uint16_t extension_iana, struct s2n_blob *raw_extensions, struct s2n_blob *extension) { diff --git a/tls/s2n_client_hello.h b/tls/s2n_client_hello.h index bed6a568497..8d6d32eb317 100644 --- a/tls/s2n_client_hello.h +++ b/tls/s2n_client_hello.h @@ -31,6 +31,13 @@ struct s2n_client_hello { s2n_parsed_extensions_list extensions; struct s2n_blob cipher_suites; struct s2n_blob session_id; + struct s2n_blob compression_methods; + /* The protocol version as written in the client hello */ + uint8_t legacy_version; + /* The protocol written on the record header containing the client hello */ + uint8_t legacy_record_version; + /* Tracks if we have recorded the version in the first record */ + unsigned int record_version_recorded : 1; unsigned int callback_invoked : 1; unsigned int callback_async_blocked : 1; diff --git a/tls/s2n_record_read.c b/tls/s2n_record_read.c index 484570e103d..b372ae21e7a 100644 --- a/tls/s2n_record_read.c +++ b/tls/s2n_record_read.c @@ -95,6 +95,12 @@ int s2n_record_header_parse( POSIX_GUARD(s2n_stuffer_read_bytes(in, protocol_version, S2N_TLS_PROTOCOL_VERSION_LEN)); const uint8_t version = (protocol_version[0] * 10) + protocol_version[1]; + /* We record the protocol version in the first record seen by the server for fingerprinting usecases */ + if (!conn->client_hello.record_version_recorded) { + conn->client_hello.legacy_record_version = version; + conn->client_hello.record_version_recorded = 1; + } + /* https://tools.ietf.org/html/rfc5246#appendix-E.1 states that servers must accept any value {03,XX} as the record * layer version number for the first TLS record. There is some ambiguity here because the client does not know * what version to use in the record header prior to receiving the ServerHello. Some client implementations may use