Skip to content

Commit

Permalink
Allow client hellos from raw bytes (#3871)
Browse files Browse the repository at this point in the history
  • Loading branch information
lrstewart committed Mar 21, 2023
1 parent cc8e2ed commit bcaaaeb
Show file tree
Hide file tree
Showing 7 changed files with 509 additions and 160 deletions.
26 changes: 26 additions & 0 deletions api/unstable/fingerprint.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,29 @@ int s2n_client_hello_get_fingerprint_hash(struct s2n_client_hello *ch,
int s2n_client_hello_get_fingerprint_string(struct s2n_client_hello *ch,
s2n_fingerprint_type type, uint32_t max_size,
uint8_t *output, uint32_t *output_size);

/**
* Creates an s2n_client_hello from bytes representing a ClientHello message.
*
* Unlike s2n_connection_get_client_hello, the s2n_client_hello returned by this
* method is owned by the application and must be freed with s2n_client_hello_free.
*
* This method does not support SSLv2 ClientHellos.
*
* @param bytes The raw bytes representing the ClientHello.
* @param size The size of raw_message.
* @returns A new s2n_client_hello on success, or NULL on failure.
*/
struct s2n_client_hello *s2n_client_hello_parse_message(const uint8_t *bytes, uint32_t size);

/**
* Frees an s2n_client_hello structure.
*
* This method should be called to free s2n_client_hellos returned by
* s2n_client_hello_parse_message. It will error if passed an s2n_client_hello
* returned by s2n_connection_get_client_hello and owned by the connection.
*
* @param ch The structure to be freed.
* @returns S2N_SUCCESS on success, S2N_FAILURE on failure.
*/
int s2n_client_hello_free(struct s2n_client_hello **ch);
5 changes: 2 additions & 3 deletions codebuild/bin/grep_simple_mistakes.sh
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,8 @@ for file in $S2N_FILES_ASSERT_NOTNULL_CHECK; do
# $line_one definitely contains an assignment from s2n_stuffer_raw_read(),
# because that's what we grepped for. So verify that either $line_one or
# $line_two contains a null check.
manual_null_check_regex="(.*(if|ENSURE_POSIX|POSIX_ENSURE).*=\ NULL)|(ENSURE_REF)"
if [[ $line_one == *"notnull_check("* ]] || [[ $line_one =~ $manual_null_check_regex ]] ||\
[[ $line_two == *"notnull_check("* ]] || [[ $line_two =~ $manual_null_check_regex ]]; then
null_check_regex="(.*(if|ENSURE).*=\ NULL)|(ENSURE_REF)"
if [[ $line_one =~ $null_check_regex ]] || [[ $line_two =~ $null_check_regex ]]; then
# Found a notnull_check
continue
else
Expand Down
169 changes: 169 additions & 0 deletions tests/unit/s2n_client_hello_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,175 @@ int main(int argc, char **argv)
EXPECT_FAILURE_WITH_ERRNO(s2n_parse_client_hello(server_conn), S2N_ERR_SAFETY);
};

/* Test s2n_client_hello_parse_message
*
* Comparing ClientHellos produced by connection IO parsing vs
* produced by s2n_client_hello_parse_message is difficult, but we can
* use JA3 fingerprints as an approximation. See s2n_fingerprint_ja3_test.c
*/
{
const char *security_policies[] = { "default", "default_tls13", "test_all" };

DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key));

/* Test: Can parse ClientHellos sent by the s2n client */
for (size_t i = 0; i < s2n_array_len(security_policies); i++) {
const char *security_policy = security_policies[i];

DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
EXPECT_SUCCESS(s2n_connection_set_config(client, config));
EXPECT_SUCCESS(s2n_connection_set_cipher_preferences(client, security_policy));

EXPECT_SUCCESS(s2n_handshake_write_header(&client->handshake.io, TLS_CLIENT_HELLO));
EXPECT_SUCCESS(s2n_client_hello_send(client));
EXPECT_SUCCESS(s2n_handshake_finish_header(&client->handshake.io));

uint32_t raw_size = s2n_stuffer_data_available(&client->handshake.io);
EXPECT_NOT_EQUAL(raw_size, 0);
uint8_t *raw = s2n_stuffer_raw_read(&client->handshake.io, raw_size);
EXPECT_NOT_NULL(raw);

DEFER_CLEANUP(struct s2n_client_hello *client_hello = NULL, s2n_client_hello_free);
EXPECT_NOT_NULL(client_hello = s2n_client_hello_parse_message(raw, raw_size));
EXPECT_TRUE(client_hello->alloced);
};

