Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
7 changes: 6 additions & 1 deletion tls/s2n_connection.c

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is connection reuse in scope for this optimization? s2n_connection_wipe preserves conn->config but resets required_hash_algs back to all 7, so a wiped-and-reused connection would re-hedge all 7 hashes unless set_config is called again.

Original file line number Diff line number Diff line change
Expand Up @@ -602,8 +602,13 @@ int s2n_connection_wipe(struct s2n_connection *conn)
POSIX_GUARD_RESULT(s2n_psk_parameters_init(&conn->psk_params));
conn->server_keying_material_lifetime = ONE_WEEK_IN_SEC;

/* Require all handshakes hashes. This set can be reduced as the handshake progresses. */
/* Require all handshakes hashes. This set can be reduced as the handshake progresses.
* s2n_connection_zero already reset required_hash_algs_narrowed, but reset it
* explicitly to keep the invariant local: a wiped connection requires all
* hashes and has not yet narrowed them. Renegotiation wipes the connection,
* so renegotiated handshakes re-narrow at their first transcript update. */
POSIX_GUARD(s2n_handshake_require_all_hashes(&conn->handshake));
conn->handshake.required_hash_algs_narrowed = 0;

if (conn->mode == S2N_SERVER) {
/* Start with the highest protocol version so that the highest common protocol version can be selected */
Expand Down
5 changes: 5 additions & 0 deletions tls/s2n_handshake.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ struct s2n_handshake {
/* Indicates that this is a renegotiation handshake */
unsigned renegotiation : 1;

/* Indicates that required_hash_algs has been narrowed based on the
* connection's security policy. Set lazily on the first transcript update,
* when the effective security policy is final. */
unsigned required_hash_algs_narrowed : 1;

s2n_state_machine state_machine;
};

Expand Down
33 changes: 33 additions & 0 deletions tls/s2n_handshake_transcript.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,39 @@ int s2n_conn_update_handshake_hashes(struct s2n_connection *conn, struct s2n_blo
struct s2n_handshake_hashes *hashes = conn->handshake.hashes;
POSIX_ENSURE_REF(hashes);

/* Lazily narrow the required transcript hashes to only those the security
* policy could need, so that unnecessary hashes are not updated for the rest
* of the handshake. This is done at the first transcript update, when the
* effective security policy is final, rather than in the policy setters:
* tests (and potentially applications) write conn->security_policy_override
* directly, bypassing any setter hooks.
*
* The effective policy is resolved like s2n_connection_get_security_policy
* (override first, then config), but inline: a missing config or policy is
* not an error here and just keeps the safe default of requiring all hashes.
*
* Note that s2n_conn_update_required_handshake_hashes later overwrites this
* bitmap once negotiation results are known. For TLS1.2 client auth it may
* re-enable ALL hashes, including some not in the policy's narrowed set,
* whose states will then be missing these earlier messages. That is still
* correct: the TLS1.2 CertificateVerify transcript hash is defined by the
* negotiated signature scheme, which is always chosen from the policy's
* signature preferences (see s2n_signature_algorithm_recv), and all hashes
* of TLS1.2-eligible schemes in those preferences are in the narrowed set
* and therefore have the full transcript. The extra re-enabled hash states
* are updated wastefully from that point on, but never read. */
if (!conn->handshake.required_hash_algs_narrowed) {
conn->handshake.required_hash_algs_narrowed = 1;
const struct s2n_security_policy *policy = conn->security_policy_override;
if (policy == NULL && conn->config != NULL) {
policy = conn->config->security_policy;
}
if (policy != NULL) {
POSIX_GUARD_RESULT(s2n_security_policy_get_required_hash_algs(policy,
conn->handshake.required_hash_algs));
}
}

/* MD5 and SHA1 are not permitted in FIPS mode, but an exception is made in
* order to continue to support TLS1.0 and TLS1.1. NIST SP 800-52r1 approves
* their continued use for the signature check in the CertificateVerify message
Expand Down
88 changes: 88 additions & 0 deletions tls/s2n_security_policies.c
Original file line number Diff line number Diff line change
Expand Up @@ -1912,6 +1912,91 @@ int s2n_connection_set_cipher_preferences(struct s2n_connection *conn, const cha
return S2N_SUCCESS;
}

/* Compute the hash algorithms that a security policy could require for the
* handshake transcript before cipher suite negotiation completes.
*
* Before the cipher suite and protocol version are negotiated, connections hedge
* by maintaining all transcript hashes. However, a security policy limits which
* hashes could actually be needed:
* - The TLS 1.0/1.1 PRF and legacy default signature schemes need MD5, SHA1,
* and the combined MD5+SHA1 hash, but only if the policy allows < TLS 1.2.
* - The TLS 1.2/1.3 PRF hash is defined by the negotiated cipher suite, so any
* cipher suite in the policy could contribute its PRF hash.
* - The TLS 1.2 CertificateVerify hash is defined by the negotiated signature
* scheme, which is always chosen from the policy's signature preferences.
* TLS 1.3 CertificateVerify uses the cipher suite's PRF hash instead, so
* TLS 1.3-only signature schemes do not contribute additional hashes.
*/
static S2N_RESULT s2n_security_policy_compute_required_hash_algs(
const struct s2n_security_policy *policy, uint8_t required[S2N_HASH_ALGS_COUNT])
{
RESULT_ENSURE_REF(policy);
RESULT_ENSURE_REF(required);

/* If the policy is missing information needed to narrow the required hashes,
* fall back to requiring all hashes. Policies set via the public API are
* validated to include signature preferences, but tests may construct
* incomplete policies.
*/
if (policy->cipher_preferences == NULL || policy->cipher_preferences->suites == NULL
|| policy->signature_preferences == NULL
|| policy->signature_preferences->signature_schemes == NULL) {
memset(required, 1, S2N_HASH_ALGS_COUNT);
return S2N_RESULT_OK;
}

memset(required, 0, S2N_HASH_ALGS_COUNT);

/* The TLS 1.0/1.1 PRF requires both MD5 and SHA1, and the legacy default
* signature schemes require SHA1 or the combined MD5+SHA1 hash. */
if (policy->minimum_protocol_version < S2N_TLS12) {
required[S2N_HASH_MD5] = 1;
required[S2N_HASH_SHA1] = 1;
required[S2N_HASH_MD5_SHA1] = 1;
}

for (size_t i = 0; i < policy->cipher_preferences->count; i++) {
const struct s2n_cipher_suite *cipher = policy->cipher_preferences->suites[i];
RESULT_ENSURE_REF(cipher);
s2n_hash_algorithm hash_alg = S2N_HASH_NONE;
RESULT_GUARD_POSIX(s2n_hmac_hash_alg(cipher->prf_alg, &hash_alg));
RESULT_ENSURE_LT(hash_alg, S2N_HASH_ALGS_COUNT);
required[hash_alg] = 1;
}

for (size_t i = 0; i < policy->signature_preferences->count; i++) {
const struct s2n_signature_scheme *scheme = policy->signature_preferences->signature_schemes[i];
RESULT_ENSURE_REF(scheme);
/* TLS 1.3 CertificateVerify signs the cipher suite's PRF hash, already
* accounted for above, so TLS 1.3-only schemes are skipped. */
if (scheme->minimum_protocol_version >= S2N_TLS13) {
continue;
}
RESULT_ENSURE_LT(scheme->hash_alg, S2N_HASH_ALGS_COUNT);
required[scheme->hash_alg] = 1;
}

return S2N_RESULT_OK;
}

S2N_RESULT s2n_security_policy_get_required_hash_algs(const struct s2n_security_policy *security_policy,
uint8_t out[S2N_HASH_ALGS_COUNT])
{
RESULT_ENSURE_REF(security_policy);
RESULT_ENSURE_REF(out);

for (int i = 0; security_policy_selection[i].version != NULL; i++) {
if (security_policy_selection[i].security_policy == security_policy) {
RESULT_CHECKED_MEMCPY(out, security_policy_selection[i].required_hash_algs, S2N_HASH_ALGS_COUNT);
return S2N_RESULT_OK;
}
}

/* If the policy is not in the official list, compute the result */
RESULT_GUARD(s2n_security_policy_compute_required_hash_algs(security_policy, out));
return S2N_RESULT_OK;
}

int s2n_security_policies_init()
{
for (int i = 0; security_policy_selection[i].version != NULL; i++) {
Expand Down Expand Up @@ -1967,6 +2052,9 @@ int s2n_security_policies_init()

POSIX_GUARD(s2n_validate_kem_preferences(kem_preference, security_policy_selection[i].pq_kem_extension_required));

POSIX_GUARD_RESULT(s2n_security_policy_compute_required_hash_algs(security_policy,
security_policy_selection[i].required_hash_algs));

/* Validate that security rules are correctly applied.
* This should be checked by a unit test, but outside of unit tests we
* check again here to cover the case where the unit tests are not run.
Expand Down
7 changes: 7 additions & 0 deletions tls/s2n_security_policies.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ struct s2n_security_policy_selection {
unsigned ecc_extension_required : 1;
unsigned pq_kem_extension_required : 1;
unsigned supports_tls13 : 1;
/* Pre-computed set of hash algorithms the policy could require for the
* handshake transcript before cipher suite negotiation completes.
* Indexed by s2n_hash_algorithm; 1 = required.
*/
uint8_t required_hash_algs[S2N_HASH_ALGS_COUNT];
};

extern struct s2n_security_policy_selection security_policy_selection[];
Expand Down Expand Up @@ -263,6 +268,8 @@ int s2n_find_security_policy_from_version(const char *version, const struct s2n_
const char *s2n_find_version_from_security_policy(const struct s2n_security_policy *security_policy);
int s2n_validate_kem_preferences(const struct s2n_kem_preferences *kem_preferences, bool pq_kem_extension_required);
S2N_RESULT s2n_validate_certificate_signature_preferences(const struct s2n_signature_preferences *s2n_certificate_signature_preferences);
S2N_RESULT s2n_security_policy_get_required_hash_algs(const struct s2n_security_policy *security_policy,
uint8_t out[S2N_HASH_ALGS_COUNT]);
S2N_RESULT s2n_security_policy_get_version(const struct s2n_security_policy *security_policy,
const char **version);
/* Checks to see if a certificate has a signature algorithm that's in our
Expand Down
Loading