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: apply cert signature preferences locally #4407

Merged
merged 22 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
11 changes: 11 additions & 0 deletions crypto/s2n_certificate.c
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ int s2n_cert_chain_and_key_load(struct s2n_cert_chain_and_key *chain_and_key)

DEFER_CLEANUP(X509 *leaf_cert = NULL, X509_free_pointer);
POSIX_GUARD_RESULT(s2n_openssl_x509_parse(&head->raw, &leaf_cert));
POSIX_GUARD_RESULT(s2n_openssl_x509_get_cert_info(leaf_cert, &head->info));

/* Parse the leaf cert for the public key and certificate type */
DEFER_CLEANUP(struct s2n_pkey public_key = { 0 }, s2n_pkey_free);
Expand All @@ -383,6 +384,16 @@ int s2n_cert_chain_and_key_load(struct s2n_cert_chain_and_key *chain_and_key)
head->ec_curve_nid = nid;
}

/* populate libcrypto nid's required for cert restrictions */
struct s2n_cert *current = head->next;
while (current != NULL) {
DEFER_CLEANUP(X509 *parsed_cert = NULL, X509_free_pointer);
POSIX_GUARD_RESULT(s2n_openssl_x509_parse(&current->raw, &parsed_cert));
POSIX_GUARD_RESULT(s2n_openssl_x509_get_cert_info(parsed_cert, &current->info));

current = current->next;
}

return S2N_SUCCESS;
}

Expand Down
1 change: 1 addition & 0 deletions crypto/s2n_certificate.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct s2n_cert {
s2n_pkey_type pkey_type;
uint16_t ec_curve_nid;
s2n_cert_public_key public_key;
struct s2n_cert_info info;
maddeleine marked this conversation as resolved.
Show resolved Hide resolved
struct s2n_blob raw;
struct s2n_cert *next;
};
Expand Down
2 changes: 2 additions & 0 deletions tests/testlib/s2n_testlib.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ S2N_RESULT s2n_connection_set_test_master_secret(struct s2n_connection *conn, co
#define S2N_IP_V6_LO_RSA_KEY "../pems/sni/ip_v6_lo_rsa_key.pem"
#define S2N_WITHOUT_CN_RSA_CERT "../pems/sni/without_cn_rsa_cert.pem"
#define S2N_WITHOUT_CN_RSA_KEY "../pems/sni/without_cn_rsa_key.pem"
#define S2N_KITTEN_SAN_CHAIN "../pems/san_with_ca/kitten-chain.pem"
#define S2N_KITTEN_SAN_KEY "../pems/san_with_ca/kitten-key.pem"

#define S2N_DHPARAMS_2048 "../pems/dhparams_2048.pem"

Expand Down
26 changes: 26 additions & 0 deletions tests/unit/s2n_cert_chain_and_key_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,32 @@ int main(int argc, char **argv)
};
};

/* s2n_cert_chain_and_key_load_pem */
{
/* when loading a chain, all certs have a info associated with them and root is self-signed */
{
DEFER_CLEANUP(struct s2n_cert_chain_and_key *cert_chain = NULL,
s2n_cert_chain_and_key_ptr_free);
EXPECT_SUCCESS(s2n_test_cert_permutation_load_server_chain(&cert_chain, "ec", "ecdsa",
"p384", "sha256"));
struct s2n_cert *leaf = cert_chain->cert_chain->head;
EXPECT_EQUAL(leaf->info.self_signed, false);
EXPECT_EQUAL(leaf->info.signature_nid, NID_ecdsa_with_SHA256);
EXPECT_EQUAL(leaf->info.signature_digest_nid, NID_sha256);

struct s2n_cert *intermediate = leaf->next;
jmayclin marked this conversation as resolved.
Show resolved Hide resolved
EXPECT_EQUAL(intermediate->info.self_signed, false);
EXPECT_EQUAL(intermediate->info.signature_nid, NID_ecdsa_with_SHA256);
EXPECT_EQUAL(intermediate->info.signature_digest_nid, NID_sha256);

struct s2n_cert *root = intermediate->next;
EXPECT_NULL(root->next);
EXPECT_EQUAL(root->info.self_signed, true);
EXPECT_EQUAL(root->info.signature_nid, NID_ecdsa_with_SHA256);
EXPECT_EQUAL(root->info.signature_digest_nid, NID_sha256);
};
};

