Skip to content

Commit

Permalink
feat: More client hello getters (#4380)
Browse files Browse the repository at this point in the history
  • Loading branch information
maddeleine authored Feb 6, 2024
1 parent 6c7cdc8 commit 3c80856
Show file tree
Hide file tree
Showing 7 changed files with 324 additions and 5 deletions.
54 changes: 54 additions & 0 deletions api/s2n.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
*
Expand Down
180 changes: 180 additions & 0 deletions tests/unit/s2n_client_hello_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/s2n_connection_size_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/s2n_record_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
53 changes: 49 additions & 4 deletions tls/s2n_client_hello.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand All @@ -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));
Expand Down Expand Up @@ -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)
{
Expand Down
7 changes: 7 additions & 0 deletions tls/s2n_client_hello.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 3c80856

Please sign in to comment.