diff --git a/api/s2n.h b/api/s2n.h index 4ebf27c3e68..4e1a34abe9f 100644 --- a/api/s2n.h +++ b/api/s2n.h @@ -1992,6 +1992,8 @@ S2N_API extern uint64_t s2n_connection_get_delay(struct s2n_connection *conn); * @warning Do NOT set a lower blinding delay unless you understand the risks and have other * mitigations for timing side channels in place. * + * @note This delay needs to be set lower than any timeouts, such as your TCP socket timeout. + * * @param config The config object being updated. * @param seconds The maximum number of seconds that s2n-tls will delay for in the event of a * sensitive error. diff --git a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs index 82f23b8a6c2..312464a24b1 100644 --- a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs +++ b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs @@ -236,7 +236,7 @@ mod tests { callbacks::{ClientHelloCallback, ConnectionFuture, ConnectionFutureResult}, enums::ClientAuthType, error::ErrorType, - testing::{client_hello::*, s2n_tls::*, *}, + testing::{client_hello::*, *}, }; use alloc::sync::Arc; use core::sync::atomic::Ordering; @@ -246,13 +246,13 @@ mod tests { #[test] fn handshake_default() { let config = build_config(&security::DEFAULT).unwrap(); - establish_connection(config); + assert!(TestPair::handshake_with_config(&config).is_ok()); } #[test] fn handshake_default_tls13() { let config = build_config(&security::DEFAULT_TLS13).unwrap(); - establish_connection(config) + assert!(TestPair::handshake_with_config(&config).is_ok()); } #[test] @@ -368,44 +368,20 @@ mod tests { config.build()? }; - let server = { - // create and configure a server connection - let mut server = crate::connection::Connection::new_server(); - server.set_config(config.clone())?; - server.set_waker(Some(&waker))?; - Harness::new(server) - }; - - let client = { - // create a client connection - let mut client = crate::connection::Connection::new_client(); - client.set_config(config)?; - Harness::new(client) - }; + let mut pair = TestPair::from_config(&config); + pair.server.set_waker(Some(&waker))?; + let s2n_err = pair.handshake().unwrap_err(); + // the underlying error should be the custom error the application provided + let app_err = s2n_err.application_error().unwrap(); + let io_err = app_err.downcast_ref::().unwrap(); + let _custom_err = io_err + .get_ref() + .unwrap() + .downcast_ref::() + .unwrap(); - let mut pair = Pair::new(server, client); - loop { - match pair.poll() { - Poll::Ready(result) => { - let err = result.expect_err("handshake should fail"); - - // the underlying error should be the custom error the application provided - let s2n_err = err.downcast_ref::().unwrap(); - let app_err = s2n_err.application_error().unwrap(); - let io_err = app_err.downcast_ref::().unwrap(); - let _custom_err = io_err - .get_ref() - .unwrap() - .downcast_ref::() - .unwrap(); - break; - } - Poll::Pending => continue, - } - } // assert that the future is async returned Poll::Pending once assert_eq!(wake_count, 1); - Ok(()) } @@ -422,24 +398,10 @@ mod tests { config.build()? }; - let server = { - // create and configure a server connection - let mut server = crate::connection::Connection::new_server(); - server.set_config(config.clone())?; - server.set_waker(Some(&waker))?; - Harness::new(server) - }; - - let client = { - // create a client connection - let mut client = crate::connection::Connection::new_client(); - client.set_config(config)?; - Harness::new(client) - }; - - let pair = Pair::new(server, client); + let mut pair = TestPair::from_config(&config); + pair.server.set_waker(Some(&waker))?; + pair.handshake()?; - poll_tls_pair(pair); // confirm that the callback returned Pending `require_pending_count` times assert_eq!(wake_count, require_pending_count); // confirm that the final invoked count is +1 more than `require_pending_count` @@ -450,6 +412,7 @@ mod tests { Ok(()) } + #[test] fn client_hello_callback_sync() -> Result<(), Error> { let (waker, wake_count) = new_count_waker(); @@ -491,25 +454,12 @@ mod tests { config.build()? }; - let server = { - // create and configure a server connection - let mut server = crate::connection::Connection::new_server(); - server.set_config(config.clone())?; - server.set_waker(Some(&waker))?; - Harness::new(server) - }; - - let client = { - // create a client connection - let mut client = crate::connection::Connection::new_client(); - client.set_config(config)?; - Harness::new(client) - }; - - let pair = Pair::new(server, client); + let mut pair = TestPair::from_config(&config); + pair.server.set_waker(Some(&waker))?; assert_eq!(callback.count(), 0); - poll_tls_pair(pair); + + pair.handshake()?; assert_eq!(callback.count(), 1); assert_eq!(wake_count, 0); Ok(()) @@ -538,7 +488,7 @@ mod tests { builder.load_pem(&fs::read(&cert)?, &fs::read(&key)?)?; builder.trust_location(Some(&cert), None)?; - establish_connection(builder.build()?); + TestPair::handshake_with_config(&builder.build()?)?; Ok(()) } @@ -576,13 +526,9 @@ mod tests { config.build()? }; - let mut pair = tls_pair(config); - pair.server - .0 - .connection_mut() - .set_waker(Some(&noop_waker()))?; - - poll_tls_pair(pair); + let mut pair = TestPair::from_config(&config); + pair.server.set_waker(Some(&noop_waker()))?; + pair.handshake()?; } Ok(()) } @@ -602,23 +548,17 @@ mod tests { }; // confirm that default connection establishment fails - let mut pair = tls_pair(reject_config.clone()); - assert!(poll_tls_pair_result(&mut pair).is_err()); + let mut pair = TestPair::from_config(&reject_config); + assert!(pair.handshake().is_err()); // confirm that overriding the verify_host_callback on connection causes // the handshake to succeed - pair = tls_pair(reject_config); - pair.server - .0 - .connection - .set_verify_host_callback(InsecureAcceptAllCertificatesHandler {}) - .unwrap(); + let mut pair = TestPair::from_config(&reject_config); pair.client - .0 - .connection - .set_verify_host_callback(InsecureAcceptAllCertificatesHandler {}) - .unwrap(); - assert!(poll_tls_pair_result(&mut pair).is_ok()); + .set_verify_host_callback(InsecureAcceptAllCertificatesHandler {})?; + pair.server + .set_verify_host_callback(InsecureAcceptAllCertificatesHandler {})?; + pair.handshake()?; Ok(()) } @@ -633,24 +573,10 @@ mod tests { config.build()? }; - let server = { - let mut server = crate::connection::Connection::new_server(); - server.set_config(config.clone())?; - Harness::new(server) - }; - - let client = { - let mut client = crate::connection::Connection::new_client(); - client.set_config(config)?; - Harness::new(client) - }; - - let pair = Pair::new(server, client); - let pair = poll_tls_pair(pair); - let server = pair.server.0.connection; - let client = pair.client.0.connection; + let mut pair = TestPair::from_config(&config); + pair.handshake()?; - for conn in [server, client] { + for conn in [pair.server, pair.client] { assert!(!conn.client_cert_used()); let cert = conn.client_cert_chain_bytes()?; assert!(cert.is_none()); @@ -673,28 +599,14 @@ mod tests { config.build()? }; - let server = { - let mut server = crate::connection::Connection::new_server(); - server.set_config(config.clone())?; - Harness::new(server) - }; - - let client = { - let mut client = crate::connection::Connection::new_client(); - client.set_config(config)?; - Harness::new(client) - }; - - let pair = Pair::new(server, client); - let pair = poll_tls_pair(pair); - let server = pair.server.0.connection; - let client = pair.client.0.connection; + let mut pair = TestPair::from_config(&config); + pair.handshake()?; - let cert = server.client_cert_chain_bytes()?; + let cert = pair.server.client_cert_chain_bytes()?; assert!(cert.is_some()); assert!(!cert.unwrap().is_empty()); - for conn in [server, client] { + for conn in [pair.server, pair.client] { assert!(conn.client_cert_used()); let sig_alg = conn.selected_client_signature_algorithm()?; assert!(sig_alg.is_some()); @@ -706,7 +618,7 @@ mod tests { } #[test] - fn system_certs_loaded_by_default() { + fn system_certs_loaded_by_default() -> Result<(), Error> { let keypair = CertKeyPair::default(); // Load the server certificate into the trust store by overriding the OpenSSL default @@ -714,20 +626,18 @@ mod tests { temp_env::with_var("SSL_CERT_FILE", Some(keypair.cert_path()), || { let mut builder = Builder::new(); builder - .load_pem(keypair.cert(), keypair.key()) - .unwrap() - .set_security_policy(&security::DEFAULT_TLS13) - .unwrap() - .set_verify_host_callback(InsecureAcceptAllCertificatesHandler {}) - .unwrap(); + .load_pem(keypair.cert(), keypair.key())? + .set_security_policy(&security::DEFAULT_TLS13)? + .set_verify_host_callback(InsecureAcceptAllCertificatesHandler {})?; let config = builder.build().unwrap(); - establish_connection(config); - }); + TestPair::handshake_with_config(&config)?; + Ok(()) + }) } #[test] - fn disable_loading_system_certs() { + fn disable_loading_system_certs() -> Result<(), Error> { let keypair = CertKeyPair::default(); // Load the server certificate into the trust store by overriding the OpenSSL default @@ -736,24 +646,19 @@ mod tests { // Test the Builder itself, and also the Builder produced by the Config builder() API. for mut builder in [Builder::new(), Config::builder()] { builder - .load_pem(keypair.cert(), keypair.key()) - .unwrap() - .set_security_policy(&security::DEFAULT_TLS13) - .unwrap() - .set_verify_host_callback(InsecureAcceptAllCertificatesHandler {}) - .unwrap(); + .load_pem(keypair.cert(), keypair.key())? + .set_security_policy(&security::DEFAULT_TLS13)? + .set_verify_host_callback(InsecureAcceptAllCertificatesHandler {})?; // Disable loading system certificates - builder.with_system_certs(false).unwrap(); + builder.with_system_certs(false)?; - let config = builder.build().unwrap(); + let config = builder.build()?; let mut config_with_system_certs = config.clone(); - let mut pair = tls_pair(config); - // System certificates should not be loaded into the trust store. The handshake // should fail since the certificate should not be trusted. - assert!(poll_tls_pair_result(&mut pair).is_err()); + assert!(TestPair::handshake_with_config(&config).is_err()); // The handshake should succeed after trusting the certificate. unsafe { @@ -761,9 +666,10 @@ mod tests { config_with_system_certs.as_mut_ptr(), ); } - establish_connection(config_with_system_certs); + TestPair::handshake_with_config(&config_with_system_certs)?; } - }); + Ok(()) + }) } #[test] @@ -776,24 +682,10 @@ mod tests { config.build()? }; - let server = { - let mut server = crate::connection::Connection::new_server(); - server.set_config(config.clone())?; - Harness::new(server) - }; - - let client = { - let mut client = crate::connection::Connection::new_client(); - client.set_config(config)?; - Harness::new(client) - }; - - let pair = Pair::new(server, client); - let pair = poll_tls_pair(pair); - let server = pair.server.0.connection; - let client = pair.client.0.connection; + let mut pair = TestPair::from_config(&config); + pair.handshake()?; - for conn in [server, client] { + for conn in [pair.server, pair.client] { let chain = conn.peer_cert_chain()?; assert_eq!(chain.len(), 1); for cert in chain.iter() { @@ -816,29 +708,15 @@ mod tests { config.build()? }; - let server = { - let mut server = crate::connection::Connection::new_server(); - server.set_config(config.clone())?; - Harness::new(server) - }; - - let client = { - let mut client = crate::connection::Connection::new_client(); - client.set_config(config)?; - Harness::new(client) - }; + let mut pair = TestPair::from_config(&config); // None before handshake... - assert!(server.connection.selected_cert().is_none()); - assert!(client.connection.selected_cert().is_none()); - - let pair = Pair::new(server, client); + assert!(pair.server.selected_cert().is_none()); + assert!(pair.client.selected_cert().is_none()); - let pair = poll_tls_pair(pair); - let server = pair.server.0.connection; - let client = pair.client.0.connection; + pair.handshake()?; - for conn in [&server, &client] { + for conn in [&pair.server, &pair.client] { let chain = conn.selected_cert().unwrap(); assert_eq!(chain.len(), 1); for cert in chain.iter() { @@ -851,14 +729,14 @@ mod tests { // Same config is used for both and we are doing mTLS, so both should select the same // certificate. assert_eq!( - server + pair.server .selected_cert() .unwrap() .iter() .next() .unwrap()? .der()?, - client + pair.client .selected_cert() .unwrap() .iter() @@ -874,12 +752,11 @@ mod tests { fn master_secret_success() -> Result<(), Error> { let policy = security::Policy::from_version("test_all_tls12")?; let config = config_builder(&policy)?.build()?; - let pair = poll_tls_pair(tls_pair(config)); - let server = pair.server.0.connection; - let client = pair.client.0.connection; + let mut pair = TestPair::from_config(&config); + pair.handshake()?; - let server_secret = server.master_secret()?; - let client_secret = client.master_secret()?; + let server_secret = pair.server.master_secret()?; + let client_secret = pair.client.master_secret()?; assert_eq!(server_secret, client_secret); Ok(()) @@ -888,16 +765,13 @@ mod tests { #[test] fn master_secret_failure() -> Result<(), Error> { // TLS1.3 does not support getting the master secret - let config = config_builder(&security::DEFAULT_TLS13)?.build()?; - let pair = poll_tls_pair(tls_pair(config)); - let server = pair.server.0.connection; - let client = pair.client.0.connection; - - let server_error = server.master_secret().unwrap_err(); - assert_eq!(server_error.kind(), ErrorType::UsageError); + let mut pair = TestPair::from_config(&build_config(&security::DEFAULT_TLS13)?); + pair.handshake()?; - let client_error = client.master_secret().unwrap_err(); - assert_eq!(client_error.kind(), ErrorType::UsageError); + for conn in [pair.client, pair.server] { + let err = conn.master_secret().unwrap_err(); + assert_eq!(err.kind(), ErrorType::UsageError); + } Ok(()) } @@ -912,26 +786,21 @@ mod tests { send_key_updates: 0, }; - let pair = tls_pair(build_config(&security::DEFAULT_TLS13)?); - let mut pair = poll_tls_pair(pair); + let mut pair = TestPair::from_config(&build_config(&security::DEFAULT_TLS13)?); + pair.handshake()?; // there haven't been any key updates at the start of the connection - let client_updates = pair.client.0.connection.as_ref().key_update_counts()?; - assert_eq!(client_updates, empty_key_updates); - let server_updates = pair.server.0.connection.as_ref().key_update_counts()?; - assert_eq!(server_updates, empty_key_updates); + assert_eq!(pair.client.key_update_counts()?, empty_key_updates); + assert_eq!(pair.server.key_update_counts()?, empty_key_updates); pair.server - .0 - .connection - .as_mut() .request_key_update(PeerKeyUpdate::KeyUpdateNotRequested)?; - assert!(pair.poll_send(Mode::Server, &[0]).is_ready()); + assert!(pair.server.poll_send(&[0]).is_ready()); // the server send key has been updated - let client_updates = pair.client.0.connection.as_ref().key_update_counts()?; + let client_updates = pair.client.key_update_counts()?; assert_eq!(client_updates, empty_key_updates); - let server_updates = pair.server.0.connection.as_ref().key_update_counts()?; + let server_updates = pair.server.key_update_counts()?; assert_eq!(server_updates.recv_key_updates, 0); assert_eq!(server_updates.send_key_updates, 1); diff --git a/tests/cbmc/aws-verification-model-for-libcrypto b/tests/cbmc/aws-verification-model-for-libcrypto index 440a07ca02d..d732f7257fc 160000 --- a/tests/cbmc/aws-verification-model-for-libcrypto +++ b/tests/cbmc/aws-verification-model-for-libcrypto @@ -1 +1 @@ -Subproject commit 440a07ca02d60ff8158cb99fb89833de35d76ae6 +Subproject commit d732f7257fc569e1be32d34037b111af0a058ab0 diff --git a/tests/cbmc/proofs/Makefile.common b/tests/cbmc/proofs/Makefile.common index ce5315c5533..43a3cab6aa6 100644 --- a/tests/cbmc/proofs/Makefile.common +++ b/tests/cbmc/proofs/Makefile.common @@ -299,8 +299,8 @@ CHECKFLAGS += $(CBMC_FLAG_UNSIGNED_OVERFLOW_CHECK) NONDET_STATIC ?= # Flags to pass to goto-cc for compilation and linking -COMPILE_FLAGS ?= -Wall -LINK_FLAGS ?= -Wall +COMPILE_FLAGS ?= -Wall -Werror +LINK_FLAGS ?= -Wall -Werror EXPORT_FILE_LOCAL_SYMBOLS ?= --export-file-local-symbols # During instrumentation, it adds models of C library functions diff --git a/tests/fuzz/runFuzzTest.sh b/tests/fuzz/runFuzzTest.sh index e6f11e12f0a..2e7312bfe05 100755 --- a/tests/fuzz/runFuzzTest.sh +++ b/tests/fuzz/runFuzzTest.sh @@ -203,7 +203,7 @@ then COVERAGE_FAILURE_ALLOWED=1 fi - if [ "$FEATURE_COVERAGE" -lt $MIN_FEATURES_COVERED && COVERAGE_FAILURE_ALLOWED -eq 0 ]; then + if [[ "$FEATURE_COVERAGE" -lt $MIN_FEATURES_COVERED && COVERAGE_FAILURE_ALLOWED -eq 0 ]]; then printf "\033[31;1mERROR!\033[0m ${TEST_NAME} only covers ${FEATURE_COVERAGE} features, which is below ${MIN_FEATURES_COVERED}! This may be due to missing corpus files or a bug.\n" exit -1; fi diff --git a/tests/unit/s2n_extensions_server_key_share_select_test.c b/tests/unit/s2n_extensions_server_key_share_select_test.c index efd707c1c5b..9591a792fc6 100644 --- a/tests/unit/s2n_extensions_server_key_share_select_test.c +++ b/tests/unit/s2n_extensions_server_key_share_select_test.c @@ -383,8 +383,7 @@ int main() EXPECT_SUCCESS(s2n_connection_free(server_conn)); }; - /* When client sent valid keyshares - * only for ECC, server should choose curves[0] and not send HRR. */ + /* When client sent KeyShares only for ECC but also supports PQ, server should choose PQ and send HRR. */ { struct s2n_connection *server_conn = NULL; EXPECT_NOT_NULL(server_conn = s2n_connection_new(S2N_SERVER)); @@ -422,13 +421,12 @@ int main() EXPECT_SUCCESS(s2n_extensions_server_key_share_select(server_conn)); - /* Server should update its choice to curve[0], no HRR */ - EXPECT_EQUAL(server_conn->kex_params.server_ecc_evp_params.negotiated_curve, ecc_pref->ecc_curves[0]); - EXPECT_NULL(server_params->kem_group); - EXPECT_NULL(server_params->kem_params.kem); - EXPECT_NULL(server_params->ecc_params.negotiated_curve); + EXPECT_NULL(server_conn->kex_params.server_ecc_evp_params.negotiated_curve); + EXPECT_EQUAL(server_params->kem_group, kem_group0); + EXPECT_EQUAL(server_params->kem_params.kem, kem_group0->kem); + EXPECT_EQUAL(server_params->ecc_params.negotiated_curve, kem_group0->curve); EXPECT_NULL(server_conn->kex_params.client_kem_group_params.kem_group); - EXPECT_FALSE(s2n_is_hello_retry_handshake(server_conn)); + EXPECT_TRUE(s2n_is_hello_retry_handshake(server_conn)); EXPECT_SUCCESS(s2n_connection_free(server_conn)); }; diff --git a/tests/unit/s2n_fingerprint_test.c b/tests/unit/s2n_fingerprint_test.c new file mode 100644 index 00000000000..9b06e9a2838 --- /dev/null +++ b/tests/unit/s2n_fingerprint_test.c @@ -0,0 +1,249 @@ +/* + * 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 "tls/s2n_fingerprint.h" + +#include "crypto/s2n_hash.h" +#include "s2n_test.h" +#include "testlib/s2n_testlib.h" + +#define S2N_TEST_HASH S2N_HASH_SHA256 +#define TEST_COUNT 10 + +static S2N_RESULT s2n_test_hash_state_new(struct s2n_hash_state *hash_state) +{ + EXPECT_SUCCESS(s2n_hash_new(hash_state)); + EXPECT_SUCCESS(s2n_hash_init(hash_state, S2N_TEST_HASH)); + return S2N_RESULT_OK; +} + +int main(int argc, char **argv) +{ + BEGIN_TEST(); + + const char test_char = '!'; + const char test_str[] = "hello"; + const size_t test_str_len = strlen(test_str); + EXPECT_NOT_EQUAL(test_char, test_str[0]); + + const uint8_t test_str_digest[] = { + 0x2c, 0xf2, 0x4d, 0xba, 0x5f, 0xb0, 0xa3, 0xe, 0x26, 0xe8, 0x3b, + 0x2a, 0xc5, 0xb9, 0xe2, 0x9e, 0x1b, 0x16, 0x1e, 0x5c, 0x1f, 0xa7, + 0x42, 0x5e, 0x73, 0x4, 0x33, 0x62, 0x93, 0x8b, 0x98, 0x24 + }; + + /* Test s2n_fingerprint_hash_add_char */ + { + /* Safety */ + EXPECT_ERROR_WITH_ERRNO(s2n_fingerprint_hash_add_char(NULL, test_char), + S2N_ERR_NULL); + + /* Add to stuffer */ + { + DEFER_CLEANUP(struct s2n_stuffer output = { 0 }, s2n_stuffer_free); + EXPECT_SUCCESS(s2n_stuffer_alloc(&output, TEST_COUNT)); + struct s2n_fingerprint_hash hash = { .buffer = &output }; + + for (size_t i = 1; i <= TEST_COUNT; i++) { + EXPECT_OK(s2n_fingerprint_hash_add_char(&hash, test_char)); + EXPECT_EQUAL(s2n_stuffer_data_available(&output), 1); + + char actual_value = 0; + EXPECT_SUCCESS(s2n_stuffer_read_char(&output, &actual_value)); + EXPECT_EQUAL(actual_value, test_char); + } + }; + + /* Add to hash */ + { + DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free); + EXPECT_OK(s2n_test_hash_state_new(&hash_state)); + struct s2n_fingerprint_hash hash = { .hash = &hash_state }; + + for (size_t i = 1; i <= TEST_COUNT; i++) { + EXPECT_OK(s2n_fingerprint_hash_add_char(&hash, test_char)); + EXPECT_EQUAL(hash.hash->currently_in_hash, i); + } + }; + + /* Error due to insufficient space */ + { + DEFER_CLEANUP(struct s2n_stuffer output = { 0 }, s2n_stuffer_free); + struct s2n_fingerprint_hash hash = { .buffer = &output }; + EXPECT_ERROR_WITH_ERRNO(s2n_fingerprint_hash_add_char(&hash, test_char), + S2N_ERR_INSUFFICIENT_MEM_SIZE); + + EXPECT_SUCCESS(s2n_stuffer_alloc(&output, 1)); + EXPECT_OK(s2n_fingerprint_hash_add_char(&hash, test_char)); + EXPECT_ERROR_WITH_ERRNO(s2n_fingerprint_hash_add_char(&hash, test_char), + S2N_ERR_INSUFFICIENT_MEM_SIZE); + }; + }; + + /* Test s2n_fingerprint_hash_add_str */ + { + /* Safety */ + { + /* Null hash */ + EXPECT_ERROR_WITH_ERRNO(s2n_fingerprint_hash_add_str(NULL, test_str, 0), + S2N_ERR_NULL); + + /* Null str with stuffer */ + { + DEFER_CLEANUP(struct s2n_stuffer output = { 0 }, s2n_stuffer_free); + EXPECT_SUCCESS(s2n_stuffer_alloc(&output, 100)); + struct s2n_fingerprint_hash hash = { .buffer = &output }; + EXPECT_ERROR_WITH_ERRNO(s2n_fingerprint_hash_add_str(&hash, NULL, 10), + S2N_ERR_NULL); + EXPECT_OK(s2n_fingerprint_hash_add_str(&hash, NULL, 0)); + }; + + /* Null str with hash */ + { + DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free); + EXPECT_OK(s2n_test_hash_state_new(&hash_state)); + struct s2n_fingerprint_hash hash = { .hash = &hash_state }; + EXPECT_ERROR_WITH_ERRNO(s2n_fingerprint_hash_add_str(&hash, NULL, 10), + S2N_ERR_NULL); + EXPECT_OK(s2n_fingerprint_hash_add_str(&hash, NULL, 0)); + }; + }; + + /* Add to stuffer */ + { + DEFER_CLEANUP(struct s2n_stuffer output = { 0 }, s2n_stuffer_free); + EXPECT_SUCCESS(s2n_stuffer_alloc(&output, test_str_len * TEST_COUNT)); + struct s2n_fingerprint_hash hash = { .buffer = &output }; + + for (size_t i = 1; i <= TEST_COUNT; i++) { + EXPECT_OK(s2n_fingerprint_hash_add_str(&hash, test_str, test_str_len)); + EXPECT_EQUAL(s2n_stuffer_data_available(&output), test_str_len); + + uint8_t actual_value[sizeof(test_str)] = { 0 }; + EXPECT_SUCCESS(s2n_stuffer_read_bytes(&output, actual_value, test_str_len)); + EXPECT_BYTEARRAY_EQUAL(actual_value, test_str, test_str_len); + } + }; + + /* Add to hash */ + { + DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free); + EXPECT_OK(s2n_test_hash_state_new(&hash_state)); + struct s2n_fingerprint_hash hash = { .hash = &hash_state }; + + for (size_t i = 1; i <= TEST_COUNT; i++) { + EXPECT_OK(s2n_fingerprint_hash_add_str(&hash, test_str, test_str_len)); + EXPECT_EQUAL(hash.hash->currently_in_hash, test_str_len * i); + } + }; + + /* Error due to insufficient space */ + { + struct s2n_stuffer output = { 0 }; + struct s2n_fingerprint_hash hash = { .buffer = &output }; + EXPECT_ERROR_WITH_ERRNO( + s2n_fingerprint_hash_add_str(&hash, test_str, test_str_len), + S2N_ERR_INSUFFICIENT_MEM_SIZE); + + EXPECT_SUCCESS(s2n_stuffer_alloc(&output, 1)); + EXPECT_ERROR_WITH_ERRNO( + s2n_fingerprint_hash_add_str(&hash, test_str, test_str_len), + S2N_ERR_INSUFFICIENT_MEM_SIZE); + EXPECT_SUCCESS(s2n_stuffer_free(&output)); + + EXPECT_SUCCESS(s2n_stuffer_alloc(&output, test_str_len - 1)); + EXPECT_ERROR_WITH_ERRNO( + s2n_fingerprint_hash_add_str(&hash, test_str, test_str_len), + S2N_ERR_INSUFFICIENT_MEM_SIZE); + EXPECT_SUCCESS(s2n_stuffer_free(&output)); + + EXPECT_SUCCESS(s2n_stuffer_alloc(&output, test_str_len)); + EXPECT_OK(s2n_fingerprint_hash_add_char(&hash, test_char)); + EXPECT_ERROR_WITH_ERRNO( + s2n_fingerprint_hash_add_str(&hash, test_str, test_str_len), + S2N_ERR_INSUFFICIENT_MEM_SIZE); + EXPECT_SUCCESS(s2n_stuffer_free(&output)); + }; + }; + + /* Test s2n_fingerprint_hash_digest */ + { + /* Safety */ + { + uint8_t output_value[1] = { 0 }; + EXPECT_ERROR_WITH_ERRNO( + s2n_fingerprint_hash_digest(NULL, output_value, sizeof(output_value)), + S2N_ERR_NULL); + }; + + /* Digest successfully calculated */ + { + DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free); + EXPECT_OK(s2n_test_hash_state_new(&hash_state)); + struct s2n_fingerprint_hash hash = { .hash = &hash_state }; + + EXPECT_OK(s2n_fingerprint_hash_add_str(&hash, test_str, test_str_len)); + EXPECT_EQUAL(hash.hash->currently_in_hash, test_str_len); + + uint8_t actual_digest[sizeof(test_str_digest)] = { 0 }; + EXPECT_OK(s2n_fingerprint_hash_digest(&hash, actual_digest, sizeof(actual_digest))); + EXPECT_BYTEARRAY_EQUAL(actual_digest, test_str_digest, sizeof(test_str_digest)); + EXPECT_EQUAL(hash.bytes_digested, test_str_len); + }; + + /* Hash can be reused after digest */ + { + DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free); + EXPECT_OK(s2n_test_hash_state_new(&hash_state)); + struct s2n_fingerprint_hash hash = { .hash = &hash_state }; + + const size_t count = 10; + for (size_t i = 0; i < count; i++) { + uint8_t actual_digest[sizeof(test_str_digest)] = { 0 }; + EXPECT_OK(s2n_fingerprint_hash_add_str(&hash, test_str, test_str_len)); + EXPECT_OK(s2n_fingerprint_hash_digest(&hash, actual_digest, sizeof(actual_digest))); + EXPECT_BYTEARRAY_EQUAL(actual_digest, test_str_digest, sizeof(test_str_digest)); + } + EXPECT_EQUAL(hash.bytes_digested, test_str_len * count); + }; + }; + + /* Test s2n_fingerprint_hash_do_digest */ + { + /* Safety */ + EXPECT_FALSE(s2n_fingerprint_hash_do_digest(NULL)); + + struct s2n_fingerprint_hash hash = { 0 }; + EXPECT_FALSE(s2n_fingerprint_hash_do_digest(&hash)); + + struct s2n_stuffer output = { 0 }; + hash.buffer = &output; + EXPECT_FALSE(s2n_fingerprint_hash_do_digest(&hash)); + + struct s2n_hash_state hash_state = { 0 }; + hash.hash = &hash_state; + EXPECT_TRUE(s2n_fingerprint_hash_do_digest(&hash)); + }; + + /* Test s2n_assert_grease_value */ + { + EXPECT_TRUE(s2n_is_grease_value(0x0A0A)); + EXPECT_TRUE(s2n_is_grease_value(0xFAFA)); + EXPECT_FALSE(s2n_is_grease_value(0x0000)); + EXPECT_FALSE(s2n_is_grease_value(0x0001)); + }; + + END_TEST(); +} diff --git a/tests/unit/s2n_tls13_pq_handshake_test.c b/tests/unit/s2n_tls13_pq_handshake_test.c index 74f26f0c288..d8301c761de 100644 --- a/tests/unit/s2n_tls13_pq_handshake_test.c +++ b/tests/unit/s2n_tls13_pq_handshake_test.c @@ -26,31 +26,94 @@ /* Include C file directly to access static functions */ #include "tls/s2n_handshake_io.c" -const struct s2n_kem_group *s2n_get_highest_priority_shared_kem_group(const struct s2n_kem_preferences *client_prefs, const struct s2n_kem_preferences *server_prefs) +const struct s2n_kem_group *s2n_get_predicted_negotiated_kem_group(const struct s2n_kem_preferences *client_prefs, const struct s2n_kem_preferences *server_prefs) { PTR_ENSURE_REF(client_prefs); PTR_ENSURE_REF(server_prefs); - for (int i = 0; i < client_prefs->tls13_kem_group_count; i++) { - for (int j = 0; j < server_prefs->tls13_kem_group_count; j++) { - const struct s2n_kem_group *client_group = client_prefs->tls13_kem_groups[i]; - const struct s2n_kem_group *server_group = server_prefs->tls13_kem_groups[j]; + + /* Client will offer their highest priority PQ KeyShare in their ClientHello. This PQ KeyShare + * will be most preferred since it can be negotiated in 1-RTT (even if there are other mutually + * supported PQ KeyShares that the server would prefer over this one but would require 2-RTT's). + */ + const struct s2n_kem_group *client_default = client_prefs->tls13_kem_groups[0]; + PTR_ENSURE_REF(client_default); + + for (int i = 0; i < server_prefs->tls13_kem_group_count; i++) { + const struct s2n_kem_group *server_group = server_prefs->tls13_kem_groups[i]; + PTR_ENSURE_REF(server_group); + if (s2n_kem_group_is_available(client_default) && s2n_kem_group_is_available(server_group) + && client_default->iana_id == server_group->iana_id + && s2n_kem_group_is_available(client_default)) { + return client_default; + } + } + + /* Otherwise, if the client's default isn't supported, and a 2-RTT PQ handshake is required, the server will choose + * whichever mutually supported PQ KeyShare that is highest on the server's preference list. */ + for (int i = 0; i < server_prefs->tls13_kem_group_count; i++) { + const struct s2n_kem_group *server_group = server_prefs->tls13_kem_groups[i]; + + /* j starts at 1 since we already checked client_prefs->tls13_kem_groups[0] above */ + for (int j = 1; j < client_prefs->tls13_kem_group_count; j++) { + const struct s2n_kem_group *client_group = client_prefs->tls13_kem_groups[j]; PTR_ENSURE_REF(client_group); PTR_ENSURE_REF(server_group); if (s2n_kem_group_is_available(client_group) && s2n_kem_group_is_available(server_group) - && s2n_kem_group_is_available(client_group) == s2n_kem_group_is_available(server_group)) { + && client_group->iana_id == server_group->iana_id + && s2n_kem_group_is_available(client_group)) { return client_group; } } } + + return NULL; +} + +const struct s2n_ecc_named_curve *s2n_get_predicted_negotiated_ecdhe_curve(const struct s2n_security_policy *client_sec_policy, + const struct s2n_security_policy *server_sec_policy) +{ + PTR_ENSURE_REF(client_sec_policy); + PTR_ENSURE_REF(server_sec_policy); + + /* Client will offer their highest priority ECDHE KeyShare in their ClientHello. This KeyShare + * will be most preferred since it can be negotiated in 1-RTT (even if there are other mutually + * supported ECDHE KeyShares that the server would prefer over this one but would require 2-RTT's). + */ + const struct s2n_ecc_named_curve *client_default = client_sec_policy->ecc_preferences->ecc_curves[0]; + PTR_ENSURE_REF(client_default); + + for (int i = 0; i < server_sec_policy->ecc_preferences->count; i++) { + const struct s2n_ecc_named_curve *server_curve = server_sec_policy->ecc_preferences->ecc_curves[i]; + PTR_ENSURE_REF(server_curve); + if (server_curve->iana_id == client_default->iana_id) { + return client_default; + } + } + + /* Otherwise, if the client's default isn't supported, and a 2-RTT handshake is required, the server will choose + * whichever mutually supported PQ KeyShare that is highest on the server's preference list. */ + for (int i = 0; i < server_sec_policy->ecc_preferences->count; i++) { + const struct s2n_ecc_named_curve *server_curve = server_sec_policy->ecc_preferences->ecc_curves[i]; + + /* j starts at 1 since we already checked client_sec_policy->ecc_preferences->ecc_curves[0] above */ + for (int j = 1; j < client_sec_policy->ecc_preferences->count; j++) { + const struct s2n_ecc_named_curve *client_curve = client_sec_policy->ecc_preferences->ecc_curves[j]; + PTR_ENSURE_REF(client_curve); + PTR_ENSURE_REF(server_curve); + if (client_curve->iana_id == server_curve->iana_id) { + return client_curve; + } + } + } + return NULL; } int s2n_test_tls13_pq_handshake(const struct s2n_security_policy *client_sec_policy, - const struct s2n_security_policy *server_sec_policy, + const struct s2n_security_policy *server_sec_policy, const struct s2n_kem_group *expected_kem_group, const struct s2n_ecc_named_curve *expected_curve, bool hrr_expected, bool len_prefix_expected) { /* XOR check: can expect to negotiate either a KEM group, or a classic EC curve, but not both/neither */ - const struct s2n_kem_group *expected_kem_group = s2n_get_highest_priority_shared_kem_group(client_sec_policy->kem_preferences, server_sec_policy->kem_preferences); POSIX_ENSURE((expected_kem_group == NULL) != (expected_curve == NULL), S2N_ERR_SAFETY); /* Set up connections */ @@ -118,11 +181,7 @@ int s2n_test_tls13_pq_handshake(const struct s2n_security_policy *client_sec_pol /* Server sends ServerHello or HRR */ POSIX_GUARD(s2n_conn_set_handshake_type(server_conn)); - if (hrr_expected) { - POSIX_ENSURE_EQ(s2n_conn_get_current_message_type(server_conn), HELLO_RETRY_MSG); - } else { - POSIX_ENSURE_EQ(s2n_conn_get_current_message_type(server_conn), SERVER_HELLO); - } + POSIX_ENSURE_EQ(hrr_expected, s2n_handshake_type_check_tls13_flag(server_conn, HELLO_RETRY_REQUEST)); POSIX_GUARD(s2n_handshake_write_io(server_conn)); /* Server sends CCS */ @@ -343,15 +402,16 @@ int main() .ecc_preferences = security_policy_test_tls13_retry.ecc_preferences, }; - const struct s2n_ecc_named_curve *expected_curve = &s2n_ecc_curve_x25519; + const struct s2n_ecc_named_curve *default_curve = &s2n_ecc_curve_x25519; if (!s2n_is_evp_apis_supported()) { - expected_curve = &s2n_ecc_curve_secp256r1; + default_curve = &s2n_ecc_curve_secp256r1; } struct pq_handshake_test_vector { const struct s2n_security_policy *client_policy; const struct s2n_security_policy *server_policy; + const struct s2n_kem_group *expected_kem_group; const struct s2n_ecc_named_curve *expected_curve; bool hrr_expected; bool len_prefix_expected; @@ -361,10 +421,19 @@ int main() * If PQ is disabled, the expected negotiation outcome is overridden below * before performing the handshake test. */ const struct pq_handshake_test_vector test_vectors[] = { + { + .client_policy = &security_policy_pq_tls_1_3_2023_06_01, + .server_policy = &security_policy_pq_tls_1_0_2021_05_24, + .expected_kem_group = &s2n_x25519_kyber_512_r3, + .expected_curve = NULL, + .hrr_expected = s2n_pq_is_enabled(), + .len_prefix_expected = false, + }, /* Server and Client both support PQ and TLS 1.3 */ { .client_policy = &security_policy_pq_tls_1_1_2021_05_21, .server_policy = &security_policy_pq_tls_1_1_2021_05_21, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -372,6 +441,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2021_05_22, .server_policy = &security_policy_pq_tls_1_0_2021_05_22, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -379,6 +449,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2021_05_23, .server_policy = &security_policy_pq_tls_1_0_2021_05_23, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -386,6 +457,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2021_05_24, .server_policy = &security_policy_pq_tls_1_0_2021_05_24, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -393,6 +465,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2021_05_26, .server_policy = &security_policy_pq_tls_1_0_2021_05_26, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -400,6 +473,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2023_01_24, .server_policy = &security_policy_pq_tls_1_0_2023_01_24, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = false, @@ -411,6 +485,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_3_2023_06_01, .server_policy = &security_policy_pq_tls_1_3_2023_06_01, + .expected_kem_group = &s2n_secp256r1_kyber_768_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = false, @@ -418,6 +493,7 @@ int main() { .client_policy = &kyber1024_test_policy, .server_policy = &security_policy_pq_tls_1_3_2023_06_01, + .expected_kem_group = &s2n_secp521r1_kyber_1024_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = false, @@ -425,6 +501,7 @@ int main() { .client_policy = &kyber768_test_policy, .server_policy = &security_policy_pq_tls_1_3_2023_06_01, + .expected_kem_group = &s2n_secp384r1_kyber_768_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = false, @@ -436,6 +513,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_1_2021_05_21, .server_policy = &security_policy_pq_tls_1_3_2023_06_01, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = !s2n_pq_is_enabled(), .len_prefix_expected = true, @@ -444,6 +522,7 @@ int main() { .client_policy = &kyber_test_policy_draft0, .server_policy = &kyber_test_policy_draft5, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -451,6 +530,7 @@ int main() { .client_policy = &kyber_test_policy_draft5, .server_policy = &kyber_test_policy_draft0, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = false, @@ -458,6 +538,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2021_05_24, .server_policy = &security_policy_pq_tls_1_0_2023_01_24, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -465,6 +546,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2023_01_24, .server_policy = &security_policy_pq_tls_1_0_2021_05_24, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = false, @@ -475,6 +557,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2020_12, .server_policy = &security_policy_pq_tls_1_0_2020_12, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -486,6 +569,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2020_12, .server_policy = &kyber_test_policy_draft0, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -497,6 +581,7 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2020_12, .server_policy = &kyber_test_policy_draft5, + .expected_kem_group = &s2n_x25519_kyber_512_r3, .expected_curve = NULL, .hrr_expected = false, .len_prefix_expected = true, @@ -507,7 +592,8 @@ int main() { .client_policy = &security_policy_pq_tls_1_0_2020_12, .server_policy = &security_policy_test_all_tls13, - .expected_curve = expected_curve, + .expected_kem_group = NULL, + .expected_curve = default_curve, .hrr_expected = false, .len_prefix_expected = true, }, @@ -517,7 +603,8 @@ int main() { .client_policy = &ecc_retry_policy, .server_policy = &security_policy_test_all_tls13, - .expected_curve = expected_curve, + .expected_kem_group = NULL, + .expected_curve = default_curve, .hrr_expected = true, .len_prefix_expected = true, }, @@ -527,7 +614,8 @@ int main() { .client_policy = &security_policy_test_all_tls13, .server_policy = &security_policy_pq_tls_1_0_2020_12, - .expected_curve = expected_curve, + .expected_kem_group = NULL, + .expected_curve = default_curve, .hrr_expected = false, .len_prefix_expected = true, }, @@ -537,7 +625,8 @@ int main() { .client_policy = &security_policy_test_tls13_retry, .server_policy = &security_policy_pq_tls_1_0_2020_12, - .expected_curve = expected_curve, + .expected_kem_group = NULL, + .expected_curve = default_curve, .hrr_expected = true, .len_prefix_expected = true, }, @@ -547,24 +636,50 @@ int main() const struct pq_handshake_test_vector *vector = &test_vectors[i]; const struct s2n_security_policy *client_policy = vector->client_policy; const struct s2n_security_policy *server_policy = vector->server_policy; + const struct s2n_kem_group *kem_group = vector->expected_kem_group; const struct s2n_ecc_named_curve *curve = vector->expected_curve; bool hrr_expected = vector->hrr_expected; bool len_prefix_expected = vector->len_prefix_expected; if (!s2n_pq_is_enabled()) { - /* If PQ is disabled, for older policies we expect to negotiate - * x25519 ECDH if available. Newer policies only include NIST - * curves, so if the server policy doesn't contain x25519, modify - * that expectation to a NIST curve. - */ - if (!s2n_ecc_preferences_includes_curve(server_policy->ecc_preferences, expected_curve->iana_id)) { + EXPECT_TRUE(client_policy->ecc_preferences->count > 0); + const struct s2n_ecc_named_curve *client_default = client_policy->ecc_preferences->ecc_curves[0]; + const struct s2n_ecc_named_curve *predicted_curve = s2n_get_predicted_negotiated_ecdhe_curve(client_policy, server_policy); + + /* If either policy doesn't support the default curve, fall back to p256 as it should + * be in common with every ECC preference list. */ + if (!s2n_ecc_preferences_includes_curve(client_policy->ecc_preferences, default_curve->iana_id) + || !s2n_ecc_preferences_includes_curve(server_policy->ecc_preferences, default_curve->iana_id)) { + EXPECT_TRUE(s2n_ecc_preferences_includes_curve(client_policy->ecc_preferences, s2n_ecc_curve_secp256r1.iana_id)); + EXPECT_TRUE(s2n_ecc_preferences_includes_curve(server_policy->ecc_preferences, s2n_ecc_curve_secp256r1.iana_id)); curve = &s2n_ecc_curve_secp256r1; - } else { - curve = expected_curve; } + + /* The client's preferred curve will be a higher priority than the default if both sides + * support TLS 1.3, and if the client's default can be chosen by the server in 1-RTT. */ + if (s2n_security_policy_supports_tls13(client_policy) && s2n_security_policy_supports_tls13(server_policy) + && s2n_ecc_preferences_includes_curve(server_policy->ecc_preferences, client_default->iana_id)) { + curve = client_default; + } + + /* Finally, confirm that the expected curve listed in the test vector matches the output of s2n_get_predicted_negotiated_ecdhe_curve() */ + EXPECT_EQUAL(curve->iana_id, predicted_curve->iana_id); + } + + if (!s2n_kem_group_is_available(kem_group)) { + kem_group = NULL; + } + + if (kem_group != NULL) { + const struct s2n_kem_group *predicted_kem_group = s2n_get_predicted_negotiated_kem_group(client_policy->kem_preferences, server_policy->kem_preferences); + POSIX_ENSURE_REF(predicted_kem_group); + + /* Confirm that the expected KEM Group listed in the test vector matches the output of + * s2n_get_predicted_negotiated_kem_group() */ + POSIX_ENSURE_EQ(kem_group->iana_id, predicted_kem_group->iana_id); } - EXPECT_SUCCESS(s2n_test_tls13_pq_handshake(client_policy, server_policy, curve, hrr_expected, len_prefix_expected)); + EXPECT_SUCCESS(s2n_test_tls13_pq_handshake(client_policy, server_policy, kem_group, curve, hrr_expected, len_prefix_expected)); } END_TEST(); diff --git a/tls/extensions/s2n_server_key_share.c b/tls/extensions/s2n_server_key_share.c index 08128c8df34..1e0ecf84b71 100644 --- a/tls/extensions/s2n_server_key_share.c +++ b/tls/extensions/s2n_server_key_share.c @@ -346,13 +346,13 @@ int s2n_extensions_server_key_share_select(struct s2n_connection *conn) { POSIX_ENSURE_REF(conn); - const struct s2n_ecc_preferences *ecc_pref = NULL; - POSIX_GUARD(s2n_connection_get_ecc_preferences(conn, &ecc_pref)); - POSIX_ENSURE_REF(ecc_pref); + /* Get the client's preferred groups for the KeyShares that were actually sent by the client */ + const struct s2n_ecc_named_curve *client_curve = conn->kex_params.client_ecc_evp_params.negotiated_curve; + const struct s2n_kem_group *client_kem_group = conn->kex_params.client_kem_group_params.kem_group; - const struct s2n_kem_preferences *kem_pref = NULL; - POSIX_GUARD(s2n_connection_get_kem_preferences(conn, &kem_pref)); - POSIX_ENSURE_REF(kem_pref); + /* Get the server's preferred groups (which may or may not have been sent in the KeyShare by the client) */ + const struct s2n_ecc_named_curve *server_curve = conn->kex_params.server_ecc_evp_params.negotiated_curve; + const struct s2n_kem_group *server_kem_group = conn->kex_params.server_kem_group_params.kem_group; /* Boolean XOR check. When receiving the supported_groups extension, s2n server * should (exclusively) set either server_curve or server_kem_group based on the @@ -361,37 +361,48 @@ int s2n_extensions_server_key_share_select(struct s2n_connection *conn) * groups; key negotiation is not possible and the handshake should be aborted * without sending HRR. (The case of both being non-NULL should never occur, and * is an error.) */ - const struct s2n_ecc_named_curve *server_curve = conn->kex_params.server_ecc_evp_params.negotiated_curve; - const struct s2n_kem_group *server_kem_group = conn->kex_params.server_kem_group_params.kem_group; POSIX_ENSURE((server_curve == NULL) != (server_kem_group == NULL), S2N_ERR_ECDHE_UNSUPPORTED_CURVE); /* To avoid extra round trips, we prefer to negotiate a group for which we have already * received a key share (even if it is different than the group previously chosen). In * general, we prefer to negotiate PQ over ECDHE; however, if both client and server * support PQ, but the client sent only EC key shares, then we will negotiate ECHDE. */ - if (conn->kex_params.client_kem_group_params.kem_group) { + + /* Option 1: Select the best mutually supported PQ Hybrid Group that can be negotiated in 1-RTT */ + if (client_kem_group != NULL) { POSIX_ENSURE_REF(conn->kex_params.client_kem_group_params.ecc_params.negotiated_curve); POSIX_ENSURE_REF(conn->kex_params.client_kem_group_params.kem_params.kem); conn->kex_params.server_kem_group_params.kem_group = conn->kex_params.client_kem_group_params.kem_group; conn->kex_params.server_kem_group_params.ecc_params.negotiated_curve = conn->kex_params.client_kem_group_params.ecc_params.negotiated_curve; conn->kex_params.server_kem_group_params.kem_params.kem = conn->kex_params.client_kem_group_params.kem_params.kem; + conn->kex_params.server_ecc_evp_params.negotiated_curve = NULL; + return S2N_SUCCESS; + } + /* Option 2: Otherwise, if any PQ Hybrid Groups can be negotiated in 2-RTT's select that one. This ensures that + * clients who offer PQ (and presumably therefore have concerns about quantum computing impacting the long term + * confidentiality of their data), have their choice to offer PQ respected, even if they predict the server-side + * supports a different PQ KeyShare algorithms. This ensures clients with PQ support are never downgraded to non-PQ + * algorithms. */ + if (server_kem_group != NULL) { + /* Null out any available ECC curves so that they won't be sent in the ClientHelloRetry */ conn->kex_params.server_ecc_evp_params.negotiated_curve = NULL; + POSIX_GUARD(s2n_set_hello_retry_required(conn)); return S2N_SUCCESS; } - if (conn->kex_params.client_ecc_evp_params.negotiated_curve) { + /* Option 3: Otherwise, if there is a mutually supported classical ECDHE-only group can be negotiated in 1-RTT, select that one */ + if (client_curve) { conn->kex_params.server_ecc_evp_params.negotiated_curve = conn->kex_params.client_ecc_evp_params.negotiated_curve; - conn->kex_params.server_kem_group_params.kem_group = NULL; conn->kex_params.server_kem_group_params.ecc_params.negotiated_curve = NULL; conn->kex_params.server_kem_group_params.kem_params.kem = NULL; return S2N_SUCCESS; } - /* Server and client have mutually supported groups, but the client did not send key - * shares for any of them. Send HRR indicating the server's preference. */ + /* Option 4: Server and client have at least 1 mutually supported group, but the client did not send key shares for + * any of them. Send a HelloRetryRequest indicating the server's preference. */ POSIX_GUARD(s2n_set_hello_retry_required(conn)); return S2N_SUCCESS; } diff --git a/tls/s2n_fingerprint.c b/tls/s2n_fingerprint.c index e55162cd1ac..ca077b2a206 100644 --- a/tls/s2n_fingerprint.c +++ b/tls/s2n_fingerprint.c @@ -13,23 +13,12 @@ * permissions and limitations under the License. */ -#include "api/unstable/fingerprint.h" -#include "crypto/s2n_fips.h" -#include "crypto/s2n_hash.h" -#include "stuffer/s2n_stuffer.h" -#include "tls/extensions/s2n_extension_list.h" -#include "tls/s2n_client_hello.h" -#include "tls/s2n_crypto_constants.h" +#include "tls/s2n_fingerprint.h" + #include "utils/s2n_blob.h" -#include "utils/s2n_result.h" +#include "utils/s2n_mem.h" #include "utils/s2n_safety.h" -#define S2N_JA3_FIELD_DIV ',' -#define S2N_JA3_LIST_DIV '-' - -/* UINT16_MAX == 65535 */ -#define S2N_UINT16_STR_MAX_SIZE 5 - /* See https://datatracker.ietf.org/doc/html/rfc8701 * for an explanation of GREASE and lists of the GREASE values. */ @@ -45,278 +34,117 @@ static S2N_RESULT s2n_assert_grease_value(uint16_t val) return S2N_RESULT_OK; } -static bool s2n_is_grease_value(uint16_t val) +bool s2n_is_grease_value(uint16_t val) { return s2n_result_is_ok(s2n_assert_grease_value(val)); } -static S2N_RESULT s2n_fingerprint_hash_flush(struct s2n_hash_state *hash, struct s2n_stuffer *in) -{ - if (hash == NULL) { - /* If the buffer is full and needs to be flushed, but no hash was provided, - * then we have insufficient memory to complete the fingerprint. - * - * The application will need to provide a larger buffer. - */ - RESULT_BAIL(S2N_ERR_INSUFFICIENT_MEM_SIZE); - } - - uint32_t hash_data_len = s2n_stuffer_data_available(in); - uint8_t *hash_data = s2n_stuffer_raw_read(in, hash_data_len); - RESULT_ENSURE_REF(hash_data); - RESULT_GUARD_POSIX(s2n_hash_update(hash, hash_data, hash_data_len)); - RESULT_GUARD_POSIX(s2n_stuffer_wipe(in)); - return S2N_RESULT_OK; -} - -static S2N_RESULT s2n_fingerprint_write_char(struct s2n_stuffer *stuffer, - char c, struct s2n_hash_state *hash) -{ - if (s2n_stuffer_space_remaining(stuffer) < 1) { - RESULT_GUARD(s2n_fingerprint_hash_flush(hash, stuffer)); - } - RESULT_GUARD_POSIX(s2n_stuffer_write_char(stuffer, c)); - return S2N_RESULT_OK; -} - -static S2N_RESULT s2n_fingerprint_write_entry(struct s2n_stuffer *stuffer, - bool *is_list, uint16_t value, struct s2n_hash_state *hash) -{ - /* If we have already written at least one value for this field, - * then we are writing a list and need to prepend a list divider before - * writing the next value. - */ - RESULT_ENSURE_REF(is_list); - if (*is_list) { - RESULT_GUARD(s2n_fingerprint_write_char(stuffer, S2N_JA3_LIST_DIV, hash)); - } - *is_list = true; - - /* snprintf always appends a '\0' to the output, - * but that extra '\0' is not included in the return value */ - uint8_t entry[S2N_UINT16_STR_MAX_SIZE + 1] = { 0 }; - int written = snprintf((char *) entry, sizeof(entry), "%u", value); - RESULT_ENSURE_GT(written, 0); - RESULT_ENSURE_LTE(written, S2N_UINT16_STR_MAX_SIZE); - - if (s2n_stuffer_space_remaining(stuffer) < (uint64_t) written) { - RESULT_GUARD(s2n_fingerprint_hash_flush(hash, stuffer)); - } - RESULT_GUARD_POSIX(s2n_stuffer_write_bytes(stuffer, entry, written)); - - return S2N_RESULT_OK; -} - -static S2N_RESULT s2n_fingerprint_write_version(struct s2n_client_hello *ch, - struct s2n_stuffer *output, struct s2n_hash_state *hash) -{ - RESULT_ENSURE_REF(ch); - bool is_list = false; - uint16_t version = 0; - struct s2n_stuffer message = { 0 }; - RESULT_GUARD_POSIX(s2n_stuffer_init_written(&message, &ch->raw_message)); - RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&message, &version)); - RESULT_GUARD(s2n_fingerprint_write_entry(output, &is_list, version, hash)); - return S2N_RESULT_OK; -} - -static S2N_RESULT s2n_fingerprint_write_ciphers(struct s2n_client_hello *ch, - struct s2n_stuffer *output, struct s2n_hash_state *hash) -{ - RESULT_ENSURE_REF(ch); - - bool cipher_found = false; - struct s2n_stuffer ciphers = { 0 }; - RESULT_GUARD_POSIX(s2n_stuffer_init_written(&ciphers, &ch->cipher_suites)); - while (s2n_stuffer_data_available(&ciphers)) { - uint16_t cipher = 0; - RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&ciphers, &cipher)); - if (s2n_is_grease_value(cipher)) { - continue; - } - RESULT_GUARD(s2n_fingerprint_write_entry(output, &cipher_found, cipher, hash)); - } - return S2N_RESULT_OK; -} - -static S2N_RESULT s2n_fingerprint_write_extensions(struct s2n_client_hello *ch, - struct s2n_stuffer *output, struct s2n_hash_state *hash) +S2N_RESULT s2n_fingerprint_hash_add_char(struct s2n_fingerprint_hash *hash, char c) { - RESULT_ENSURE_REF(ch); - - /* We have to use the raw extensions instead of the parsed extensions - * because s2n-tls both intentionally ignores any unknown extensions - * and reorders the extensions when parsing the list. - */ - struct s2n_stuffer extensions = { 0 }; - RESULT_GUARD_POSIX(s2n_stuffer_init_written(&extensions, &ch->extensions.raw)); - - bool extension_found = false; - while (s2n_stuffer_data_available(&extensions)) { - uint16_t extension = 0, extension_size = 0; - RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&extensions, &extension)); - RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&extensions, &extension_size)); - RESULT_GUARD_POSIX(s2n_stuffer_skip_read(&extensions, extension_size)); - if (s2n_is_grease_value(extension)) { - continue; - } - RESULT_GUARD(s2n_fingerprint_write_entry(output, &extension_found, extension, hash)); + RESULT_ENSURE_REF(hash); + if (hash->hash) { + RESULT_GUARD_POSIX(s2n_hash_update(hash->hash, &c, 1)); + } else { + RESULT_ENSURE_REF(hash->buffer); + RESULT_ENSURE(s2n_stuffer_space_remaining(hash->buffer) >= 1, + S2N_ERR_INSUFFICIENT_MEM_SIZE); + RESULT_GUARD_POSIX(s2n_stuffer_write_char(hash->buffer, c)); } return S2N_RESULT_OK; } -static S2N_RESULT s2n_fingerprint_write_elliptic_curves(struct s2n_client_hello *ch, - struct s2n_stuffer *output, struct s2n_hash_state *hash) +S2N_RESULT s2n_fingerprint_hash_add_str(struct s2n_fingerprint_hash *hash, + const char *str, size_t str_size) { - RESULT_ENSURE_REF(ch); - - s2n_parsed_extension *elliptic_curves_extension = NULL; - int result = s2n_client_hello_get_parsed_extension(S2N_EXTENSION_SUPPORTED_GROUPS, - &ch->extensions, &elliptic_curves_extension); - if (result != S2N_SUCCESS) { - return S2N_RESULT_OK; - } - - struct s2n_stuffer elliptic_curves = { 0 }; - RESULT_GUARD_POSIX(s2n_stuffer_init_written(&elliptic_curves, - &elliptic_curves_extension->extension)); - - uint16_t count = 0; - RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&elliptic_curves, &count)); - - bool curve_found = false; - while (s2n_stuffer_data_available(&elliptic_curves)) { - uint16_t curve = 0; - RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&elliptic_curves, &curve)); - if (s2n_is_grease_value(curve)) { - continue; - } - RESULT_GUARD(s2n_fingerprint_write_entry(output, &curve_found, curve, hash)); + RESULT_ENSURE_REF(hash); + RESULT_ENSURE(S2N_MEM_IS_READABLE(str, str_size), S2N_ERR_NULL); + if (hash->hash) { + RESULT_GUARD_POSIX(s2n_hash_update(hash->hash, str, str_size)); + } else { + RESULT_ENSURE_REF(hash->buffer); + RESULT_ENSURE(s2n_stuffer_space_remaining(hash->buffer) >= str_size, + S2N_ERR_INSUFFICIENT_MEM_SIZE); + RESULT_GUARD_POSIX(s2n_stuffer_write_text(hash->buffer, str, str_size)); } return S2N_RESULT_OK; } -static S2N_RESULT s2n_fingerprint_write_point_formats(struct s2n_client_hello *ch, - struct s2n_stuffer *output, struct s2n_hash_state *hash) +S2N_RESULT s2n_fingerprint_hash_digest(struct s2n_fingerprint_hash *hash, uint8_t *out, size_t out_size) { - RESULT_ENSURE_REF(ch); - - s2n_parsed_extension *point_formats_extension = NULL; - int result = s2n_client_hello_get_parsed_extension(S2N_EXTENSION_EC_POINT_FORMATS, - &ch->extensions, &point_formats_extension); - if (result != S2N_SUCCESS) { - return S2N_RESULT_OK; - } + RESULT_ENSURE_REF(hash); + RESULT_ENSURE_REF(hash->hash); - struct s2n_stuffer point_formats = { 0 }; - RESULT_GUARD_POSIX(s2n_stuffer_init_written(&point_formats, - &point_formats_extension->extension)); + uint64_t bytes = 0; + RESULT_GUARD_POSIX(s2n_hash_get_currently_in_hash_total(hash->hash, &bytes)); + hash->bytes_digested += bytes; - uint8_t count = 0; - RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(&point_formats, &count)); - - bool format_found = false; - while (s2n_stuffer_data_available(&point_formats)) { - uint8_t format = 0; - RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(&point_formats, &format)); - RESULT_GUARD(s2n_fingerprint_write_entry(output, &format_found, format, hash)); - } + RESULT_GUARD_POSIX(s2n_hash_digest(hash->hash, out, out_size)); + RESULT_GUARD_POSIX(s2n_hash_reset(hash->hash)); return S2N_RESULT_OK; } -/* JA3 involves concatenating a set of fields from the ClientHello: - * SSLVersion,Cipher,SSLExtension,EllipticCurve,EllipticCurvePointFormat - * For example: - * "769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0" - * See https://github.com/salesforce/ja3 - */ -static S2N_RESULT s2n_fingerprint_ja3(struct s2n_client_hello *ch, - struct s2n_stuffer *output, uint32_t *output_size, struct s2n_hash_state *hash) +bool s2n_fingerprint_hash_do_digest(struct s2n_fingerprint_hash *hash) { - RESULT_ENSURE_REF(ch); - RESULT_ENSURE(!ch->sslv2, S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED); - - RESULT_GUARD(s2n_fingerprint_write_version(ch, output, hash)); - RESULT_GUARD(s2n_fingerprint_write_char(output, S2N_JA3_FIELD_DIV, hash)); - RESULT_GUARD(s2n_fingerprint_write_ciphers(ch, output, hash)); - RESULT_GUARD(s2n_fingerprint_write_char(output, S2N_JA3_FIELD_DIV, hash)); - RESULT_GUARD(s2n_fingerprint_write_extensions(ch, output, hash)); - RESULT_GUARD(s2n_fingerprint_write_char(output, S2N_JA3_FIELD_DIV, hash)); - RESULT_GUARD(s2n_fingerprint_write_elliptic_curves(ch, output, hash)); - RESULT_GUARD(s2n_fingerprint_write_char(output, S2N_JA3_FIELD_DIV, hash)); - RESULT_GUARD(s2n_fingerprint_write_point_formats(ch, output, hash)); - - return S2N_RESULT_OK; + return hash && hash->hash; } int s2n_client_hello_get_fingerprint_hash(struct s2n_client_hello *ch, s2n_fingerprint_type type, - uint32_t max_hash_size, uint8_t *hash, uint32_t *hash_size, uint32_t *str_size) + uint32_t max_output_size, uint8_t *output, uint32_t *output_size, uint32_t *str_size) { POSIX_ENSURE(type == S2N_FINGERPRINT_JA3, S2N_ERR_INVALID_ARGUMENT); - POSIX_ENSURE(max_hash_size >= MD5_DIGEST_LENGTH, S2N_ERR_INSUFFICIENT_MEM_SIZE); - POSIX_ENSURE_REF(hash); - POSIX_ENSURE_REF(hash_size); + const struct s2n_fingerprint_method *method = &ja3_fingerprint; + + uint8_t hash_size = 0; + POSIX_GUARD(s2n_hash_digest_size(method->hash, &hash_size)); + POSIX_ENSURE(max_output_size >= hash_size, S2N_ERR_INSUFFICIENT_MEM_SIZE); + + POSIX_ENSURE_REF(ch); + POSIX_ENSURE(!ch->sslv2, S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED); + POSIX_ENSURE_REF(output); + POSIX_ENSURE_REF(output_size); POSIX_ENSURE_REF(str_size); - *hash_size = 0; + *output_size = 0; *str_size = 0; - /* The maximum size of the JA3 string is variable and could theoretically - * be extremely large. However, we don't need enough memory to hold the full - * string when calculating a hash. We can calculate and add the JA3 string - * to the hash in chunks, similarly to how the TLS transcript hash is - * calculated by adding handshake messages to the hash as they become - * available. After a chunk is added to the hash, the string buffer can be - * wiped and reused for the next chunk. - * - * The size of this buffer was chosen fairly arbitrarily. - */ - uint8_t string_mem[50] = { 0 }; - struct s2n_blob string_blob = { 0 }; - struct s2n_stuffer string_stuffer = { 0 }; - POSIX_GUARD(s2n_blob_init(&string_blob, string_mem, sizeof(string_mem))); - POSIX_GUARD(s2n_stuffer_init(&string_stuffer, &string_blob)); - - /* JA3 uses an MD5 hash. - * The hash doesn't have to be cryptographically secure, - * so the weakness of MD5 shouldn't be a problem. - */ - DEFER_CLEANUP(struct s2n_hash_state md5_hash = { 0 }, s2n_hash_free); - POSIX_GUARD(s2n_hash_new(&md5_hash)); - if (s2n_is_in_fips_mode()) { - /* This hash is unrelated to TLS and does not affect FIPS */ - POSIX_GUARD(s2n_hash_allow_md5_for_fips(&md5_hash)); - } - POSIX_GUARD(s2n_hash_init(&md5_hash, S2N_HASH_MD5)); + struct s2n_stuffer output_stuffer = { 0 }; + POSIX_GUARD(s2n_blob_init(&output_stuffer.blob, output, max_output_size)); - POSIX_GUARD_RESULT(s2n_fingerprint_ja3(ch, &string_stuffer, hash_size, &md5_hash)); - POSIX_GUARD_RESULT(s2n_fingerprint_hash_flush(&md5_hash, &string_stuffer)); + DEFER_CLEANUP(struct s2n_hash_state hash_state = { 0 }, s2n_hash_free); + POSIX_GUARD(s2n_hash_new(&hash_state)); + s2n_hash_allow_md5_for_fips(&hash_state); + POSIX_GUARD(s2n_hash_init(&hash_state, method->hash)); - uint64_t in_hash = 0; - POSIX_GUARD(s2n_hash_get_currently_in_hash_total(&md5_hash, &in_hash)); - POSIX_ENSURE_LTE(in_hash, UINT32_MAX); - *str_size = in_hash; + struct s2n_fingerprint_hash hash = { + .hash = &hash_state, + }; - POSIX_GUARD(s2n_hash_digest(&md5_hash, hash, MD5_DIGEST_LENGTH)); - *hash_size = MD5_DIGEST_LENGTH; + POSIX_GUARD_RESULT(method->fingerprint(ch, &hash, &output_stuffer)); + *output_size = s2n_stuffer_data_available(&output_stuffer); + *str_size = hash.bytes_digested; return S2N_SUCCESS; } int s2n_client_hello_get_fingerprint_string(struct s2n_client_hello *ch, s2n_fingerprint_type type, - uint32_t max_size, uint8_t *output, uint32_t *output_size) + uint32_t max_output_size, uint8_t *output, uint32_t *output_size) { POSIX_ENSURE(type == S2N_FINGERPRINT_JA3, S2N_ERR_INVALID_ARGUMENT); - POSIX_ENSURE(max_size > 0, S2N_ERR_INSUFFICIENT_MEM_SIZE); + const struct s2n_fingerprint_method *method = &ja3_fingerprint; + POSIX_ENSURE(max_output_size > 0, S2N_ERR_INSUFFICIENT_MEM_SIZE); + + POSIX_ENSURE_REF(ch); + POSIX_ENSURE(!ch->sslv2, S2N_ERR_PROTOCOL_VERSION_UNSUPPORTED); POSIX_ENSURE_REF(output); POSIX_ENSURE_REF(output_size); *output_size = 0; - struct s2n_blob output_blob = { 0 }; struct s2n_stuffer output_stuffer = { 0 }; - POSIX_GUARD(s2n_blob_init(&output_blob, output, max_size)); - POSIX_GUARD(s2n_stuffer_init(&output_stuffer, &output_blob)); + POSIX_GUARD(s2n_blob_init(&output_stuffer.blob, output, max_output_size)); - POSIX_GUARD_RESULT(s2n_fingerprint_ja3(ch, &output_stuffer, output_size, NULL)); - *output_size = s2n_stuffer_data_available(&output_stuffer); + struct s2n_fingerprint_hash hash = { + .buffer = &output_stuffer, + }; + POSIX_GUARD_RESULT(method->fingerprint(ch, &hash, &output_stuffer)); + *output_size = s2n_stuffer_data_available(&output_stuffer); return S2N_SUCCESS; } diff --git a/tls/s2n_fingerprint.h b/tls/s2n_fingerprint.h new file mode 100644 index 00000000000..d0db1ac9550 --- /dev/null +++ b/tls/s2n_fingerprint.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#pragma once + +#include "api/s2n.h" +#include "api/unstable/fingerprint.h" +#include "crypto/s2n_hash.h" +#include "stuffer/s2n_stuffer.h" +#include "tls/s2n_client_hello.h" +#include "utils/s2n_result.h" + +struct s2n_fingerprint_hash { + uint32_t bytes_digested; + struct s2n_stuffer *buffer; + struct s2n_hash_state *hash; +}; +S2N_RESULT s2n_fingerprint_hash_add_char(struct s2n_fingerprint_hash *hash, char c); +S2N_RESULT s2n_fingerprint_hash_add_str(struct s2n_fingerprint_hash *hash, const char *str, size_t str_size); +S2N_RESULT s2n_fingerprint_hash_digest(struct s2n_fingerprint_hash *hash, uint8_t *out, size_t out_size); +bool s2n_fingerprint_hash_do_digest(struct s2n_fingerprint_hash *hash); + +struct s2n_fingerprint_method { + s2n_hash_algorithm hash; + S2N_RESULT (*fingerprint)(struct s2n_client_hello *ch, + struct s2n_fingerprint_hash *hash, struct s2n_stuffer *output); +}; +extern struct s2n_fingerprint_method ja3_fingerprint; + +bool s2n_is_grease_value(uint16_t val); diff --git a/tls/s2n_fingerprint_ja3.c b/tls/s2n_fingerprint_ja3.c new file mode 100644 index 00000000000..c05ee8e5a00 --- /dev/null +++ b/tls/s2n_fingerprint_ja3.c @@ -0,0 +1,208 @@ +/* + * 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 "tls/extensions/s2n_extension_list.h" +#include "tls/s2n_fingerprint.h" +#include "utils/s2n_blob.h" +#include "utils/s2n_safety.h" + +#define S2N_JA3_FIELD_DIV ',' +#define S2N_JA3_LIST_DIV '-' + +/* UINT16_MAX == 65535 */ +#define S2N_UINT16_STR_MAX_SIZE 5 + +static S2N_RESULT s2n_fingerprint_ja3_digest(struct s2n_fingerprint_hash *hash, + struct s2n_stuffer *out) +{ + if (!s2n_fingerprint_hash_do_digest(hash)) { + return S2N_RESULT_OK; + } + + uint8_t *digest = s2n_stuffer_raw_write(out, MD5_DIGEST_LENGTH); + RESULT_GUARD_PTR(digest); + RESULT_GUARD(s2n_fingerprint_hash_digest(hash, digest, MD5_DIGEST_LENGTH)); + + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_fingerprint_ja3_iana(struct s2n_fingerprint_hash *hash, + bool *is_list, uint16_t iana) +{ + if (s2n_is_grease_value(iana)) { + return S2N_RESULT_OK; + } + + /* If we have already written at least one value for this field, + * then we are writing a list and need to prepend a list divider before + * writing the next value. + */ + if (*is_list) { + RESULT_GUARD(s2n_fingerprint_hash_add_char(hash, S2N_JA3_LIST_DIV)); + } else { + *is_list = true; + } + + /* snprintf always appends a '\0' to the output, + * but that extra '\0' is not included in the return value */ + char str[S2N_UINT16_STR_MAX_SIZE + 1] = { 0 }; + int written = snprintf(str, sizeof(str), "%u", iana); + RESULT_ENSURE_GT(written, 0); + RESULT_ENSURE_LTE(written, S2N_UINT16_STR_MAX_SIZE); + + RESULT_GUARD(s2n_fingerprint_hash_add_str(hash, str, written)); + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_fingerprint_ja3_version(struct s2n_fingerprint_hash *hash, + struct s2n_client_hello *ch) +{ + struct s2n_stuffer message = { 0 }; + RESULT_ENSURE_REF(ch); + RESULT_GUARD_POSIX(s2n_stuffer_init_written(&message, &ch->raw_message)); + + uint16_t version = 0; + RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&message, &version)); + + bool is_list = false; + RESULT_GUARD(s2n_fingerprint_ja3_iana(hash, &is_list, version)); + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_fingerprint_ja3_cipher_suites(struct s2n_fingerprint_hash *hash, + struct s2n_client_hello *ch) +{ + RESULT_ENSURE_REF(ch); + + struct s2n_stuffer ciphers = { 0 }; + RESULT_GUARD_POSIX(s2n_stuffer_init_written(&ciphers, &ch->cipher_suites)); + + bool found = false; + while (s2n_stuffer_data_available(&ciphers)) { + uint16_t iana = 0; + RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&ciphers, &iana)); + RESULT_GUARD(s2n_fingerprint_ja3_iana(hash, &found, iana)); + } + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_fingerprint_ja3_extensions(struct s2n_fingerprint_hash *hash, + struct s2n_client_hello *ch) +{ + RESULT_ENSURE_REF(ch); + + /* We have to use the raw extensions instead of the parsed extensions + * because s2n-tls both intentionally ignores any unknown extensions + * and reorders the extensions when parsing the list. + */ + struct s2n_stuffer extensions = { 0 }; + RESULT_GUARD_POSIX(s2n_stuffer_init_written(&extensions, &ch->extensions.raw)); + + bool found = false; + while (s2n_stuffer_data_available(&extensions)) { + uint16_t iana = 0, size = 0; + RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&extensions, &iana)); + RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&extensions, &size)); + RESULT_GUARD_POSIX(s2n_stuffer_skip_read(&extensions, size)); + RESULT_GUARD(s2n_fingerprint_ja3_iana(hash, &found, iana)); + } + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_fingerprint_ja3_elliptic_curves(struct s2n_fingerprint_hash *hash, + struct s2n_client_hello *ch) +{ + RESULT_ENSURE_REF(ch); + + s2n_parsed_extension *extension = NULL; + int result = s2n_client_hello_get_parsed_extension(S2N_EXTENSION_SUPPORTED_GROUPS, + &ch->extensions, &extension); + if (result != S2N_SUCCESS) { + return S2N_RESULT_OK; + } + + struct s2n_stuffer elliptic_curves = { 0 }; + RESULT_GUARD_POSIX(s2n_stuffer_init_written(&elliptic_curves, &extension->extension)); + RESULT_GUARD_POSIX(s2n_stuffer_skip_read(&elliptic_curves, sizeof(uint16_t))); + + bool found = false; + while (s2n_stuffer_data_available(&elliptic_curves)) { + uint16_t iana = 0; + RESULT_GUARD_POSIX(s2n_stuffer_read_uint16(&elliptic_curves, &iana)); + RESULT_GUARD(s2n_fingerprint_ja3_iana(hash, &found, iana)); + } + return S2N_RESULT_OK; +} + +static S2N_RESULT s2n_fingerprint_ja3_point_formats(struct s2n_fingerprint_hash *hash, + struct s2n_client_hello *ch) +{ + RESULT_ENSURE_REF(ch); + + s2n_parsed_extension *extension = NULL; + int result = s2n_client_hello_get_parsed_extension(S2N_EXTENSION_EC_POINT_FORMATS, + &ch->extensions, &extension); + if (result != S2N_SUCCESS) { + return S2N_RESULT_OK; + } + + struct s2n_stuffer point_formats = { 0 }; + RESULT_GUARD_POSIX(s2n_stuffer_init_written(&point_formats, &extension->extension)); + RESULT_GUARD_POSIX(s2n_stuffer_skip_read(&point_formats, sizeof(uint8_t))); + + bool found = false; + while (s2n_stuffer_data_available(&point_formats)) { + uint8_t iana = 0; + RESULT_GUARD_POSIX(s2n_stuffer_read_uint8(&point_formats, &iana)); + RESULT_GUARD(s2n_fingerprint_ja3_iana(hash, &found, iana)); + } + return S2N_RESULT_OK; +} + +/* JA3 involves concatenating a set of fields from the ClientHello: + * SSLVersion,Cipher,SSLExtension,EllipticCurve,EllipticCurvePointFormat + * For example: + * "769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0" + * See https://github.com/salesforce/ja3 + */ +static S2N_RESULT s2n_fingerprint_ja3_write(struct s2n_fingerprint_hash *hash, + struct s2n_client_hello *ch) +{ + RESULT_GUARD(s2n_fingerprint_ja3_version(hash, ch)); + RESULT_GUARD(s2n_fingerprint_hash_add_char(hash, S2N_JA3_FIELD_DIV)); + RESULT_GUARD(s2n_fingerprint_ja3_cipher_suites(hash, ch)); + RESULT_GUARD(s2n_fingerprint_hash_add_char(hash, S2N_JA3_FIELD_DIV)); + RESULT_GUARD(s2n_fingerprint_ja3_extensions(hash, ch)); + RESULT_GUARD(s2n_fingerprint_hash_add_char(hash, S2N_JA3_FIELD_DIV)); + RESULT_GUARD(s2n_fingerprint_ja3_elliptic_curves(hash, ch)); + RESULT_GUARD(s2n_fingerprint_hash_add_char(hash, S2N_JA3_FIELD_DIV)); + RESULT_GUARD(s2n_fingerprint_ja3_point_formats(hash, ch)); + return S2N_RESULT_OK; +} + +S2N_RESULT s2n_fingerprint_ja3(struct s2n_client_hello *client_hello, + struct s2n_fingerprint_hash *hash, struct s2n_stuffer *output) +{ + RESULT_GUARD(s2n_fingerprint_ja3_write(hash, client_hello)); + RESULT_GUARD(s2n_fingerprint_ja3_digest(hash, output)); + return S2N_RESULT_OK; +} + +struct s2n_fingerprint_method ja3_fingerprint = { + /* The hash doesn't have to be cryptographically secure, + * so the weakness of MD5 shouldn't be a problem. */ + .hash = S2N_HASH_MD5, + .fingerprint = s2n_fingerprint_ja3, +}; diff --git a/tls/s2n_server_hello_retry.c b/tls/s2n_server_hello_retry.c index fd05ca07236..702c4362c8d 100644 --- a/tls/s2n_server_hello_retry.c +++ b/tls/s2n_server_hello_retry.c @@ -69,10 +69,11 @@ int s2n_server_hello_retry_recv(struct s2n_connection *conn) POSIX_ENSURE_REF(kem_pref); const struct s2n_ecc_named_curve *named_curve = conn->kex_params.server_ecc_evp_params.negotiated_curve; - const struct s2n_kem_group *kem_group = conn->kex_params.server_kem_group_params.kem_group; + const struct s2n_kem_group *server_preferred_kem_group = conn->kex_params.server_kem_group_params.kem_group; + const struct s2n_kem_group *client_preferred_kem_group = conn->kex_params.client_kem_group_params.kem_group; /* Boolean XOR check: exactly one of {named_curve, kem_group} should be non-null. */ - POSIX_ENSURE((named_curve != NULL) != (kem_group != NULL), S2N_ERR_INVALID_HELLO_RETRY); + POSIX_ENSURE((named_curve != NULL) != (server_preferred_kem_group != NULL), S2N_ERR_INVALID_HELLO_RETRY); /** *= https://www.rfc-editor.org/rfc/rfc8446#4.2.8 @@ -85,7 +86,9 @@ int s2n_server_hello_retry_recv(struct s2n_connection *conn) if (named_curve != NULL && s2n_ecc_preferences_includes_curve(ecc_pref, named_curve->iana_id)) { selected_group_in_supported_groups = true; } - if (kem_group != NULL && s2n_kem_group_is_available(kem_group) && s2n_kem_preferences_includes_tls13_kem_group(kem_pref, kem_group->iana_id)) { + if (server_preferred_kem_group != NULL + && s2n_kem_group_is_available(server_preferred_kem_group) + && s2n_kem_preferences_includes_tls13_kem_group(kem_pref, server_preferred_kem_group->iana_id)) { selected_group_in_supported_groups = true; } @@ -96,14 +99,16 @@ int s2n_server_hello_retry_recv(struct s2n_connection *conn) *# in the original ClientHello. **/ bool new_key_share_requested = false; + if (named_curve != NULL) { new_key_share_requested = (named_curve != conn->kex_params.client_ecc_evp_params.negotiated_curve); } - if (kem_group != NULL) { + + if (server_preferred_kem_group != NULL) { /* If PQ is disabled, the client should not have sent any PQ IDs * in the supported_groups list of the initial ClientHello */ POSIX_ENSURE(s2n_pq_is_enabled(), S2N_ERR_NO_SUPPORTED_LIBCRYPTO_API); - new_key_share_requested = (kem_group != conn->kex_params.client_kem_group_params.kem_group); + new_key_share_requested = (server_preferred_kem_group != client_preferred_kem_group); } /**