From d6baf1f5d2ca2d8b03ee099e87f8b32e65eb76b6 Mon Sep 17 00:00:00 2001 From: Lindsay Stewart Date: Tue, 30 Apr 2024 10:39:06 -0700 Subject: [PATCH] feat: set certificate_authorities from trust store (#4509) --- api/s2n.h | 22 ++ error/s2n_errno.c | 1 + error/s2n_errno.h | 1 + .../S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.c | 31 +++ ...N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.flags | 0 tests/testlib/s2n_testlib.h | 2 + tests/unit/s2n_cert_authorities_test.c | 190 ++++++++++++++++++ tls/extensions/s2n_cert_authorities.c | 68 +++++++ tls/extensions/s2n_cert_authorities.h | 12 ++ 9 files changed, 327 insertions(+) create mode 100644 tests/features/S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.c create mode 100644 tests/features/S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.flags diff --git a/api/s2n.h b/api/s2n.h index 6ef207e090d..f30d59876a0 100644 --- a/api/s2n.h +++ b/api/s2n.h @@ -3763,6 +3763,28 @@ S2N_API int s2n_connection_serialize(struct s2n_connection *conn, uint8_t *buffe */ S2N_API int s2n_connection_deserialize(struct s2n_connection *conn, uint8_t *buffer, uint32_t buffer_length); +/* Load all acceptable certificate authorities from the currently configured trust store. + * + * The loaded certificate authorities will be advertised during the handshake. + * This can help your peer select a certificate if they have multiple certificate + * chains available. + * + * For now, s2n-tls only supports advertising certificate authorities to support + * client auth, so only servers will send the list of certificate authorities. + * + * To avoid configuration mistakes, certificate authorities cannot be loaded from + * a trust store that includes the default system certificates. That means that + * s2n_config_new_minimal or s2n_config_wipe_trust_store should be used. + * + * s2n-tls currently limits the total certificate authorities size to 10k bytes. + * This method will fail if the certificate authorities retrieved from the trust + * store exceed that limit. + * + * @param config A pointer to the s2n_config object. + * @returns S2N_SUCCESS on success. S2N_FAILURE on failure. + */ +S2N_API int s2n_config_set_cert_authorities_from_trust_store(struct s2n_config *config); + #ifdef __cplusplus } #endif diff --git a/error/s2n_errno.c b/error/s2n_errno.c index 2f64bd5f2fe..cea807ba489 100644 --- a/error/s2n_errno.c +++ b/error/s2n_errno.c @@ -311,6 +311,7 @@ static const char *no_such_error = "Internal s2n error"; ERR_ENTRY(S2N_ERR_MISSING_CERT_REQUEST, "Client requires mutual authentication, but server did not request a cert") \ ERR_ENTRY(S2N_ERR_MISSING_CLIENT_CERT, "Server requires client certificate") \ ERR_ENTRY(S2N_ERR_INVALID_SERIALIZED_CONNECTION, "Serialized connection is invalid"); \ + ERR_ENTRY(S2N_ERR_TOO_MANY_CAS, "Too many certificate authorities in trust store"); \ /* clang-format on */ #define ERR_STR_CASE(ERR, str) \ diff --git a/error/s2n_errno.h b/error/s2n_errno.h index ced98ecc5f5..838f7d10388 100644 --- a/error/s2n_errno.h +++ b/error/s2n_errno.h @@ -328,6 +328,7 @@ typedef enum { S2N_ERR_KTLS_KEY_LIMIT, S2N_ERR_SECURITY_POLICY_INCOMPATIBLE_CERT, S2N_ERR_INVALID_SERIALIZED_CONNECTION, + S2N_ERR_TOO_MANY_CAS, S2N_ERR_T_USAGE_END, } s2n_error; diff --git a/tests/features/S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.c b/tests/features/S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.c new file mode 100644 index 00000000000..bd9d370f34a --- /dev/null +++ b/tests/features/S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.c @@ -0,0 +1,31 @@ +/* + * 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 + +int main() { + /* X509_STORE_get0_objects appears to be the earliest method available that + * can retrieve all certificates from an X509_STORE. + * + * X509_STORE_get_by_subject and X509_STORE_get1_certs are available even + * earlier (Openssl-1.0.2), but both require known X509_NAMEs. + */ + STACK_OF(X509_OBJECT) *objects = X509_STORE_get0_objects(NULL); + X509 *cert = X509_OBJECT_get0_X509(NULL); + /* We could use i2d_X509_NAME instead if necessary, but X509_NAME_get0_der + * should be available where X509_STORE_get0_objects is */ + X509_NAME_get0_der(NULL, NULL, NULL); + return 0; +} diff --git a/tests/features/S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.flags b/tests/features/S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.flags new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/testlib/s2n_testlib.h b/tests/testlib/s2n_testlib.h index 160a439a2fe..78754fed2c1 100644 --- a/tests/testlib/s2n_testlib.h +++ b/tests/testlib/s2n_testlib.h @@ -192,6 +192,8 @@ S2N_RESULT s2n_connection_set_test_master_secret(struct s2n_connection *conn, co #define S2N_MIXED_CHAIN_KEY "../pems/mixed_chains/ecdsa/server-key.pem" #define S2N_MIXED_CHAIN_CA "../pems/mixed_chains/ecdsa/ca-cert.pem" +#define S2N_TEST_TRUST_STORE "../pems/trust-store/ca-bundle.crt" + #define S2N_DEFAULT_TEST_CERT_CHAIN S2N_RSA_2048_PKCS1_CERT_CHAIN #define S2N_DEFAULT_TEST_PRIVATE_KEY S2N_RSA_2048_PKCS1_KEY diff --git a/tests/unit/s2n_cert_authorities_test.c b/tests/unit/s2n_cert_authorities_test.c index a2a553c44fb..39d0baa317e 100644 --- a/tests/unit/s2n_cert_authorities_test.c +++ b/tests/unit/s2n_cert_authorities_test.c @@ -37,6 +37,79 @@ int main(int argc, char **argv) s2n_cert_authorities_extension.iana_value, &temp_id)); const s2n_extension_type_id ca_ext_id = temp_id; + /* Test: awslc should always support loading from the trust store */ + if (s2n_libcrypto_is_awslc()) { + EXPECT_TRUE(s2n_cert_authorities_supported_from_trust_store()); + } + + /* Test: s2n_config_set_cert_authorities_from_trust_store */ + { + /* Test: Safety */ + { + EXPECT_FAILURE_WITH_ERRNO( + s2n_config_set_cert_authorities_from_trust_store(NULL), + S2N_ERR_NULL); + }; + + /* Test: fails if not supported */ + { + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new_minimal(), + s2n_config_ptr_free); + EXPECT_NOT_NULL(config); + EXPECT_SUCCESS(s2n_config_set_verification_ca_location(config, + S2N_ECDSA_P512_CERT_CHAIN, NULL)); + + if (s2n_cert_authorities_supported_from_trust_store()) { + EXPECT_SUCCESS(s2n_config_set_cert_authorities_from_trust_store(config)); + EXPECT_NOT_EQUAL(config->cert_authorities.size, 0); + } else { + EXPECT_FAILURE_WITH_ERRNO( + s2n_config_set_cert_authorities_from_trust_store(config), + S2N_ERR_INTERNAL_LIBCRYPTO_ERROR); + EXPECT_EQUAL(config->cert_authorities.size, 0); + } + }; + + /* Test: not allowed with system trust store */ + { + /* s2n_config_new configures the default trust store */ + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free); + EXPECT_NOT_NULL(config); + + /* Fails with default system trust store */ + EXPECT_FAILURE_WITH_ERRNO( + s2n_config_set_cert_authorities_from_trust_store(config), + S2N_ERR_INVALID_STATE); + EXPECT_EQUAL(config->cert_authorities.size, 0); + + /* Succeeds again after wiping trust store */ + EXPECT_SUCCESS(s2n_config_wipe_trust_store(config)); + EXPECT_SUCCESS(s2n_config_set_cert_authorities_from_trust_store(config)); + EXPECT_EQUAL(config->cert_authorities.size, 0); + }; + + /* Test: empty trust store */ + { + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new_minimal(), s2n_config_ptr_free); + EXPECT_NOT_NULL(config); + EXPECT_SUCCESS(s2n_config_set_cert_authorities_from_trust_store(config)); + EXPECT_EQUAL(config->cert_authorities.size, 0); + }; + + /* Test: too many CAs in trust store */ + if (s2n_cert_authorities_supported_from_trust_store()) { + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new_minimal(), s2n_config_ptr_free); + EXPECT_NOT_NULL(config); + /* This is just a copy of the default trust store from an Amazon Linux instance */ + EXPECT_SUCCESS(s2n_config_set_verification_ca_location(config, S2N_TEST_TRUST_STORE, NULL)); + + EXPECT_FAILURE_WITH_ERRNO( + s2n_config_set_cert_authorities_from_trust_store(config), + S2N_ERR_TOO_MANY_CAS); + EXPECT_EQUAL(config->cert_authorities.size, 0); + }; + }; + /* Test: s2n_certificate_authorities_extension.send */ { /* Test: writes whatever CA data is available */ @@ -211,5 +284,122 @@ int main(int argc, char **argv) EXPECT_TRUE(S2N_CBIT_TEST(client->extension_requests_received, ca_ext_id)); }; + /* Known value test: compare our extension to openssl s_server */ + if (s2n_is_rsa_pss_certs_supported() && s2n_cert_authorities_supported_from_trust_store()) { + /* clang-format off */ + const struct { + const char *cert_name; + uint8_t expected_bytes_size; + uint8_t expected_bytes[1000]; + } test_cases[] = { + { + .cert_name = S2N_RSA_PSS_2048_SHA256_LEAF_CERT, + .expected_bytes_size = 32, + .expected_bytes = { + 0x00, 0x2f, 0x00, 0x1c, 0x00, 0x1a, 0x00, 0x18, + 0x30, 0x16, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x0b, 0x65, 0x78, 0x61, + 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d + }, + }, + { + .cert_name = S2N_ECDSA_P512_CERT_CHAIN, + .expected_bytes_size = 107, + .expected_bytes = { + 0x00, 0x2f, 0x00, 0x67, 0x00, 0x65, 0x00, 0x63, + 0x30, 0x61, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x02, 0x57, 0x41, 0x31, 0x10, 0x30, 0x0e, + 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x07, 0x53, + 0x65, 0x61, 0x74, 0x74, 0x6c, 0x65, 0x31, 0x0f, + 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, + 0x06, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, + 0x0c, 0x30, 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, + 0x0c, 0x03, 0x73, 0x32, 0x6e, 0x31, 0x14, 0x30, + 0x12, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0b, + 0x73, 0x32, 0x6e, 0x54, 0x65, 0x73, 0x74, 0x43, + 0x65, 0x72, 0x74 + }, + }, + { + .cert_name = S2N_RSA_2048_SHA256_URI_SANS_CERT, + .expected_bytes_size = 192, + .expected_bytes = { + 0x00, 0x2f, 0x00, 0xbc, 0x00, 0xba, 0x00, 0x53, + 0x30, 0x51, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, + 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x02, 0x57, 0x41, 0x31, 0x0f, 0x30, 0x0d, + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x41, + 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x0c, 0x30, + 0x0a, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x03, + 0x73, 0x32, 0x6e, 0x31, 0x16, 0x30, 0x14, 0x06, + 0x03, 0x55, 0x04, 0x03, 0x0c, 0x0d, 0x73, 0x32, + 0x6e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x00, 0x63, 0x30, 0x61, 0x31, + 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, + 0x13, 0x02, 0x55, 0x53, 0x31, 0x0b, 0x30, 0x09, + 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x02, 0x57, + 0x41, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, + 0x04, 0x07, 0x0c, 0x07, 0x53, 0x65, 0x61, 0x74, + 0x74, 0x6c, 0x65, 0x31, 0x0f, 0x30, 0x0d, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x41, 0x6d, + 0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x0c, 0x30, 0x0a, + 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x03, 0x73, + 0x32, 0x6e, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x0b, 0x73, 0x32, 0x6e, + 0x54, 0x65, 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x74 + }, + }, + { + .cert_name = S2N_RSA_2048_PKCS1_CERT_CHAIN, + .expected_bytes_size = 94, + .expected_bytes = { + 0x00, 0x2f, 0x00, 0x5a, 0x00, 0x58, 0x00, 0x1a, + 0x30, 0x18, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0c, 0x0d, 0x73, 0x32, 0x6e, + 0x54, 0x65, 0x73, 0x74, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x00, 0x20, 0x30, 0x1e, 0x31, 0x1c, + 0x30, 0x1a, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x13, 0x73, 0x32, 0x6e, 0x54, 0x65, 0x73, 0x74, + 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, + 0x69, 0x61, 0x74, 0x65, 0x00, 0x18, 0x30, 0x16, + 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x0c, 0x0b, 0x73, 0x32, 0x6e, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x6f, 0x6f, 0x74 + }, + }, + }; + /* clang-format on */ + + for (size_t i = 0; i < s2n_array_len(test_cases); i++) { + DEFER_CLEANUP(struct s2n_config *config = s2n_config_new_minimal(), + s2n_config_ptr_free); + EXPECT_SUCCESS(s2n_config_set_verification_ca_location(config, + test_cases[i].cert_name, NULL)); + + EXPECT_SUCCESS(s2n_config_set_cert_authorities_from_trust_store(config)); + + DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER), + s2n_connection_ptr_free); + EXPECT_SUCCESS(s2n_connection_set_config(conn, config)); + conn->actual_protocol_version = S2N_TLS13; + + DEFER_CLEANUP(struct s2n_stuffer output = { 0 }, s2n_stuffer_free); + EXPECT_SUCCESS(s2n_stuffer_growable_alloc(&output, 0)); + + EXPECT_SUCCESS(s2n_extension_send(&s2n_cert_authorities_extension, + conn, &output)); + + size_t output_size = s2n_stuffer_data_available(&output); + EXPECT_EQUAL(test_cases[i].expected_bytes_size, output_size); + + uint8_t *output_bytes = s2n_stuffer_raw_read(&output, output_size); + EXPECT_NOT_NULL(output_bytes); + EXPECT_BYTEARRAY_EQUAL(test_cases[i].expected_bytes, output_bytes, output_size); + } + }; + END_TEST(); } diff --git a/tls/extensions/s2n_cert_authorities.c b/tls/extensions/s2n_cert_authorities.c index 1043dfe89ee..5dcf562fe70 100644 --- a/tls/extensions/s2n_cert_authorities.c +++ b/tls/extensions/s2n_cert_authorities.c @@ -14,8 +14,76 @@ */ #include "tls/extensions/s2n_cert_authorities.h" +#include + #include "utils/s2n_safety.h" +bool s2n_cert_authorities_supported_from_trust_store() +{ +#if S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST + return true; +#else + return false; +#endif +} + +static S2N_RESULT s2n_cert_authorities_set_from_trust_store(struct s2n_config *config) +{ + RESULT_ENSURE_REF(config); + + if (!config->trust_store.trust_store) { + return S2N_RESULT_OK; + } + +#if S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST + DEFER_CLEANUP(struct s2n_stuffer output = { 0 }, s2n_stuffer_free); + RESULT_GUARD_POSIX(s2n_stuffer_growable_alloc(&output, 256)); + + STACK_OF(X509_OBJECT) *objects = X509_STORE_get0_objects(config->trust_store.trust_store); + RESULT_ENSURE(objects, S2N_ERR_INTERNAL_LIBCRYPTO_ERROR); + + int objects_count = sk_X509_OBJECT_num(objects); + RESULT_ENSURE(objects_count >= 0, S2N_ERR_INTERNAL_LIBCRYPTO_ERROR); + + for (int i = 0; i < objects_count; i++) { + X509_OBJECT *x509_object = sk_X509_OBJECT_value(objects, i); + RESULT_ENSURE(x509_object, S2N_ERR_INTERNAL_LIBCRYPTO_ERROR); + + X509 *cert = X509_OBJECT_get0_X509(x509_object); + if (cert == NULL) { + /* X509_OBJECTs can also be CRLs, resulting in NULL here. Skip. */ + continue; + } + + X509_NAME *name = X509_get_subject_name(cert); + RESULT_ENSURE(name, S2N_ERR_INTERNAL_LIBCRYPTO_ERROR); + + const uint8_t *name_bytes = NULL; + size_t name_size = 0; + RESULT_GUARD_OSSL(X509_NAME_get0_der(name, &name_bytes, &name_size), + S2N_ERR_INTERNAL_LIBCRYPTO_ERROR); + + RESULT_GUARD_POSIX(s2n_stuffer_write_uint16(&output, name_size)); + RESULT_GUARD_POSIX(s2n_stuffer_write_bytes(&output, name_bytes, name_size)); + RESULT_ENSURE(s2n_stuffer_data_available(&output) <= S2N_CERT_AUTHORITIES_MAX_SIZE, + S2N_ERR_TOO_MANY_CAS); + } + + RESULT_GUARD_POSIX(s2n_stuffer_extract_blob(&output, &config->cert_authorities)); + return S2N_RESULT_OK; +#else + RESULT_BAIL(S2N_ERR_INTERNAL_LIBCRYPTO_ERROR); +#endif +} + +int s2n_config_set_cert_authorities_from_trust_store(struct s2n_config *config) +{ + POSIX_ENSURE_REF(config); + POSIX_ENSURE(!config->trust_store.loaded_system_certs, S2N_ERR_INVALID_STATE); + POSIX_GUARD_RESULT(s2n_cert_authorities_set_from_trust_store(config)); + return S2N_SUCCESS; +} + int s2n_cert_authorities_send(struct s2n_connection *conn, struct s2n_stuffer *out) { POSIX_ENSURE_REF(conn); diff --git a/tls/extensions/s2n_cert_authorities.h b/tls/extensions/s2n_cert_authorities.h index 6821b1ad99a..20b7b3047db 100644 --- a/tls/extensions/s2n_cert_authorities.h +++ b/tls/extensions/s2n_cert_authorities.h @@ -19,6 +19,18 @@ #include "tls/extensions/s2n_extension_type.h" #include "tls/s2n_connection.h" +/* The only defined bound on the size of the certificate_authorities is the maximum + * size of an extension, UINT16_MAX. However, the full extensions list is also + * limited to UINT16_MAX, so all the extensions on a message combined cannot exceed + * UINT16_MAX. Other extensions could therefore limit the maximum size of the + * certificate_authorities extension. + * + * To keep the limit predictable and avoid surprise errors during negotiation, + * set a reasonable fixed limit. + */ +#define S2N_CERT_AUTHORITIES_MAX_SIZE (10000) + extern const s2n_extension_type s2n_cert_authorities_extension; +bool s2n_cert_authorities_supported_from_trust_store(); int s2n_cert_authorities_send(struct s2n_connection *conn, struct s2n_stuffer *out);