Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: More client hello getters #4380

Merged
merged 8 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
179 changes: 179 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,182 @@ 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_SAFETY);
};

/* 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);
maddeleine marked this conversation as resolved.
Show resolved Hide resolved
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);
maddeleine marked this conversation as resolved.
Show resolved Hide resolved
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;
maddeleine marked this conversation as resolved.
Show resolved Hide resolved
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) */
{
lrstewart marked this conversation as resolved.
Show resolved Hide resolved
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
48 changes: 44 additions & 4 deletions tls/s2n_client_hello.c
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,7 @@ 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));
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 +394,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 +920,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_GTE(list_length, ch->compression_methods.size);
maddeleine marked this conversation as resolved.
Show resolved Hide resolved
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_CLIENT_HELLO_VERSION);
maddeleine marked this conversation as resolved.
Show resolved Hide resolved
*out = ch->legacy_record_version;
maddeleine marked this conversation as resolved.
Show resolved Hide resolved
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
6 changes: 6 additions & 0 deletions tls/s2n_record_read.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading