Skip to content

Commit

Permalink
feat: set certificate_authorities from trust store (#4509)
Browse files Browse the repository at this point in the history
  • Loading branch information
lrstewart authored Apr 30, 2024
1 parent e7ba5b3 commit d6baf1f
Show file tree
Hide file tree
Showing 9 changed files with 327 additions and 0 deletions.
22 changes: 22 additions & 0 deletions api/s2n.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions error/s2n_errno.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
1 change: 1 addition & 0 deletions error/s2n_errno.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
31 changes: 31 additions & 0 deletions tests/features/S2N_LIBCRYPTO_SUPPORTS_X509_STORE_LIST.c
Original file line number Diff line number Diff line change
@@ -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 <openssl/x509.h>

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;
}
Empty file.
2 changes: 2 additions & 0 deletions tests/testlib/s2n_testlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
190 changes: 190 additions & 0 deletions tests/unit/s2n_cert_authorities_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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();
}
68 changes: 68 additions & 0 deletions tls/extensions/s2n_cert_authorities.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,76 @@
*/
#include "tls/extensions/s2n_cert_authorities.h"

#include <openssl/x509.h>

#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);
Expand Down
12 changes: 12 additions & 0 deletions tls/extensions/s2n_cert_authorities.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

0 comments on commit d6baf1f

Please sign in to comment.