diff --git a/api/s2n.h b/api/s2n.h index 9416cd38ceb..5f840c2823c 100644 --- a/api/s2n.h +++ b/api/s2n.h @@ -1530,6 +1530,31 @@ 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); +/** + * Retrieves the supported groups received from the client in the supported groups extension. + * + * IANA values for each of the received supported groups are written to the provided `groups` + * array, and `groups_count` is set to the number of received supported groups. + * + * `groups_count_max` should be set to the maximum capacity of the `groups` array. If + * `groups_count_max` is less than the number of received supported groups, this function will + * error. To determine how large `groups` should be in advance, use + * `s2n_client_hello_get_extension_length()` with the S2N_EXTENSION_SUPPORTED_GROUPS extension + * type, and divide the value by 2. + * + * If no supported groups extension was received from the peer, or the received supported groups + * extension is malformed, this function will error. + * + * @param ch A pointer to the ClientHello. Can be retrieved from a connection via + * `s2n_connection_get_client_hello()`. + * @param groups The array to populate with the received supported groups. + * @param groups_count_max The maximum number of supported groups that can fit in the `groups` array. + * @param groups_count Returns the number of received supported groups. + * @returns S2N_SUCCESS on success. S2N_FAILURE on failure. + */ +S2N_API extern int s2n_client_hello_get_supported_groups(struct s2n_client_hello *ch, uint16_t *groups, + uint16_t groups_count_max, uint16_t *groups_count); + /** * Sets the file descriptor for a s2n connection. * diff --git a/error/s2n_errno.c b/error/s2n_errno.c index 1d5e57fb6ee..5778fc63ac1 100644 --- a/error/s2n_errno.c +++ b/error/s2n_errno.c @@ -216,6 +216,7 @@ static const char *no_such_error = "Internal s2n error"; ERR_ENTRY(S2N_ERR_SEND_SIZE, "Retried s2n_send() size is invalid") \ ERR_ENTRY(S2N_ERR_CORK_SET_ON_UNMANAGED, "Attempt to set connection cork management on unmanaged IO") \ ERR_ENTRY(S2N_ERR_UNRECOGNIZED_EXTENSION, "TLS extension not recognized") \ + ERR_ENTRY(S2N_ERR_EXTENSION_NOT_RECEIVED, "The TLS extension was not received") \ ERR_ENTRY(S2N_ERR_INVALID_SCT_LIST, "SCT list is invalid") \ ERR_ENTRY(S2N_ERR_INVALID_OCSP_RESPONSE, "OCSP response is invalid") \ ERR_ENTRY(S2N_ERR_UPDATING_EXTENSION, "Updating extension data failed") \ diff --git a/error/s2n_errno.h b/error/s2n_errno.h index 5b97bd8aa7c..8cac347350b 100644 --- a/error/s2n_errno.h +++ b/error/s2n_errno.h @@ -259,6 +259,7 @@ typedef enum { S2N_ERR_SEND_SIZE, S2N_ERR_CORK_SET_ON_UNMANAGED, S2N_ERR_UNRECOGNIZED_EXTENSION, + S2N_ERR_EXTENSION_NOT_RECEIVED, S2N_ERR_INVALID_SCT_LIST, S2N_ERR_INVALID_OCSP_RESPONSE, S2N_ERR_UPDATING_EXTENSION, diff --git a/tests/unit/s2n_client_hello_get_supported_groups_test.c b/tests/unit/s2n_client_hello_get_supported_groups_test.c new file mode 100644 index 00000000000..8cb7de97f2b --- /dev/null +++ b/tests/unit/s2n_client_hello_get_supported_groups_test.c @@ -0,0 +1,388 @@ +/* +* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +* +* Licensed under the Apache License, Version 2.0 (the "License"). +* You may not use this file except in compliance with the License. +* A copy of the License is located at +* +* http://aws.amazon.com/apache2.0 +* +* or in the "license" file accompanying this file. This file is distributed +* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +* express or implied. See the License for the specific language governing +* permissions and limitations under the License. +*/ + +#include "pq-crypto/s2n_pq.h" +#include "s2n_test.h" +#include "testlib/s2n_testlib.h" +#include "tls/extensions/s2n_client_supported_groups.h" +#include "tls/s2n_client_hello.h" +#include "tls/s2n_tls.h" +#include "utils/s2n_random.h" + +#define S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT 16 + +/* Each supported group is 2 bytes. */ +#define S2N_TEST_SUPPORTED_GROUPS_LIST_SIZE (S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT * S2N_SUPPORTED_GROUP_SIZE) + +/* 2 length bytes + space for the list of supported groups. */ +#define S2N_TEST_SUPPORTED_GROUPS_EXTENSION_SIZE (2 + S2N_TEST_SUPPORTED_GROUPS_LIST_SIZE) + +struct s2n_client_hello_context { + struct s2n_stuffer *sent_supported_groups_extension; + int invoked_count; +}; + +int s2n_check_received_supported_groups_cb(struct s2n_connection *conn, void *ctx) +{ + EXPECT_NOT_NULL(ctx); + + struct s2n_client_hello_context *context = (struct s2n_client_hello_context *) ctx; + EXPECT_NOT_NULL(context->sent_supported_groups_extension); + context->invoked_count += 1; + + struct s2n_client_hello *client_hello = s2n_connection_get_client_hello(conn); + EXPECT_NOT_NULL(client_hello); + + uint16_t received_groups[S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT] = { 0 }; + uint16_t received_groups_count = 0; + EXPECT_SUCCESS(s2n_client_hello_get_supported_groups(client_hello, received_groups, + s2n_array_len(received_groups), &received_groups_count)); + + uint16_t sent_groups_count = 0; + EXPECT_OK(s2n_supported_groups_parse_count(context->sent_supported_groups_extension, &sent_groups_count)); + EXPECT_EQUAL(received_groups_count, sent_groups_count); + + for (size_t i = 0; i < received_groups_count; i++) { + uint16_t received_group = received_groups[i]; + + /* s2n_stuffer_read_uint16 is used to read each of the sent supported groups in + * network-order endianness, and compare them to the received supported groups which have + * already been converted to the machine's endianness. + */ + uint16_t sent_group = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(context->sent_supported_groups_extension, &sent_group)); + + EXPECT_EQUAL(received_group, sent_group); + } + + return S2N_SUCCESS; +} + +int main(int argc, char **argv) +{ + BEGIN_TEST(); + + DEFER_CLEANUP(struct s2n_cert_chain_and_key *chain_and_key = NULL, s2n_cert_chain_and_key_ptr_free); + EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&chain_and_key, + S2N_DEFAULT_TEST_CERT_CHAIN, S2N_DEFAULT_TEST_PRIVATE_KEY)); + + s2n_extension_type_id supported_groups_id = 0; + EXPECT_SUCCESS(s2n_extension_supported_iana_value_to_id(S2N_EXTENSION_SUPPORTED_GROUPS, &supported_groups_id)); + + /* Safety */ + { + struct s2n_client_hello client_hello = { 0 }; + uint16_t supported_groups[S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT] = { 0 }; + uint16_t supported_groups_count = 0; + + int ret = s2n_client_hello_get_supported_groups(NULL, supported_groups, s2n_array_len(supported_groups), + &supported_groups_count); + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_NULL); + EXPECT_EQUAL(supported_groups_count, 0); + + ret = s2n_client_hello_get_supported_groups(&client_hello, NULL, s2n_array_len(supported_groups), + &supported_groups_count); + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_NULL); + EXPECT_EQUAL(supported_groups_count, 0); + + ret = s2n_client_hello_get_supported_groups(&client_hello, supported_groups, s2n_array_len(supported_groups), NULL); + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_NULL); + EXPECT_EQUAL(supported_groups_count, 0); + } + + /* Ensure that the maximum size of the provided supported groups list is respected. */ + { + struct s2n_client_hello client_hello = { 0 }; + + uint8_t extension_data[S2N_TEST_SUPPORTED_GROUPS_EXTENSION_SIZE] = { 0 }; + struct s2n_blob extension_blob = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&extension_blob, extension_data, sizeof(extension_data))); + + s2n_parsed_extension *supported_groups_extension = &client_hello.extensions.parsed_extensions[supported_groups_id]; + supported_groups_extension->extension_type = S2N_EXTENSION_SUPPORTED_GROUPS; + supported_groups_extension->extension = extension_blob; + + struct s2n_stuffer extension_stuffer = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_init(&extension_stuffer, &extension_blob)); + EXPECT_SUCCESS(s2n_stuffer_write_uint16(&extension_stuffer, S2N_TEST_SUPPORTED_GROUPS_LIST_SIZE)); + + uint16_t supported_groups[S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT] = { 0 }; + uint16_t supported_groups_count = 0; + + /* Fail if the provided buffer is too small. */ + int ret = s2n_client_hello_get_supported_groups(&client_hello, supported_groups, + S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT - 1, &supported_groups_count); + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_INSUFFICIENT_MEM_SIZE); + EXPECT_EQUAL(supported_groups_count, 0); + + EXPECT_SUCCESS(s2n_stuffer_reread(&extension_stuffer)); + + /* Succeed with a correctly sized buffer. */ + EXPECT_SUCCESS(s2n_client_hello_get_supported_groups(&client_hello, supported_groups, + S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT, &supported_groups_count)); + EXPECT_EQUAL(supported_groups_count, S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT); + } + + /* Error if the client hello isn't parsed yet. */ + { + struct s2n_client_hello client_hello = { 0 }; + + uint16_t supported_groups[S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT] = { 0 }; + uint16_t supported_groups_count = 0; + int ret = s2n_client_hello_get_supported_groups(&client_hello, supported_groups, + s2n_array_len(supported_groups), &supported_groups_count); + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_EXTENSION_NOT_RECEIVED); + } + + /* Error if a supported groups extension wasn't received. */ + for (int disable_ecc = 0; disable_ecc <= 1; disable_ecc++) { + 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)); + if (disable_ecc) { + /* The 20150202 security policy doesn't contain any ECDHE cipher suites, so the + * supported groups extension won't be sent. + */ + EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "20150202")); + } else { + /* The 20170210 security policy contains ECDHE cipher suites, so the supported groups + * extension will be sent. + */ + EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "20170210")); + } + + DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_SUCCESS(s2n_connection_set_config(client, config)); + + 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_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); + + bool supported_groups_extension_exists = false; + EXPECT_SUCCESS(s2n_client_hello_has_extension(client_hello, S2N_EXTENSION_SUPPORTED_GROUPS, + &supported_groups_extension_exists)); + EXPECT_EQUAL(supported_groups_extension_exists, !disable_ecc); + + uint16_t supported_groups[S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT] = { 0 }; + uint16_t supported_groups_count = 0; + int ret = s2n_client_hello_get_supported_groups(client_hello, supported_groups, s2n_array_len(supported_groups), + &supported_groups_count); + + if (disable_ecc) { + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_EXTENSION_NOT_RECEIVED); + EXPECT_EQUAL(supported_groups_count, 0); + } else { + EXPECT_SUCCESS(ret); + + /* The 20170210 security policy contains 2 ECC curves. */ + EXPECT_EQUAL(supported_groups_count, 2); + } + } + + /* Test parsing a supported groups extension with a malformed groups list length. */ + { + struct s2n_client_hello client_hello = { 0 }; + + s2n_parsed_extension *supported_groups_extension = &client_hello.extensions.parsed_extensions[supported_groups_id]; + supported_groups_extension->extension_type = S2N_EXTENSION_SUPPORTED_GROUPS; + + /* Test parsing a correct groups list length */ + { + uint8_t extension_data[S2N_TEST_SUPPORTED_GROUPS_EXTENSION_SIZE] = { 0 }; + struct s2n_blob extension_blob = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&extension_blob, extension_data, sizeof(extension_data))); + supported_groups_extension->extension = extension_blob; + + struct s2n_stuffer extension_stuffer = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_init(&extension_stuffer, &extension_blob)); + + EXPECT_SUCCESS(s2n_stuffer_write_uint16(&extension_stuffer, S2N_TEST_SUPPORTED_GROUPS_LIST_SIZE)); + + uint16_t supported_groups[S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT] = { 0 }; + uint16_t supported_groups_count = 0; + EXPECT_SUCCESS(s2n_client_hello_get_supported_groups(&client_hello, supported_groups, + s2n_array_len(supported_groups), &supported_groups_count)); + + EXPECT_EQUAL(supported_groups_count, S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT); + } + + /* Test parsing a groups list length that is larger than the extension length */ + { + uint8_t extension_data[S2N_TEST_SUPPORTED_GROUPS_EXTENSION_SIZE] = { 0 }; + struct s2n_blob extension_blob = { 0 }; + uint32_t extension_too_small_size = S2N_TEST_SUPPORTED_GROUPS_EXTENSION_SIZE - 2; + EXPECT_SUCCESS(s2n_blob_init(&extension_blob, extension_data, extension_too_small_size)); + supported_groups_extension->extension = extension_blob; + + struct s2n_stuffer extension_stuffer = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_init(&extension_stuffer, &extension_blob)); + EXPECT_SUCCESS(s2n_stuffer_write_uint16(&extension_stuffer, S2N_TEST_SUPPORTED_GROUPS_LIST_SIZE)); + + uint16_t supported_groups[S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT] = { 0 }; + uint16_t supported_groups_count = 0; + int ret = s2n_client_hello_get_supported_groups(&client_hello, supported_groups, + s2n_array_len(supported_groups), &supported_groups_count); + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_INVALID_PARSED_EXTENSIONS); + + EXPECT_EQUAL(supported_groups_count, 0); + } + + /* Test parsing a groups list that contains a malformed supported group */ + { + uint8_t extension_data[S2N_TEST_SUPPORTED_GROUPS_EXTENSION_SIZE] = { 0 }; + struct s2n_blob extension_blob = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&extension_blob, extension_data, sizeof(extension_data))); + supported_groups_extension->extension = extension_blob; + + struct s2n_stuffer extension_stuffer = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_init(&extension_stuffer, &extension_blob)); + + uint16_t one_and_a_half_groups_size = 3; + EXPECT_SUCCESS(s2n_stuffer_write_uint16(&extension_stuffer, one_and_a_half_groups_size)); + + uint16_t supported_groups[S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT] = { 0 }; + uint16_t supported_groups_count = 0; + int ret = s2n_client_hello_get_supported_groups(&client_hello, supported_groups, + s2n_array_len(supported_groups), &supported_groups_count); + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_INVALID_PARSED_EXTENSIONS); + + EXPECT_EQUAL(supported_groups_count, 0); + } + } + + /* Ensure that the supported groups in the client hello are written to the output array. */ + { + struct s2n_client_hello client_hello = { 0 }; + + s2n_parsed_extension *supported_groups_extension = &client_hello.extensions.parsed_extensions[supported_groups_id]; + supported_groups_extension->extension_type = S2N_EXTENSION_SUPPORTED_GROUPS; + + for (uint16_t test_groups_count = 0; test_groups_count < S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT; test_groups_count++) { + uint16_t test_groups_list_size = test_groups_count * 2; + + uint8_t test_groups_list_data[S2N_TEST_SUPPORTED_GROUPS_LIST_SIZE] = { 0 }; + struct s2n_blob test_groups_list_blob = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&test_groups_list_blob, test_groups_list_data, test_groups_list_size)); + EXPECT_OK(s2n_get_public_random_data(&test_groups_list_blob)); + + uint8_t extension_data[S2N_TEST_SUPPORTED_GROUPS_EXTENSION_SIZE] = { 0 }; + struct s2n_blob extension_blob = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&extension_blob, extension_data, sizeof(extension_data))); + supported_groups_extension->extension = extension_blob; + + struct s2n_stuffer extension_stuffer = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_init(&extension_stuffer, &extension_blob)); + EXPECT_SUCCESS(s2n_stuffer_write_uint16(&extension_stuffer, test_groups_list_size)); + EXPECT_SUCCESS(s2n_stuffer_write(&extension_stuffer, &test_groups_list_blob)); + + uint16_t supported_groups[S2N_TEST_SUPPORTED_GROUPS_LIST_COUNT] = { 0 }; + uint16_t supported_groups_count = 0; + EXPECT_SUCCESS(s2n_client_hello_get_supported_groups(&client_hello, supported_groups, + s2n_array_len(supported_groups), &supported_groups_count)); + EXPECT_EQUAL(supported_groups_count, test_groups_count); + + struct s2n_stuffer test_groups_list_stuffer = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_init_written(&test_groups_list_stuffer, &test_groups_list_blob)); + + for (size_t i = 0; i < supported_groups_count; i++) { + uint16_t test_group = 0; + EXPECT_SUCCESS(s2n_stuffer_read_uint16(&test_groups_list_stuffer, &test_group)); + uint16_t written_group = supported_groups[i]; + + EXPECT_EQUAL(test_group, written_group); + } + } + } + + /* Self-talk: Ensure that the retrieved supported groups match what was sent by the client. + * + * This test also ensures that s2n_client_hello_get_supported_groups is usable from within the + * client hello callback. + */ + { + /* Test security policies with a range of different ECC curves and KEM groups. */ + const char *policies[] = { + "20170210", + "20190801", + "AWS-CRT-SDK-TLSv1.2-2023", + "20230317", + "20210816", + "PQ-TLS-1-0-2021-05-20", + "PQ-TLS-1-2-2023-04-08", + "test_all" + }; + + for (int version_index = 0; version_index < s2n_array_len(policies); version_index++) { + const char *policy = policies[version_index]; + + DEFER_CLEANUP(struct s2n_config *client_config = s2n_config_new(), s2n_config_ptr_free); + EXPECT_NOT_NULL(client_config); + EXPECT_SUCCESS(s2n_config_set_unsafe_for_testing(client_config)); + EXPECT_SUCCESS(s2n_config_set_cipher_preferences(client_config, policy)); + + DEFER_CLEANUP(struct s2n_config *server_config = s2n_config_new(), s2n_config_ptr_free); + EXPECT_NOT_NULL(server_config); + EXPECT_SUCCESS(s2n_config_set_unsafe_for_testing(server_config)); + EXPECT_SUCCESS(s2n_config_set_cipher_preferences(server_config, policy)); + EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(server_config, chain_and_key)); + + struct s2n_client_hello_context context = { + .invoked_count = 0, + }; + EXPECT_SUCCESS(s2n_config_set_client_hello_cb(server_config, s2n_check_received_supported_groups_cb, + &context)); + + DEFER_CLEANUP(struct s2n_connection *client_conn = s2n_connection_new(S2N_CLIENT), s2n_connection_ptr_free); + EXPECT_NOT_NULL(client_conn); + EXPECT_SUCCESS(s2n_connection_set_config(client_conn, client_config)); + + DEFER_CLEANUP(struct s2n_connection *server_conn = s2n_connection_new(S2N_SERVER), s2n_connection_ptr_free); + EXPECT_NOT_NULL(server_conn); + EXPECT_SUCCESS(s2n_connection_set_config(server_conn, server_config)); + + DEFER_CLEANUP(struct s2n_test_io_pair io_pair = { 0 }, s2n_io_pair_close); + EXPECT_SUCCESS(s2n_io_pair_init_non_blocking(&io_pair)); + EXPECT_SUCCESS(s2n_connection_set_io_pair(client_conn, &io_pair)); + EXPECT_SUCCESS(s2n_connection_set_io_pair(server_conn, &io_pair)); + + s2n_blocked_status blocked = S2N_NOT_BLOCKED; + EXPECT_OK(s2n_negotiate_until_message(client_conn, &blocked, SERVER_HELLO)); + + uint8_t sent_supported_groups_data[S2N_TEST_SUPPORTED_GROUPS_EXTENSION_SIZE]; + struct s2n_blob sent_supported_groups_blob = { 0 }; + EXPECT_SUCCESS(s2n_blob_init(&sent_supported_groups_blob, sent_supported_groups_data, + s2n_array_len(sent_supported_groups_data))); + + struct s2n_stuffer sent_supported_groups_stuffer = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_init(&sent_supported_groups_stuffer, &sent_supported_groups_blob)); + EXPECT_SUCCESS(s2n_client_supported_groups_extension.send(client_conn, &sent_supported_groups_stuffer)); + context.sent_supported_groups_extension = &sent_supported_groups_stuffer; + + EXPECT_SUCCESS(s2n_negotiate_test_server_and_client(server_conn, client_conn)); + + EXPECT_EQUAL(context.invoked_count, 1); + } + } + + END_TEST(); +} diff --git a/tests/unit/s2n_client_hello_test.c b/tests/unit/s2n_client_hello_test.c index 4d150223e81..d52ba6fac68 100644 --- a/tests/unit/s2n_client_hello_test.c +++ b/tests/unit/s2n_client_hello_test.c @@ -128,6 +128,49 @@ int main(int argc, char **argv) EXPECT_SUCCESS(s2n_connection_free(conn)); }; + /* Test s2n_client_hello_has_extension with a zero-length extension */ + for (int send_sct = 0; send_sct <= 1; send_sct++) { + 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)); + + /* The SCT extension is zero-length. */ + if (send_sct) { + EXPECT_SUCCESS(s2n_config_set_ct_support_level(config, S2N_CT_SUPPORT_REQUEST)); + } + + DEFER_CLEANUP(struct s2n_connection *client = s2n_connection_new(S2N_CLIENT), + s2n_connection_ptr_free); + EXPECT_SUCCESS(s2n_connection_set_config(client, config)); + + 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_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); + + s2n_parsed_extension *sct_extension = NULL; + int ret = s2n_client_hello_get_parsed_extension(S2N_EXTENSION_CERTIFICATE_TRANSPARENCY, &client_hello->extensions, + &sct_extension); + + if (send_sct) { + /* Ensure that the extension was received. */ + EXPECT_SUCCESS(ret); + POSIX_ENSURE_REF(sct_extension); + + /* Ensure that the extension is zero-length. */ + EXPECT_EQUAL(sct_extension->extension.size, 0); + } else { + /* The extension shouldn't have been received because it wasn't requested. */ + EXPECT_FAILURE_WITH_ERRNO(ret, S2N_ERR_EXTENSION_NOT_RECEIVED); + } + } + /* Test s2n_client_hello_get_raw_extension */ { uint8_t data[] = { @@ -1129,7 +1172,7 @@ int main(int argc, char **argv) EXPECT_EQUAL(s2n_client_hello_get_extension_length(client_hello, S2N_EXTENSION_CERTIFICATE_TRANSPARENCY), 0); EXPECT_NOT_NULL(ext_data = malloc(server_name_extension_len)); EXPECT_EQUAL(s2n_client_hello_get_extension_by_id(client_hello, S2N_EXTENSION_CERTIFICATE_TRANSPARENCY, ext_data, server_name_extension_len), 0); - EXPECT_EQUAL(s2n_errno, S2N_ERR_NULL); + EXPECT_EQUAL(s2n_errno, S2N_ERR_EXTENSION_NOT_RECEIVED); free(ext_data); ext_data = NULL; diff --git a/tls/extensions/s2n_client_supported_groups.c b/tls/extensions/s2n_client_supported_groups.c index 4f7e3e12c4e..5694f0d8789 100644 --- a/tls/extensions/s2n_client_supported_groups.c +++ b/tls/extensions/s2n_client_supported_groups.c @@ -78,6 +78,25 @@ static int s2n_client_supported_groups_send(struct s2n_connection *conn, struct return S2N_SUCCESS; } +S2N_RESULT s2n_supported_groups_parse_count(struct s2n_stuffer *extension, uint16_t *count) +{ + RESULT_ENSURE_REF(count); + *count = 0; + RESULT_ENSURE_REF(extension); + + uint16_t supported_groups_list_size = 0; + RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(extension, &supported_groups_list_size)); + + RESULT_ENSURE(supported_groups_list_size <= s2n_stuffer_data_available(extension), + S2N_ERR_INVALID_PARSED_EXTENSIONS); + RESULT_ENSURE(supported_groups_list_size % S2N_SUPPORTED_GROUP_SIZE == 0, + S2N_ERR_INVALID_PARSED_EXTENSIONS); + + *count = supported_groups_list_size / S2N_SUPPORTED_GROUP_SIZE; + + return S2N_RESULT_OK; +} + /* Populates the appropriate index of either the mutually_supported_curves or * mutually_supported_kem_groups array based on the received IANA ID. Will * ignore unrecognized IANA IDs (and return success). */ @@ -165,15 +184,14 @@ static int s2n_client_supported_groups_recv(struct s2n_connection *conn, struct POSIX_ENSURE_REF(conn); POSIX_ENSURE_REF(extension); - uint16_t size_of_all; - POSIX_GUARD(s2n_stuffer_read_uint16(extension, &size_of_all)); - if (size_of_all > s2n_stuffer_data_available(extension) || (size_of_all % sizeof(uint16_t))) { + uint16_t supported_groups_count = 0; + if (s2n_result_is_error(s2n_supported_groups_parse_count(extension, &supported_groups_count))) { /* Malformed length, ignore the extension */ return S2N_SUCCESS; } - for (size_t i = 0; i < (size_of_all / sizeof(uint16_t)); i++) { - uint16_t iana_id; + for (size_t i = 0; i < supported_groups_count; i++) { + uint16_t iana_id = 0; POSIX_GUARD(s2n_stuffer_read_uint16(extension, &iana_id)); POSIX_GUARD(s2n_client_supported_groups_recv_iana_id(conn, iana_id)); } diff --git a/tls/extensions/s2n_client_supported_groups.h b/tls/extensions/s2n_client_supported_groups.h index 322ac14813e..1033c514dd2 100644 --- a/tls/extensions/s2n_client_supported_groups.h +++ b/tls/extensions/s2n_client_supported_groups.h @@ -19,8 +19,12 @@ #include "tls/extensions/s2n_extension_type.h" #include "tls/s2n_connection.h" +#define S2N_SUPPORTED_GROUP_SIZE 2 + extern const s2n_extension_type s2n_client_supported_groups_extension; bool s2n_extension_should_send_if_ecc_enabled(struct s2n_connection *conn); +S2N_RESULT s2n_supported_groups_parse_count(struct s2n_stuffer *extension, uint16_t *count); + /* Old-style extension functions -- remove after extensions refactor is complete */ int s2n_recv_client_supported_groups(struct s2n_connection *conn, struct s2n_stuffer *extension); diff --git a/tls/s2n_client_hello.c b/tls/s2n_client_hello.c index dacfc0b9d88..cbc7cbe4baf 100644 --- a/tls/s2n_client_hello.c +++ b/tls/s2n_client_hello.c @@ -26,6 +26,7 @@ #include "crypto/s2n_rsa_signing.h" #include "error/s2n_errno.h" #include "stuffer/s2n_stuffer.h" +#include "tls/extensions/s2n_client_supported_groups.h" #include "tls/extensions/s2n_extension_list.h" #include "tls/extensions/s2n_server_key_share.h" #include "tls/s2n_alerts.h" @@ -863,7 +864,7 @@ int s2n_client_hello_get_parsed_extension(s2n_tls_extension_type extension_type, POSIX_GUARD(s2n_extension_supported_iana_value_to_id(extension_type, &extension_type_id)); s2n_parsed_extension *found_parsed_extension = &parsed_extension_list->parsed_extensions[extension_type_id]; - POSIX_ENSURE_REF(found_parsed_extension->extension.data); + POSIX_ENSURE(found_parsed_extension->extension.data, S2N_ERR_EXTENSION_NOT_RECEIVED); POSIX_ENSURE(found_parsed_extension->extension_type == extension_type, S2N_ERR_INVALID_PARSED_EXTENSIONS); *parsed_extension = found_parsed_extension; @@ -971,3 +972,34 @@ int s2n_client_hello_has_extension(struct s2n_client_hello *ch, uint16_t extensi } return S2N_SUCCESS; } + +int s2n_client_hello_get_supported_groups(struct s2n_client_hello *ch, uint16_t *groups, + uint16_t groups_count_max, uint16_t *groups_count_out) +{ + POSIX_ENSURE_REF(groups_count_out); + *groups_count_out = 0; + POSIX_ENSURE_REF(ch); + POSIX_ENSURE_REF(groups); + + s2n_parsed_extension *supported_groups_extension = NULL; + POSIX_GUARD(s2n_client_hello_get_parsed_extension(S2N_EXTENSION_SUPPORTED_GROUPS, &ch->extensions, &supported_groups_extension)); + POSIX_ENSURE_REF(supported_groups_extension); + + struct s2n_stuffer extension_stuffer = { 0 }; + POSIX_GUARD(s2n_stuffer_init_written(&extension_stuffer, &supported_groups_extension->extension)); + + uint16_t supported_groups_count = 0; + POSIX_GUARD_RESULT(s2n_supported_groups_parse_count(&extension_stuffer, &supported_groups_count)); + POSIX_ENSURE(supported_groups_count <= groups_count_max, S2N_ERR_INSUFFICIENT_MEM_SIZE); + + for (size_t i = 0; i < supported_groups_count; i++) { + /* s2n_stuffer_read_uint16 is used to read each of the supported groups in network-order + * endianness. + */ + POSIX_GUARD(s2n_stuffer_read_uint16(&extension_stuffer, &groups[i])); + } + + *groups_count_out = supported_groups_count; + + return S2N_SUCCESS; +}