From 458a29d0e82633e156725cbd579f70e393eecb1d Mon Sep 17 00:00:00 2001 From: Alex Weibel Date: Tue, 2 Jan 2024 10:55:45 -0800 Subject: [PATCH] Add PQ integration tests between s2n and AWS-LC's libssl (#4267) --- codebuild/bin/install_default_dependencies.sh | 1 + tests/integrationv2/common.py | 17 +++ tests/integrationv2/providers.py | 33 +++++- tests/integrationv2/test_pq_handshake.py | 105 +++++++++++++++++- 4 files changed, 153 insertions(+), 3 deletions(-) diff --git a/codebuild/bin/install_default_dependencies.sh b/codebuild/bin/install_default_dependencies.sh index 1f8fa9ed584..72f42593e82 100755 --- a/codebuild/bin/install_default_dependencies.sh +++ b/codebuild/bin/install_default_dependencies.sh @@ -68,6 +68,7 @@ fi if [[ "$S2N_LIBCRYPTO" == "awslc" && ! -d "$AWSLC_INSTALL_DIR" ]]; then codebuild/bin/install_awslc.sh "$(mktemp -d)" "$AWSLC_INSTALL_DIR" "0" > /dev/null ; fi + if [[ "$S2N_LIBCRYPTO" == "awslc-fips" && ! -d "$AWSLC_FIPS_INSTALL_DIR" ]]; then codebuild/bin/install_awslc.sh "$(mktemp -d)" "$AWSLC_FIPS_INSTALL_DIR" "1" > /dev/null ; fi diff --git a/tests/integrationv2/common.py b/tests/integrationv2/common.py index aff3c450c13..dae3a47790d 100644 --- a/tests/integrationv2/common.py +++ b/tests/integrationv2/common.py @@ -350,6 +350,21 @@ class Curves(object): P256 = Curve("P-256") P384 = Curve("P-384") P521 = Curve("P-521") + SecP256r1Kyber768Draft00 = Curve("SecP256r1Kyber768Draft00") + X25519Kyber768Draft00 = Curve("X25519Kyber768Draft00") + + @staticmethod + def from_name(name): + curves = [ + curve for attr in vars(Curves) + if not callable(curve := getattr(Curves, attr)) + and not attr.startswith("_") + and curve.name + ] + return { + curve.name: curve + for curve in curves + }.get(name) class KemGroup(object): @@ -369,6 +384,8 @@ class KemGroups(object): P256_KYBER512R3 = KemGroup("p256_kyber512") P384_KYBER768R3 = KemGroup("p384_kyber768") P521_KYBER1024R3 = KemGroup("p521_kyber1024") + SecP256r1Kyber768Draft00 = KemGroup("SecP256r1Kyber768Draft00") + X25519Kyber768Draft00 = KemGroup("X25519Kyber768Draft00") class Signature(object): diff --git a/tests/integrationv2/providers.py b/tests/integrationv2/providers.py index 2dbe5e21af2..285d3b98133 100644 --- a/tests/integrationv2/providers.py +++ b/tests/integrationv2/providers.py @@ -677,7 +677,31 @@ def get_send_marker(cls): return 'Cert issuer:' def setup_server(self): - pytest.skip('BoringSSL does not support server mode at this time') + cmd_line = ['bssl', 's_server'] + cmd_line.extend(['-accept', self.options.port]) + if self.options.cert is not None: + cmd_line.extend(['-cert', self.options.cert]) + if self.options.key is not None: + cmd_line.extend(['-key', self.options.key]) + if self.options.curve is not None: + if self.options.curve == Curves.P256: + cmd_line.extend(['-curves', 'P-256']) + elif self.options.curve == Curves.P384: + cmd_line.extend(['-curves', 'P-384']) + elif self.options.curve == Curves.P521: + cmd_line.extend(['-curves', 'P-521']) + elif self.options.curve == Curves.SecP256r1Kyber768Draft00: + cmd_line.extend(['-curves', 'SecP256r1Kyber768Draft00']) + elif self.options.curve == Curves.X25519Kyber768Draft00: + cmd_line.extend(['-curves', 'X25519Kyber768Draft00']) + elif self.options.curve == Curves.X25519: + pytest.skip('BoringSSL does not support curve {}'.format( + self.options.curve)) + + if self.options.extra_flags is not None: + cmd_line.extend(self.options.extra_flags) + + return cmd_line def setup_client(self): cmd_line = ['bssl', 's_client'] @@ -704,10 +728,17 @@ def setup_client(self): cmd_line.extend(['-curves', 'P-384']) elif self.options.curve == Curves.P521: cmd_line.extend(['-curves', 'P-521']) + elif self.options.curve == Curves.SecP256r1Kyber768Draft00: + cmd_line.extend(['-curves', 'SecP256r1Kyber768Draft00']) + elif self.options.curve == Curves.X25519Kyber768Draft00: + cmd_line.extend(['-curves', 'X25519Kyber768Draft00']) elif self.options.curve == Curves.X25519: pytest.skip('BoringSSL does not support curve {}'.format( self.options.curve)) + if self.options.extra_flags is not None: + cmd_line.extend(self.options.extra_flags) + # Clients are always ready to connect self.set_provider_ready() diff --git a/tests/integrationv2/test_pq_handshake.py b/tests/integrationv2/test_pq_handshake.py index d743f1192fa..dd33a7fe1fe 100644 --- a/tests/integrationv2/test_pq_handshake.py +++ b/tests/integrationv2/test_pq_handshake.py @@ -2,9 +2,9 @@ import os from configuration import available_ports -from common import Ciphers, ProviderOptions, Protocols, KemGroups, Certificates, pq_enabled +from common import Ciphers, Curves, ProviderOptions, Protocols, KemGroups, Certificates, pq_enabled from fixtures import managed_process # lgtm [py/unused-import] -from providers import Provider, S2N, OpenSSL +from providers import Provider, S2N, OpenSSL, BoringSSL from utils import invalid_test_parameters, get_parameter_name, to_bytes from global_flags import get_flag, S2N_PROVIDER_VERSION @@ -138,6 +138,14 @@ (KemGroups.P521_KYBER1024R3, Ciphers.PQ_TLS_1_3_2023_06_01): {"cipher": "AES256_GCM_SHA384", "kem": "NONE", "kem_group": "secp521r1_kyber-1024-r3"}, + (Ciphers.PQ_TLS_1_3_2023_06_01, KemGroups.X25519Kyber768Draft00): + {"cipher": "TLS_AES_256_GCM_SHA384", + "kem": "NONE", + "kem_group": "X25519Kyber768Draft00"}, + (Ciphers.PQ_TLS_1_3_2023_06_01, KemGroups.SecP256r1Kyber768Draft00): + {"cipher": "TLS_AES_256_GCM_SHA384", + "kem": "NONE", + "kem_group": "SecP256r1Kyber768Draft00"}, } """ @@ -180,6 +188,13 @@ def assert_s2n_negotiation_parameters(s2n_results, expected_result): assert to_bytes(expected_result['kem_group']) in s2n_results.stdout +def assert_awslc_negotiation_parameters(awslc_results, expected_result): + assert expected_result is not None + assert awslc_results.exit_code is 0 + assert to_bytes(("group: " + expected_result['kem_group'])) in awslc_results.stderr + assert to_bytes(("Cipher: " + expected_result['cipher'])) in awslc_results.stderr + + def test_nothing(): """ Sometimes the pq handshake test parameters in combination with the s2n libcrypto @@ -253,6 +268,92 @@ def test_s2nc_to_s2nd_pq_handshake(managed_process, protocol, certificate, clien assert_s2n_negotiation_parameters(results, expected_result) +@pytest.mark.parametrize("s2n_client_policy", [Ciphers.PQ_TLS_1_3_2023_06_01], ids=get_parameter_name) +@pytest.mark.parametrize("awslc_server_group", [KemGroups.SecP256r1Kyber768Draft00, KemGroups.X25519Kyber768Draft00], ids=get_parameter_name) +def test_s2nc_to_awslc_pq_handshake(managed_process, s2n_client_policy, awslc_server_group): + + if not pq_enabled(): + pytest.skip("PQ not enabled") + + if "awslc" not in get_flag(S2N_PROVIDER_VERSION): + pytest.skip("s2n must be compiled with awslc libcrypto in order to test PQ TLS compatibility") + + if "fips" in get_flag(S2N_PROVIDER_VERSION): + pytest.skip("No FIPS validated version of AWS-LC has support for negotiating Hybrid PQ TLS yet") + + port = next(available_ports) + + awslc_env_vars = dict() + awslc_env_vars["PATH"] = os.path.abspath("../../test-deps/awslc/bin") + + s2nc_client_options = ProviderOptions( + mode=Provider.ClientMode, + port=port, + insecure=True, + cipher=s2n_client_policy, + protocol=Protocols.TLS13) + + awslc_server_options = ProviderOptions( + mode=Provider.ServerMode, + port=port, + protocol=Protocols.TLS13, + env_overrides=awslc_env_vars, + curve=Curves.from_name(awslc_server_group.oqs_name)) + + awslc_server = managed_process(BoringSSL, awslc_server_options, timeout=5) + s2n_client = managed_process(S2N, s2nc_client_options, timeout=5) + expected_result = EXPECTED_RESULTS.get((s2n_client_policy, awslc_server_group), None) + + awslc_result = next(awslc_server.get_results()) + assert_awslc_negotiation_parameters(awslc_result, expected_result) + + s2nd_result = next(s2n_client.get_results()) + assert_s2n_negotiation_parameters(s2nd_result, expected_result) + + +@pytest.mark.parametrize("s2n_server_policy", [Ciphers.PQ_TLS_1_3_2023_06_01], ids=get_parameter_name) +@pytest.mark.parametrize("awslc_client_group", [KemGroups.SecP256r1Kyber768Draft00, KemGroups.X25519Kyber768Draft00], ids=get_parameter_name) +def test_s2nd_to_awslc_pq_handshake(managed_process, s2n_server_policy, awslc_client_group): + + if not pq_enabled(): + pytest.skip("PQ not enabled") + + if "awslc" not in get_flag(S2N_PROVIDER_VERSION): + pytest.skip("s2n must be compiled with awslc libcrypto in order to test PQ TLS compatibility") + + if "fips" in get_flag(S2N_PROVIDER_VERSION): + pytest.skip("No FIPS validated version of AWS-LC has support for negotiating Hybrid PQ TLS yet") + + port = next(available_ports) + + awslc_env_vars = dict() + awslc_env_vars["PATH"] = os.path.abspath("../../test-deps/awslc/bin") + + s2nd_server_options = ProviderOptions( + mode=Provider.ServerMode, + port=port, + insecure=True, + cipher=s2n_server_policy, + protocol=Protocols.TLS13) + + awslc_client_options = ProviderOptions( + mode=Provider.ClientMode, + port=port, + protocol=Protocols.TLS13, + env_overrides=awslc_env_vars, + curve=Curves.from_name(awslc_client_group.oqs_name)) + + s2nd_server = managed_process(S2N, s2nd_server_options, timeout=5) + awslc_client = managed_process(BoringSSL, awslc_client_options, timeout=5) + expected_result = EXPECTED_RESULTS.get((s2n_server_policy, awslc_client_group), None) + + awslc_result = next(awslc_client.get_results()) + assert_awslc_negotiation_parameters(awslc_result, expected_result) + + s2nd_result = next(s2nd_server.get_results()) + assert_s2n_negotiation_parameters(s2nd_result, expected_result) + + @pytest.mark.uncollect_if(func=invalid_test_parameters) @pytest.mark.parametrize("protocol", [Protocols.TLS13], ids=get_parameter_name) @pytest.mark.parametrize("cipher", [Ciphers.PQ_TLS_1_0_2020_12], ids=get_parameter_name)