EXPECT_SUCCESS(s2n_io_pair_close(&io_pair));
EXPECT_SUCCESS(s2n_config_free(client_config));

Expand Down
93 changes: 93 additions & 0 deletions tests/unit/s2n_config_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "tls/s2n_security_policies.h"
#include "tls/s2n_tls13.h"
#include "unstable/npn.h"
#include "utils/s2n_map.h"

#define S2N_TEST_MAX_SUPPORTED_GROUPS_COUNT 30

Expand Down Expand Up @@ -1086,5 +1087,97 @@ int main(int argc, char **argv)
}
}

/* Test s2n_config_validate_certificate_preferences*/
jmayclin marked this conversation as resolved.
Show resolved Hide resolved
{
DEFER_CLEANUP(struct s2n_cert_chain_and_key *ecdsa_p384_sha256 = NULL,
s2n_cert_chain_and_key_ptr_free);
DEFER_CLEANUP(struct s2n_cert_chain_and_key *ecdsa_p384_sha384 = NULL,
s2n_cert_chain_and_key_ptr_free);
EXPECT_SUCCESS(s2n_test_cert_chain_and_key_new(&ecdsa_p384_sha256, S2N_KITTEN_SAN_CHAIN,
S2N_KITTEN_SAN_KEY));
EXPECT_SUCCESS(s2n_test_cert_permutation_load_server_chain(&ecdsa_p384_sha384, "ec",
"ecdsa", "p384", "sha384"));

/* rfc9151 doesn't allow SHA256 signatures, but does allow SHA384 signatures,
* so ecdsa_p384_sha256 is invalid and ecdsa_p384_sha384 is valid */

/* valid certs are accepted */
{
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, ecdsa_p384_sha384));
EXPECT_OK(
s2n_config_validate_certificate_preferences(config, &security_policy_rfc9151));
jmayclin marked this conversation as resolved.
Show resolved Hide resolved
};

/* when cert preferences don't apply locally, invalid certs are accepted */
{
const struct s2n_signature_scheme *const test_sig_scheme_list[] = {
&s2n_ecdsa_sha384,
};

const struct s2n_signature_preferences test_certificate_signature_preferences = {
.count = s2n_array_len(test_sig_scheme_list),
.signature_schemes = test_sig_scheme_list,
};

const struct s2n_security_policy test_sp = {
.certificate_signature_preferences = &test_certificate_signature_preferences,
.certificate_preferences_apply_locally = false,
};

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, ecdsa_p384_sha256));
EXPECT_OK(s2n_config_validate_certificate_preferences(config, &test_sp));
};

/* Certs in an s2n_config are stored in default_certs_by_type, domain_name_to_cert_map, or
* both. We want to ensure that the s2n_config_validate_certificate_preferences method will
* validate certs in both locations.*/
jmayclin marked this conversation as resolved.
Show resolved Hide resolved

/* certs in default_certs_by_type are validated */
{
/* s2n_config_set_cert_chain_and_key_defaults populates default_certs_by_type
* but doesn't populate domain_name_to_cert_map
*/
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_SUCCESS(
s2n_config_set_cert_chain_and_key_defaults(config, &ecdsa_p384_sha256, 1));

/* domain certs is empty */
uint32_t domain_certs_count = 0;
EXPECT_OK(s2n_map_size(config->domain_name_to_cert_map, &domain_certs_count));
EXPECT_EQUAL(domain_certs_count, 0);
EXPECT_EQUAL(s2n_config_get_num_default_certs(config), 1);

/* certs in default_certs_by_type are validated */
EXPECT_ERROR(
s2n_config_validate_certificate_preferences(config, &security_policy_rfc9151));
jmayclin marked this conversation as resolved.
Show resolved Hide resolved
};