/* Test: Rejects invalid ClientHellos
*
* This test is important to verify that no memory is leaked when parsing fails.
*/
{
struct s2n_client_hello *client_hello = NULL;

uint8_t wrong_message_type[50] = { 0x02, 0x00, 0x00, 1 };
client_hello = s2n_client_hello_parse_message(wrong_message_type, sizeof(wrong_message_type));
EXPECT_NULL(client_hello);
EXPECT_EQUAL(s2n_errno, S2N_ERR_BAD_MESSAGE);

uint8_t wrong_message_size[50] = { 0x01, 0x00, 0x00, UINT8_MAX };
client_hello = s2n_client_hello_parse_message(wrong_message_size, sizeof(wrong_message_size));
EXPECT_NULL(client_hello);
EXPECT_EQUAL(s2n_errno, S2N_ERR_BAD_MESSAGE);

uint8_t too_short[5] = { 0x01, 0x00, 0x00, 1 };
client_hello = s2n_client_hello_parse_message(too_short, sizeof(too_short));
EXPECT_NULL(client_hello);
EXPECT_EQUAL(s2n_errno, S2N_ERR_STUFFER_OUT_OF_DATA);

uint8_t all_zeroes[50] = { 0x01, 0x00, 0x00, 46 };
client_hello = s2n_client_hello_parse_message(all_zeroes, sizeof(all_zeroes));
EXPECT_NULL(client_hello);
EXPECT_EQUAL(s2n_errno, S2N_ERR_BAD_MESSAGE);
};

/* Test: Rejects SSLv2 */
{
uint8_t sslv2_client_hello[] = {
SSLv2_CLIENT_HELLO_HEADER,
SSLv2_CLIENT_HELLO_PREFIX,
SSLv2_CLIENT_HELLO_CIPHER_SUITES,
SSLv2_CLIENT_HELLO_CHALLENGE,
};

/* Try parsing variations on the complete record vs just the message.
* The sslv2 record header is technically the first two bytes,
* but s2n-tls usually starts parsing after the first five bytes.
*/
for (size_t i = 0; i <= S2N_TLS_RECORD_HEADER_LENGTH; i++) {
struct s2n_client_hello *client_hello = s2n_client_hello_parse_message(
sslv2_client_hello + i, sizeof(sslv2_client_hello) - i);
EXPECT_NULL(client_hello);
EXPECT_EQUAL(s2n_errno, S2N_ERR_BAD_MESSAGE);
}

/* Sanity check: s2n accepts the test sslv2 message via the connection */
{
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_SUCCESS(s2n_connection_set_config(server, config));
EXPECT_SUCCESS(s2n_connection_set_cipher_preferences(server, "test_all"));

EXPECT_SUCCESS(s2n_stuffer_write_bytes(&server->header_in,
sslv2_client_hello, S2N_TLS_RECORD_HEADER_LENGTH));
EXPECT_SUCCESS(s2n_stuffer_write_bytes(&server->in,
sslv2_client_hello + S2N_TLS_RECORD_HEADER_LENGTH,
sizeof(sslv2_client_hello) - S2N_TLS_RECORD_HEADER_LENGTH));

EXPECT_FALSE(server->client_hello.sslv2);
s2n_blocked_status blocked = S2N_NOT_BLOCKED;
EXPECT_OK(s2n_negotiate_until_message(server, &blocked, SERVER_HELLO));
EXPECT_TRUE(server->client_hello.sslv2);
EXPECT_FALSE(server->client_hello.alloced);
}
};
};

/* Test s2n_client_hello_free */
{
/* Safety */
EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_free(NULL), S2N_ERR_NULL);

/* Test: Accepts but ignores NULL / already freed */
{
struct s2n_client_hello *client_hello = NULL;
for (size_t i = 0; i < 3; i++) {
EXPECT_SUCCESS(s2n_client_hello_free(&client_hello));
EXPECT_NULL(client_hello);
}
};

/* Test: Errors on client hello associated with a connection */
{
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "test_all"));
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, chain_and_key));

DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);
DEFER_CLEANUP(struct s2n_connection *server = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_SUCCESS(s2n_connection_set_config(client, config));
EXPECT_SUCCESS(s2n_connection_set_config(server, config));

EXPECT_SUCCESS(s2n_client_hello_send(client));
EXPECT_SUCCESS(s2n_stuffer_copy(&client->handshake.io, &server->handshake.io,
s2n_stuffer_data_available(&client->handshake.io)));
EXPECT_SUCCESS(s2n_client_hello_recv(server));

struct s2n_client_hello *client_hello = s2n_connection_get_client_hello(server);
EXPECT_NOT_NULL(client_hello);
EXPECT_FAILURE_WITH_ERRNO(s2n_client_hello_free(&client_hello), S2N_ERR_INVALID_ARGUMENT);
EXPECT_NOT_NULL(s2n_connection_get_client_hello(server));
EXPECT_NOT_EQUAL(server->client_hello.raw_message.size, 0);
};

/* Test: Frees client hello from raw message */
{
DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT),
s2n_connection_ptr_free);

EXPECT_SUCCESS(s2n_handshake_write_header(&client->handshake.io, TLS_CLIENT_HELLO));
EXPECT_SUCCESS(s2n_client_hello_send(client));
EXPECT_SUCCESS(s2n_handshake_finish_header(&client->handshake.io));

uint32_t raw_size = s2n_stuffer_data_available(&client->handshake.io);
EXPECT_NOT_EQUAL(raw_size, 0);
uint8_t *raw = s2n_stuffer_raw_read(&client->handshake.io, raw_size);
EXPECT_NOT_NULL(raw);

struct s2n_client_hello *client_hello = s2n_client_hello_parse_message(
raw, raw_size);
EXPECT_NOT_NULL(client_hello);

for (size_t i = 0; i < 3; i++) {
EXPECT_SUCCESS(s2n_client_hello_free(&client_hello));
EXPECT_NULL(client_hello);
}
};
};

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
Loading

0 comments on commit bcaaaeb

Please sign in to comment.