diff --git a/CMakeLists.txt b/CMakeLists.txt index 84387a1ea2f..cbce0740781 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -584,7 +584,7 @@ if (BUILD_TESTING) pytest -x -n=${N} --reruns=2 --durations=10 --cache-clear -rpfsq -o log_cli=true --log-cli-level=DEBUG --provider-version=$ENV{S2N_LIBCRYPTO} - --provider-criterion=off --fips-mode=0 --no-pq=0 ${test_file_path} + --provider-criterion=off --fips-mode=0 ${test_file_path} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/tests/integrationv2 ) else() diff --git a/bindings/rust/s2n-tls-sys/templates/Cargo.template b/bindings/rust/s2n-tls-sys/templates/Cargo.template index 9ff9295d7a2..22288cf2969 100644 --- a/bindings/rust/s2n-tls-sys/templates/Cargo.template +++ b/bindings/rust/s2n-tls-sys/templates/Cargo.template @@ -1,7 +1,7 @@ [package] name = "s2n-tls-sys" description = "A C99 implementation of the TLS/SSL protocols" -version = "0.2.7" +version = "0.2.8" authors = ["AWS s2n"] edition = "2021" rust-version = "1.63.0" diff --git a/bindings/rust/s2n-tls-tokio/Cargo.toml b/bindings/rust/s2n-tls-tokio/Cargo.toml index d97a2021f5c..a12fa8d5d5a 100644 --- a/bindings/rust/s2n-tls-tokio/Cargo.toml +++ b/bindings/rust/s2n-tls-tokio/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "s2n-tls-tokio" description = "An implementation of TLS streams for Tokio built on top of s2n-tls" -version = "0.2.7" +version = "0.2.8" authors = ["AWS s2n"] edition = "2021" rust-version = "1.63.0" @@ -15,7 +15,7 @@ default = [] errno = { version = "0.3" } libc = { version = "0.2" } pin-project-lite = { version = "0.2" } -s2n-tls = { version = "=0.2.7", path = "../s2n-tls" } +s2n-tls = { version = "=0.2.8", path = "../s2n-tls" } tokio = { version = "1", features = ["net", "time"] } [dev-dependencies] diff --git a/bindings/rust/s2n-tls/Cargo.toml b/bindings/rust/s2n-tls/Cargo.toml index c31579f7bea..2fac580b995 100644 --- a/bindings/rust/s2n-tls/Cargo.toml +++ b/bindings/rust/s2n-tls/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "s2n-tls" description = "A C99 implementation of the TLS/SSL protocols" -version = "0.2.7" +version = "0.2.8" authors = ["AWS s2n"] edition = "2021" rust-version = "1.63.0" @@ -19,7 +19,7 @@ pq = ["s2n-tls-sys/pq"] [dependencies] errno = { version = "0.3" } libc = "0.2" -s2n-tls-sys = { version = "=0.2.7", path = "../s2n-tls-sys", features = ["internal"] } +s2n-tls-sys = { version = "=0.2.8", path = "../s2n-tls-sys", features = ["internal"] } pin-project-lite = "0.2" hex = "0.4" diff --git a/bindings/rust/s2n-tls/src/connection.rs b/bindings/rust/s2n-tls/src/connection.rs index 56bab8f9c88..d2e3fda5588 100644 --- a/bindings/rust/s2n-tls/src/connection.rs +++ b/bindings/rust/s2n-tls/src/connection.rs @@ -857,6 +857,7 @@ impl Connection { Ok(()) } + /// Access the protocol version selected for the connection. pub fn actual_protocol_version(&self) -> Result { let version = unsafe { s2n_connection_get_actual_protocol_version(self.connection.as_ptr()).into_result()? @@ -864,6 +865,20 @@ impl Connection { version.try_into() } + /// Detects if the client hello is using the SSLv2 format. + /// + /// s2n-tls will not negotiate SSLv2, but will accept SSLv2 ClientHellos + /// advertising a higher protocol version like SSLv3 or TLS1.0. + /// [Connection::actual_protocol_version()] can be used to retrieve the + /// protocol version that is actually used on the connection. + pub fn client_hello_is_sslv2(&self) -> Result { + let version = unsafe { + s2n_connection_get_client_hello_version(self.connection.as_ptr()).into_result()? + }; + let version: Version = version.try_into()?; + Ok(version == Version::SSLV2) + } + pub fn handshake_type(&self) -> Result<&str, Error> { let handshake = unsafe { s2n_connection_get_handshake_type_name(self.connection.as_ptr()).into_result()? diff --git a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs index 7eac8ac2ef9..ed4ad974bdd 100644 --- a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs +++ b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs @@ -232,7 +232,7 @@ mod tests { callbacks::{ClientHelloCallback, ConnectionFuture, ConnectionFutureResult}, enums::ClientAuthType, error::ErrorType, - testing::{client_hello::*, *}, + testing::{self, client_hello::*, s2n_tls::*, *}, }; use alloc::sync::Arc; use core::sync::atomic::Ordering; @@ -890,4 +890,59 @@ mod tests { assert_eq!(protocol, b"h2"); Ok(()) } + + #[test] + fn client_hello_sslv2_negative() -> Result<(), testing::Error> { + let config = testing::build_config(&security::DEFAULT_TLS13)?; + let mut pair = TestPair::from_config(&config); + pair.handshake()?; + assert!(!pair.server.client_hello_is_sslv2()?); + Ok(()) + } + + #[test] + fn client_hello_sslv2_positive() -> Result<(), testing::Error> { + // copy-pasted from s2n-tls/tests/testlib/s2n_sslv2_client_hello.h + // by concatenating these fields together, a valid SSLv2 formatted client hello + // can be assembled + const SSLV2_CLIENT_HELLO_HEADER: &[u8] = &[0x80, 0xb3, 0x01, 0x03, 0x03]; + const SSLV2_CLIENT_HELLO_PREFIX: &[u8] = &[0x00, 0x8a, 0x00, 0x00, 0x00, 0x20]; + const SSLV2_CLIENT_HELLO_CIPHER_SUITES: &[u8] = &[ + 0x00, 0xc0, 0x24, 0x00, 0xc0, 0x28, 0x00, 0x00, 0x3d, 0x00, 0xc0, 0x26, 0x00, 0xc0, + 0x2a, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x6a, 0x00, 0xc0, 0x0a, 0x07, 0x00, 0xc0, 0x00, + 0xc0, 0x14, 0x00, 0x00, 0x35, 0x00, 0xc0, 0x05, 0x00, 0xc0, 0x0f, 0x00, 0x00, 0x39, + 0x00, 0x00, 0x38, 0x00, 0xc0, 0x23, 0x00, 0xc0, 0x27, 0x00, 0x00, 0x3c, 0x00, 0xc0, + 0x25, 0x00, 0xc0, 0x29, 0x00, 0x00, 0x67, 0x00, 0x00, 0x40, 0x00, 0xc0, 0x09, 0x06, + 0x00, 0x40, 0x00, 0xc0, 0x13, 0x00, 0x00, 0x2f, 0x00, 0xc0, 0x04, 0x01, 0x00, 0x80, + 0x00, 0xc0, 0x0e, 0x00, 0x00, 0x33, 0x00, 0x00, 0x32, 0x00, 0xc0, 0x2c, 0x00, 0xc0, + 0x2b, 0x00, 0xc0, 0x30, 0x00, 0x00, 0x9d, 0x00, 0xc0, 0x2e, 0x00, 0xc0, 0x32, 0x00, + 0x00, 0x9f, 0x00, 0x00, 0xa3, 0x00, 0xc0, 0x2f, 0x00, 0x00, 0x9c, 0x00, 0xc0, 0x2d, + 0x00, 0xc0, 0x31, 0x00, 0x00, 0x9e, 0x00, 0x00, 0xa2, 0x00, 0x00, 0xff, + ]; + const SSLV2_CLIENT_HELLO_CHALLENGE: &[u8] = &[ + 0x5b, 0xe9, 0xcc, 0xad, 0xd6, 0xa5, 0x20, 0xac, 0xa3, 0xf4, 0x8e, 0x88, 0x06, 0xb5, + 0x95, 0x53, 0x2d, 0x53, 0xfe, 0xd7, 0xa1, 0x00, 0x57, 0xc0, 0x53, 0x9d, 0x84, 0x71, + 0x80, 0x7f, 0x30, 0x7e, + ]; + + let config = testing::build_config(&security::Policy::from_version("test_all")?)?; + // we use the pair to setup IO, but we don't want the client to write anything. + // So we drop the client and just directly write the SSLv2 header to the + // client_tx_stream + let mut pair = TestPair::from_config(&config); + drop(pair.client); + + let mut client_tx_stream = pair.client_tx_stream.borrow_mut(); + client_tx_stream.write_all(SSLV2_CLIENT_HELLO_HEADER)?; + client_tx_stream.write_all(SSLV2_CLIENT_HELLO_PREFIX)?; + client_tx_stream.write_all(SSLV2_CLIENT_HELLO_CIPHER_SUITES)?; + client_tx_stream.write_all(SSLV2_CLIENT_HELLO_CHALLENGE)?; + // end the exclusive borrow + drop(client_tx_stream); + + // the first server.poll_negotiate causes the server to read in the client hello + assert!(pair.server.poll_negotiate()?.is_pending()); + assert!(pair.server.client_hello_is_sslv2()?); + Ok(()) + } } diff --git a/codebuild/bin/criterion_baseline.sh b/codebuild/bin/criterion_baseline.sh index c7cf4a3cc01..fa644b0a80a 100755 --- a/codebuild/bin/criterion_baseline.sh +++ b/codebuild/bin/criterion_baseline.sh @@ -16,8 +16,6 @@ set -eu source codebuild/bin/s2n_setup_env.sh source codebuild/bin/utils.sh -# Disable PQ -export S2N_NO_PQ=1 # Limit the number of child processes in the test run export RUST_BACKTRACE=1 export TOX_TEST_NAME="$INTEGV2_TEST" diff --git a/codebuild/bin/criterion_delta.sh b/codebuild/bin/criterion_delta.sh index 1089078ca78..b2a28fecec7 100755 --- a/codebuild/bin/criterion_delta.sh +++ b/codebuild/bin/criterion_delta.sh @@ -13,8 +13,6 @@ # permissions and limitations under the License. set -eu source ./codebuild/bin/utils.sh -# Disable PQ -export S2N_NO_PQ=1 export AWS_S3_BUCKET="s3://s2n-tls-logs/" # Limit the number of child processes in the test run export RUST_BACKTRACE=1 diff --git a/codebuild/bin/s2n_codebuild.sh b/codebuild/bin/s2n_codebuild.sh index c06d62b213b..06f8bf41d72 100755 --- a/codebuild/bin/s2n_codebuild.sh +++ b/codebuild/bin/s2n_codebuild.sh @@ -61,11 +61,6 @@ if [[ "$OS_NAME" == "linux" && "$TESTS" == "valgrind" ]]; then kill %1 fi -CMAKE_PQ_OPTION="S2N_NO_PQ=False" -if [[ -n "$S2N_NO_PQ" ]]; then - CMAKE_PQ_OPTION="S2N_NO_PQ=True" -fi - test_linked_libcrypto() { s2n_executable="$1" so_path="${LIBCRYPTO_ROOT}/lib/libcrypto.so" @@ -93,7 +88,6 @@ run_integration_v2_tests() { "$CB_BIN_DIR/install_s2n_head.sh" "$(mktemp -d)" cmake . -Bbuild \ -DCMAKE_PREFIX_PATH=$LIBCRYPTO_ROOT \ - -D${CMAKE_PQ_OPTION} \ -DS2N_BLOCK_NONPORTABLE_OPTIMIZATIONS=True \ -DBUILD_SHARED_LIBS=on \ -DS2N_INTEG_TESTS=on \ @@ -114,7 +108,6 @@ run_integration_v2_tests() { run_unit_tests() { cmake . -Bbuild \ -DCMAKE_PREFIX_PATH=$LIBCRYPTO_ROOT \ - -D${CMAKE_PQ_OPTION} \ -DS2N_BLOCK_NONPORTABLE_OPTIMIZATIONS=True \ -DBUILD_SHARED_LIBS=on cmake --build ./build -- -j $(nproc) diff --git a/codebuild/bin/s2n_codebuild_al2.sh b/codebuild/bin/s2n_codebuild_al2.sh index d2686621a8d..2f65425eb33 100755 --- a/codebuild/bin/s2n_codebuild_al2.sh +++ b/codebuild/bin/s2n_codebuild_al2.sh @@ -24,16 +24,11 @@ if [[ "$OS_NAME" == "linux" && -n "$CODEBUILD_BUILD_ARN" ]]; then sudo -E ${PRLIMIT_LOCATION} --pid "$$" --memlock=unlimited:unlimited; fi -CMAKE_PQ_OPTION="S2N_NO_PQ=False" -if [[ -n "$S2N_NO_PQ" ]]; then - CMAKE_PQ_OPTION="S2N_NO_PQ=True" -fi - # Linker flags are a workaround for openssl case "$TESTS" in "unit") cmake . -Bbuild -DCMAKE_EXE_LINKER_FLAGS="-lcrypto -lz" -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \ - -D${CMAKE_PQ_OPTION} -DS2N_BLOCK_NONPORTABLE_OPTIMIZATIONS=True + -DS2N_BLOCK_NONPORTABLE_OPTIMIZATIONS=True cmake --build ./build -j $(nproc) cmake --build ./build --target test -- ARGS="-L unit --output-on-failure" ;; diff --git a/codebuild/bin/s2n_setup_env.sh b/codebuild/bin/s2n_setup_env.sh index d87fecf8229..46b7393abe6 100755 --- a/codebuild/bin/s2n_setup_env.sh +++ b/codebuild/bin/s2n_setup_env.sh @@ -114,7 +114,6 @@ export FUZZ_TIMEOUT_SEC export GB_INSTALL_DIR export OS_NAME export S2N_CORKED_IO -export S2N_NO_PQ # For use by criterion/ci run reports export AWS_S3_URL="s3://s2n-tls-logs/release/" diff --git a/codebuild/spec/buildspec_generalbatch.yml b/codebuild/spec/buildspec_generalbatch.yml index d1e6bc6b5fe..e082d6d6ac5 100644 --- a/codebuild/spec/buildspec_generalbatch.yml +++ b/codebuild/spec/buildspec_generalbatch.yml @@ -173,9 +173,8 @@ batch: BUILD_S2N: 'true' GCC_VERSION: '9' S2N_LIBCRYPTO: 'openssl-1.1.1' - S2N_NO_PQ: 1 TESTS: unit - identifier: s2nUnitNoPQ + identifier: s2nUnitOpenssl111Gcc9 - buildspec: codebuild/spec/buildspec_ubuntu.yml env: compute-type: BUILD_GENERAL1_LARGE @@ -195,7 +194,6 @@ batch: privileged-mode: true type: ARM_CONTAINER variables: - S2N_NO_PQ: 1 TESTS: unit identifier: s2nUnitAl2Arm - buildspec: codebuild/spec/buildspec_amazonlinux2.yml @@ -204,7 +202,6 @@ batch: image: aws/codebuild/amazonlinux2-x86_64-standard:3.0 privileged-mode: true variables: - S2N_NO_PQ: 1 TESTS: unit S2N_LIBCRYPTO: default identifier: s2nUnitAL2 @@ -214,7 +211,6 @@ batch: image: aws/codebuild/amazonlinux2-x86_64-standard:3.0 privileged-mode: true variables: - S2N_NO_PQ: 1 TESTS: unit S2N_LIBCRYPTO: openssl-1.1.1 identifier: s2nUnitAl2Openssl111 @@ -296,7 +292,6 @@ batch: BUILD_S2N: 'true' GCC_VERSION: '6' S2N_LIBCRYPTO: 'libressl' - S2N_NO_PQ: 1 TESTS: unit identifier: s2nUnitLibressl - buildspec: codebuild/spec/buildspec_ubuntu.yml @@ -308,7 +303,6 @@ batch: BUILD_S2N: 'true' GCC_VERSION: '9' S2N_LIBCRYPTO: 'boringssl' - S2N_NO_PQ: 1 TESTS: unit identifier: s2nUnitBoringssl - buildspec: codebuild/spec/buildspec_ubuntu.yml @@ -332,7 +326,6 @@ batch: CC: '/usr/bin/clang' CXX: '/usr/bin/clang++' S2N_LIBCRYPTO: 'awslc' - S2N_NO_PQ: 1 TESTS: unit identifier: s2nUnitClang15 - identifier: 32BitBuildAndUnit diff --git a/codebuild/spec/buildspec_omnibus.yml b/codebuild/spec/buildspec_omnibus.yml index 88d8eaea5be..2277e63f0c3 100644 --- a/codebuild/spec/buildspec_omnibus.yml +++ b/codebuild/spec/buildspec_omnibus.yml @@ -157,7 +157,7 @@ batch: S2N_LIBCRYPTO: 'openssl-1.0.2' BUILD_S2N: 'true' - - identifier: s2nUnitNoPQ + - identifier: s2nUnitOpenssl111Gcc9 buildspec: codebuild/spec/buildspec_ubuntu.yml env: privileged-mode: true @@ -167,7 +167,6 @@ batch: TESTS: unit GCC_VERSION: '9' S2N_LIBCRYPTO: 'openssl-1.1.1' - S2N_NO_PQ: 1 BUILD_S2N: 'true' - identifier: s2nUnitAl2Arm @@ -178,7 +177,6 @@ batch: image: aws/codebuild/amazonlinux2-aarch64-standard:2.0 privileged-mode: true variables: - S2N_NO_PQ: 1 TESTS: unit - identifier: s2nUnitAl2 @@ -189,7 +187,6 @@ batch: compute-type: BUILD_GENERAL1_SMALL variables: TESTS: unit - S2N_NO_PQ: 1 - identifier: s2nLibcryptoInterningOpenSSL buildspec: codebuild/spec/buildspec_ubuntu.yml @@ -280,7 +277,6 @@ batch: BUILD_S2N: 'true' GCC_VERSION: '6' S2N_LIBCRYPTO: 'libressl' - S2N_NO_PQ: 1 TESTS: unit identifier: s2nUnitLibressl @@ -293,7 +289,6 @@ batch: BUILD_S2N: 'true' GCC_VERSION: '9' S2N_LIBCRYPTO: 'boringssl' - S2N_NO_PQ: 1 TESTS: unit identifier: s2nUnitBoringssl diff --git a/codebuild/spec/buildspec_ubuntu_integv2criterion.yml b/codebuild/spec/buildspec_ubuntu_integv2criterion.yml index 0354a0eb55e..0b737941c73 100644 --- a/codebuild/spec/buildspec_ubuntu_integv2criterion.yml +++ b/codebuild/spec/buildspec_ubuntu_integv2criterion.yml @@ -17,7 +17,6 @@ batch: variables: INTEGV2_TEST: test_well_known_endpoints S2N_USE_CRITERION: 2 - S2N_NO_PQ: 1 TESTS: integrationv2crit GCC_VERSION: 6 RUST_BACKTRACE: 1 @@ -29,7 +28,6 @@ batch: variables: INTEGV2_TEST: test_well_known_endpoints S2N_USE_CRITERION: 1 - S2N_NO_PQ: 1 TESTS: integrationv2crit GCC_VERSION: 6 ARTIFACT_BUCKET: s3://s2n-tls-logs/release diff --git a/flake.nix b/flake.nix index aa4f8200d28..2fca3a7d3b3 100644 --- a/flake.nix +++ b/flake.nix @@ -68,8 +68,7 @@ configurePhase = '' cmake -S . -B./build \ -DBUILD_SHARED_LIBS=ON \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ - -DS2N_NO_PQ=0 + -DCMAKE_BUILD_TYPE=RelWithDebInfo ''; # TODO: set when system like aarch64/mips,etc buildPhase = '' 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/cbmc/proofs/lib/summarize.py b/tests/cbmc/proofs/lib/summarize.py index ab3b526bc2c..7a2de8b75ee 100644 --- a/tests/cbmc/proofs/lib/summarize.py +++ b/tests/cbmc/proofs/lib/summarize.py @@ -86,13 +86,13 @@ def _get_status_and_proof_summaries(run_dict): count_statuses = {} proofs = [["Proof", "Status"]] for proof_pipeline in run_dict["pipelines"]: + if proof_pipeline["name"] == "print_tool_versions": + continue status_pretty_name = proof_pipeline["status"].title().replace("_", " ") try: count_statuses[status_pretty_name] += 1 except KeyError: count_statuses[status_pretty_name] = 1 - if proof_pipeline["name"] == "print_tool_versions": - continue proofs.append([proof_pipeline["name"], status_pretty_name]) statuses = [["Status", "Count"]] for status, count in count_statuses.items(): 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/integrationv2/conftest.py b/tests/integrationv2/conftest.py index e9459369f8b..c948d7115f9 100644 --- a/tests/integrationv2/conftest.py +++ b/tests/integrationv2/conftest.py @@ -1,4 +1,4 @@ -from global_flags import set_flag, S2N_PROVIDER_VERSION, S2N_FIPS_MODE, S2N_NO_PQ, S2N_USE_CRITERION +from global_flags import set_flag, S2N_PROVIDER_VERSION, S2N_FIPS_MODE, S2N_USE_CRITERION def pytest_addoption(parser): @@ -6,8 +6,6 @@ def pytest_addoption(parser): default=None, type=str, help="Set the version of the TLS provider") parser.addoption("--fips-mode", action="store", dest="fips-mode", default=False, type=int, help="S2N is running in FIPS mode") - parser.addoption("--no-pq", action="store", dest="no-pq", - default=False, type=int, help="Turn off PQ support") parser.addoption("--provider-criterion", action="store", dest="provider-criterion", default="off", type=str, choices=['off', 'baseline', 'delta'], help="Use Criterion provider in one of 3 modes: [off,baseline,delta]") @@ -21,10 +19,7 @@ def pytest_configure(config): "markers", "uncollect_if(*, func): function to unselect tests from parametrization" ) - no_pq = config.getoption('no-pq', 0) fips_mode = config.getoption('fips-mode', 0) - if no_pq == 1: - set_flag(S2N_NO_PQ, True) if fips_mode == 1: set_flag(S2N_FIPS_MODE, True) diff --git a/tests/integrationv2/global_flags.py b/tests/integrationv2/global_flags.py index b4f0adc599e..5a50e1211bc 100644 --- a/tests/integrationv2/global_flags.py +++ b/tests/integrationv2/global_flags.py @@ -2,9 +2,6 @@ # These flags enable Providers and Tests to determine how to behave # based on the environment. -# If PQ support was not compiled in to S2N -S2N_NO_PQ = 's2n_no_pq' - # If S2N is operating in FIPS mode S2N_FIPS_MODE = 's2n_fips_mode' diff --git a/tests/integrationv2/tox.ini b/tests/integrationv2/tox.ini index 5d087c4e396..fd0705b5192 100644 --- a/tests/integrationv2/tox.ini +++ b/tests/integrationv2/tox.ini @@ -27,5 +27,4 @@ commands = --provider-version={env:S2N_LIBCRYPTO} \ --provider-criterion={env:S2N_USE_CRITERION:"off"} \ --fips-mode={env:S2N_TEST_IN_FIPS_MODE:"0"} \ - --no-pq={env:S2N_NO_PQ:"0"} \ {env:TOX_TEST_NAME:""} 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); } /**