/* certs in the domain map are validated */
{
/* default_certs_by_type will only hold the most recently added cert
* of a particular cert type, but all certs with different SANs will
* be stored in domain_name_to_cert_map. */
jmayclin marked this conversation as resolved.
Show resolved Hide resolved
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, ecdsa_p384_sha384));
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, ecdsa_p384_sha256));

/* default_certs_by_type contains a single valid cert. */
EXPECT_EQUAL(s2n_config_get_num_default_certs(config), 1);
EXPECT_EQUAL(config->default_certs_by_type.certs[S2N_PKEY_TYPE_ECDSA],
ecdsa_p384_sha384);

/* domain_name_to_cert_map contains two certs and therefore must hold
* an invalid cert. */
uint32_t domain_certs_count = 0;
EXPECT_OK(s2n_map_size(config->domain_name_to_cert_map, &domain_certs_count));
EXPECT_EQUAL(domain_certs_count, 2);

/* certs in domain_map are validated */
EXPECT_ERROR(
s2n_config_validate_certificate_preferences(config, &security_policy_rfc9151));
};
};
END_TEST();
}
161 changes: 161 additions & 0 deletions tests/unit/s2n_security_policy_cert_preferences_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,167 @@ int main(int argc, char **argv)
};
};

/* s2n_security_policy_validate_certificate_chain */
{
int valid_sig_nid = s2n_ecdsa_sha256.libcrypto_nid;
int valid_hash_nid = 0;
EXPECT_SUCCESS(s2n_hash_NID_type(s2n_ecdsa_sha256.hash_alg, &valid_hash_nid));

int invalid_sig_nid = s2n_rsa_pkcs1_sha256.libcrypto_nid;
int invalid_hash_nid = 0;
EXPECT_SUCCESS(s2n_hash_NID_type(s2n_rsa_pkcs1_sha256.hash_alg, &valid_hash_nid));

struct s2n_cert_info valid = { .self_signed = false,
.signature_nid = valid_sig_nid,
.signature_digest_nid = valid_hash_nid };
jmayclin marked this conversation as resolved.
Show resolved Hide resolved
struct s2n_cert root = { 0 };
root.info = valid;
root.info.self_signed = true;

struct s2n_cert intermediate = { 0 };
intermediate.info = valid;
intermediate.next = &root;

struct s2n_cert leaf = { 0 };
leaf.info = valid;
leaf.next = &intermediate;

struct s2n_cert_chain cert_chain = { 0 };
cert_chain.head = &leaf;

struct s2n_cert_chain_and_key chain = { 0 };
chain.cert_chain = &cert_chain;
/* valid chain */
jmayclin marked this conversation as resolved.
Show resolved Hide resolved
{
EXPECT_OK(s2n_security_policy_validate_certificate_chain(&test_sp, &chain));
};

/* an invalid root signature is ignored */
{
root.info.signature_nid = invalid_sig_nid;
root.info.signature_digest_nid = invalid_sig_nid;
EXPECT_OK(s2n_security_policy_validate_certificate_chain(&test_sp, &chain));
};

/* an invalid intermediate causes a failure */
{
intermediate.info.signature_nid = invalid_sig_nid;
intermediate.info.signature_digest_nid = invalid_sig_nid;
EXPECT_ERROR(s2n_security_policy_validate_certificate_chain(&test_sp, &chain));
}
};

DEFER_CLEANUP(struct s2n_cert_chain_and_key *cert = NULL, s2n_cert_chain_and_key_ptr_free);
EXPECT_SUCCESS(
s2n_test_cert_permutation_load_server_chain(&cert, "ec", "ecdsa", "p384", "sha256"));
/* s2n_config cases */
maddeleine marked this conversation as resolved.
Show resolved Hide resolved
{
/* configure security policy then load an invalid cert */
{
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "rfc9151"));

EXPECT_FAILURE(s2n_config_add_cert_chain_and_key_to_store(config, cert));
jmayclin marked this conversation as resolved.
Show resolved Hide resolved

/* assert that no certs were loaded */
uint32_t domain_certs = 0;
EXPECT_EQUAL(s2n_config_get_num_default_certs(config), 0);
EXPECT_SUCCESS(s2n_map_size(config->domain_name_to_cert_map, &domain_certs));
EXPECT_EQUAL(domain_certs, 0);
EXPECT_EQUAL(s2n_config_get_num_default_certs(config), 0);
};

/* load a cert then configure an invalid security policy */
{
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, cert));
const struct s2n_security_policy *default_sp = config->security_policy;
EXPECT_FAILURE(s2n_config_set_cipher_preferences(config, "rfc9151"));

/* assert that the security policy was not changed */
EXPECT_EQUAL(config->security_policy, default_sp);
};
};

/* s2n_connection cases */
{
/* setup a config with the default security policy and the test cert */
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, cert));

/* set a config then set an invalid security policy override */
{
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_SUCCESS(s2n_connection_set_config(conn, config));
EXPECT_FAILURE(s2n_connection_set_cipher_preferences(conn, "rfc9151"));

/* assert that the security policy override was not successful */
EXPECT_NULL(conn->security_policy_override);
};
/* set a security_policy_override then set an invalid config */
{
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
EXPECT_SUCCESS(s2n_connection_set_cipher_preferences(conn, "rfc9151"));
struct s2n_config *default_config = conn->config;
EXPECT_FAILURE(s2n_connection_set_config(conn, config));

/* assert that the config was not changed */
EXPECT_EQUAL(conn->config, default_config);
};
};

/* certificate_signature_preferences_apply_locally behavior tests */
jmayclin marked this conversation as resolved.
Show resolved Hide resolved
{
/* for this test we need a security policy that doesn't apply cert preferences locally */
const struct s2n_security_policy *non_local_sp = &security_policy_default_fips;
EXPECT_FALSE(non_local_sp->certificate_preferences_apply_locally);

DEFER_CLEANUP(struct s2n_cert_chain_and_key *cert = NULL, s2n_cert_chain_and_key_ptr_free);
EXPECT_SUCCESS(s2n_test_cert_permutation_load_server_chain(&cert, "rsae", "pss", "4096",
"sha384"));

/* confirm that the cert does not respect certificate signature preferences */
EXPECT_ERROR(s2n_security_policy_validate_certificate_chain(non_local_sp, cert));

/* security policy can be set on a non-compliant config */
{
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, cert));
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "default_fips"));
};

/* non-compliant certs can be loaded into a config */
{
DEFER_CLEANUP(struct s2n_config *config = s2n_config_new(), s2n_config_ptr_free);
EXPECT_SUCCESS(s2n_config_set_cipher_preferences(config, "default_fips"));
EXPECT_SUCCESS(s2n_config_add_cert_chain_and_key_to_store(config, cert));
};

/* security policy can be set on a non-compliant connection */
{
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
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, cert));

EXPECT_SUCCESS(s2n_connection_set_config(conn, config));
EXPECT_SUCCESS(s2n_connection_set_cipher_preferences(conn, "default_fips"));
};

/* non-compliant certs can still be used with a connection policy override */
{
DEFER_CLEANUP(struct s2n_connection *conn = s2n_connection_new(S2N_SERVER),
s2n_connection_ptr_free);
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, cert));

EXPECT_SUCCESS(s2n_connection_set_cipher_preferences(conn, "default_fips"));
EXPECT_SUCCESS(s2n_connection_set_config(conn, config));
};
};

END_TEST();
return S2N_SUCCESS;
}
Loading
Loading