From 3b045ebbf0e7a40130c4f1882fce858815e22e4b Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Sat, 9 Nov 2024 01:31:53 +0000 Subject: [PATCH 01/13] test: add rust well-known-endpoints test --- bindings/rust/integration/Cargo.toml | 16 +++ .../integration/tests/internet_http_client.rs | 104 ++++++++++++++ .../tests/internet_kms_pq_client.rs | 89 ++++++++++++ bindings/rust/rust-toolchain | 2 +- bindings/rust/s2n-tls-hyper/Cargo.toml | 3 + .../rust/s2n-tls-hyper/tests/web_client.rs | 25 ---- bindings/rust/s2n-tls/src/connection.rs | 17 +++ bindings/rust/s2n-tls/src/testing/s2n_tls.rs | 29 ++++ .../test_well_known_endpoints.py | 134 ------------------ 9 files changed, 259 insertions(+), 160 deletions(-) create mode 100644 bindings/rust/integration/tests/internet_http_client.rs create mode 100644 bindings/rust/integration/tests/internet_kms_pq_client.rs delete mode 100644 bindings/rust/s2n-tls-hyper/tests/web_client.rs delete mode 100644 tests/integrationv2/test_well_known_endpoints.py diff --git a/bindings/rust/integration/Cargo.toml b/bindings/rust/integration/Cargo.toml index a229b2df586..3eca15ee249 100644 --- a/bindings/rust/integration/Cargo.toml +++ b/bindings/rust/integration/Cargo.toml @@ -7,7 +7,11 @@ publish = false [dependencies] s2n-tls = { path = "../s2n-tls"} +s2n-tls-hyper = { path = "../s2n-tls-hyper" } +s2n-tls-tokio = { path = "../s2n-tls-tokio" } s2n-tls-sys = { path = "../s2n-tls-sys" } + +# s2nc/d criterion harness dependencies criterion = { version = "0.3", features = ["html_reports"] } anyhow = "1" unicode-width = "=0.1.13" # newer versions require newer rust, see https://github.com/aws/s2n-tls/issues/4786 @@ -21,4 +25,16 @@ name = "s2nd" harness = false [dev-dependencies] +tokio = { version = "1", features = ["macros", "test-util"] } + +tracing = "0.1" +tracing-subscriber = "0.3" +tracing-appender = "0.2" +test-log = "0.2" + +http = "1.1" +http-body-util = "0.1" +bytes = "1.8" +hyper-util = "0.1" + regex = "=1.9.6" # newer versions require rust 1.65, see https://github.com/aws/s2n-tls/issues/4242 diff --git a/bindings/rust/integration/tests/internet_http_client.rs b/bindings/rust/integration/tests/internet_http_client.rs new file mode 100644 index 00000000000..e783f3aac48 --- /dev/null +++ b/bindings/rust/integration/tests/internet_http_client.rs @@ -0,0 +1,104 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use bytes::Bytes; +use http::{StatusCode, Uri}; +use http_body_util::{BodyExt, Empty}; +use hyper_util::{client::legacy::Client, rt::TokioExecutor}; +use s2n_tls::{config::Config, security}; +use s2n_tls_hyper::connector::HttpsConnector; +use std::{error::Error, str::FromStr}; +use tracing_subscriber::filter::LevelFilter; + +#[derive(Debug)] +struct TestCase { + pub query_target: &'static str, + pub expected_status_code: u16, +} + +impl TestCase { + const fn new(domain: &'static str, expected_status_code: u16) -> Self { + TestCase { + query_target: domain, + expected_status_code, + } + } +} + +const TEST_CASES: &[TestCase] = &[ + // Akamai hangs indefinitely. This is also observed with curl and chrome + // https://github.com/aws/s2n-tls/issues/4883 + // TestCase::new("https://www.akamai.com/", 200), + + // this is a link to the s2n-tls unit test coverage report, hosted on cloudfront + TestCase::new("https://dx1inn44oyl7n.cloudfront.net/main/index.html", 200), + // this is a link to a non-existent S3 item + TestCase::new("https://notmybucket.s3.amazonaws.com/folder/afile.jpg", 403), + TestCase::new("https://www.amazon.com", 200), + TestCase::new("https://www.apple.com", 200), + TestCase::new("https://www.att.com", 200), + TestCase::new("https://www.cloudflare.com", 200), + TestCase::new("https://www.ebay.com", 200), + TestCase::new("https://www.google.com", 200), + TestCase::new("https://www.mozilla.org", 200), + TestCase::new("https://www.netflix.com", 200), + TestCase::new("https://www.openssl.org", 200), + TestCase::new("https://www.t-mobile.com", 200), + TestCase::new("https://www.verizon.com", 200), + TestCase::new("https://www.wikipedia.org", 200), + TestCase::new("https://www.yahoo.com", 200), + TestCase::new("https://www.youtube.com", 200), + TestCase::new("https://www.github.com", 301), + TestCase::new("https://www.samsung.com", 301), + TestCase::new("https://www.twitter.com", 301), + TestCase::new("https://www.facebook.com", 302), + TestCase::new("https://www.microsoft.com", 302), + TestCase::new("https://www.ibm.com", 303), + TestCase::new("https://www.f5.com", 403), +]; + +/// Purpose: ensure that s2n-tls is compatible with other http/TLS implementations +/// +/// This test uses s2n-tls-hyper to make http requests over a TLS connection to +/// a number of well known http sites. +#[tokio::test] +async fn http_get() -> Result<(), Box> { + async fn get(test_case: &TestCase) -> Result<(), Box> { + for p in [security::DEFAULT, security::DEFAULT_TLS13] { + tracing::info!("executing test case {:#?} with {:?}", test_case, p); + + let mut config = Config::builder(); + config.set_security_policy(&p)?; + + let connector = HttpsConnector::new(config.build()?); + let client: Client<_, Empty> = + Client::builder(TokioExecutor::new()).build(connector); + + let uri = Uri::from_str(test_case.query_target)?; + let response = client.get(uri).await?; + + let expected_status = StatusCode::from_u16(test_case.expected_status_code).unwrap(); + assert_eq!(response.status(), expected_status); + + if expected_status == StatusCode::OK { + let body = response.into_body().collect().await?.to_bytes(); + assert!(!body.is_empty()); + } + } + + Ok(()) + } + + // enable tracing metrics. hyper/http has extensive logging, so these logs + // are very useful if failures happen in CI. + tracing_subscriber::fmt() + .with_max_level(LevelFilter::TRACE) + .with_test_writer() + .init(); + + for case in TEST_CASES { + get(case).await?; + } + + Ok(()) +} diff --git a/bindings/rust/integration/tests/internet_kms_pq_client.rs b/bindings/rust/integration/tests/internet_kms_pq_client.rs new file mode 100644 index 00000000000..7593cddd7c4 --- /dev/null +++ b/bindings/rust/integration/tests/internet_kms_pq_client.rs @@ -0,0 +1,89 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use s2n_tls::{config::Config, security::Policy}; +use s2n_tls_tokio::TlsConnector; +use std::error::Error; +use tokio::net::TcpStream; +use tracing::level_filters::LevelFilter; + +#[derive(Debug)] +struct TestCase { + s2n_security_policy: &'static str, + expected_cipher: &'static str, + expected_kem: Option<&'static str>, +} + +// When KMS moves to standardized ML-KEM, this test will fail. The test should +// then be updated with the new security policy that supports ML-KEM. +const TEST_CASES: &[TestCase] = &[ + // positive case: negotiates kyber + TestCase { + s2n_security_policy: "KMS-PQ-TLS-1-0-2020-07", + expected_cipher: "ECDHE-KYBER-RSA-AES256-GCM-SHA384", + expected_kem: Some("kyber512r3"), + }, + // negative cases: these policies support a variety of early kyber drafts. + // We want to confirm that non-supported kyber drafts successfully fall + // back to a full handshake. + TestCase { + s2n_security_policy: "KMS-PQ-TLS-1-0-2019-06", + expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", + expected_kem: None, + }, + TestCase { + s2n_security_policy: "PQ-SIKE-TEST-TLS-1-0-2019-11", + expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", + expected_kem: None, + }, + TestCase { + s2n_security_policy: "KMS-PQ-TLS-1-0-2020-02", + expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", + expected_kem: None, + }, + TestCase { + s2n_security_policy: "PQ-SIKE-TEST-TLS-1-0-2020-02", + expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", + expected_kem: None, + }, +]; + +/// Purpose: ensure that we remain compatible with existing pq AWS deployments. +/// +/// This test makes network calls over the public internet. +/// +/// KMS is has PQ support. Assert that we successfully negotiate PQ key exchange. +#[tokio::test] +async fn pq_kms_test() -> Result<(), Box> { + const DOMAIN: &str = "kms.us-east-1.amazonaws.com"; + const SOCKET_ADDR: (&str, u16) = (DOMAIN, 443); + + async fn test(test_case: &TestCase) -> Result<(), Box> { + tracing::info!("executing test case: {:#?}", test_case); + + let mut config = Config::builder(); + config.set_security_policy(&Policy::from_version(test_case.s2n_security_policy)?)?; + + let client = TlsConnector::new(config.build()?); + // open the TCP stream + let stream = TcpStream::connect(SOCKET_ADDR).await?; + // complete the TLS handshake + let tls = client.connect(DOMAIN, stream).await?; + + assert_eq!(tls.as_ref().cipher_suite()?, test_case.expected_cipher); + assert_eq!(tls.as_ref().kem_name()?, test_case.expected_kem); + + Ok(()) + } + + tracing_subscriber::fmt() + .with_max_level(LevelFilter::TRACE) + .with_test_writer() + .init(); + + for test_case in TEST_CASES { + test(test_case).await? + } + + Ok(()) +} diff --git a/bindings/rust/rust-toolchain b/bindings/rust/rust-toolchain index af92bdd9f58..2bf5ad0447d 100644 --- a/bindings/rust/rust-toolchain +++ b/bindings/rust/rust-toolchain @@ -1 +1 @@ -1.63.0 +stable diff --git a/bindings/rust/s2n-tls-hyper/Cargo.toml b/bindings/rust/s2n-tls-hyper/Cargo.toml index 8c474a9e739..c8cdb8a0ced 100644 --- a/bindings/rust/s2n-tls-hyper/Cargo.toml +++ b/bindings/rust/s2n-tls-hyper/Cargo.toml @@ -19,6 +19,9 @@ hyper = { version = "1" } hyper-util = { version = "0.1", features = ["client-legacy", "tokio", "http1"] } tower-service = { version = "0.3" } http = { version= "1" } +tracing-appender = "0.2.3" +tracing-subscriber = "0.3.18" +tracing = "0.1.40" [dev-dependencies] tokio = { version = "1", features = ["macros", "test-util"] } diff --git a/bindings/rust/s2n-tls-hyper/tests/web_client.rs b/bindings/rust/s2n-tls-hyper/tests/web_client.rs deleted file mode 100644 index 41a72970ed3..00000000000 --- a/bindings/rust/s2n-tls-hyper/tests/web_client.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -use bytes::Bytes; -use http::{status, Uri}; -use http_body_util::{BodyExt, Empty}; -use hyper_util::{client::legacy::Client, rt::TokioExecutor}; -use s2n_tls::config::Config; -use s2n_tls_hyper::connector::HttpsConnector; -use std::{error::Error, str::FromStr}; - -#[tokio::test] -async fn test_get_request() -> Result<(), Box> { - let connector = HttpsConnector::new(Config::default()); - let client: Client<_, Empty> = Client::builder(TokioExecutor::new()).build(connector); - - let uri = Uri::from_str("https://www.amazon.com")?; - let response = client.get(uri).await?; - assert_eq!(response.status(), status::StatusCode::OK); - - let body = response.into_body().collect().await?.to_bytes(); - assert!(!body.is_empty()); - - Ok(()) -} diff --git a/bindings/rust/s2n-tls/src/connection.rs b/bindings/rust/s2n-tls/src/connection.rs index 0ac0a389650..8d0c6b60a3c 100644 --- a/bindings/rust/s2n-tls/src/connection.rs +++ b/bindings/rust/s2n-tls/src/connection.rs @@ -972,6 +972,23 @@ impl Connection { } } + pub fn kem_name(&self) -> Result, Error> { + let kem_name = unsafe {s2n_connection_get_kem_name(self.connection.as_ptr()).into_result()?}; + let name = unsafe { + // SAFETY: The data is null terminated because it is declared as a C + // string literal. + // SAFETY: kem_name has a static lifetime because it lives on a const + // struct s2n_kem with file scope. + const_str!(kem_name) + }; + + match name { + Ok("NONE") => Ok(None), + Ok(name) => Ok(Some(name)), + Err(e) => Err(e), + } + } + pub fn selected_curve(&self) -> Result<&str, Error> { let curve = unsafe { s2n_connection_get_curve(self.connection.as_ptr()).into_result()? }; unsafe { diff --git a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs index a641eb993f0..87609322187 100644 --- a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs +++ b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs @@ -12,6 +12,7 @@ mod tests { use alloc::sync::Arc; use core::sync::atomic::Ordering; use futures_test::task::{new_count_waker, noop_waker}; + use security::Policy; use std::{fs, path::Path, pin::Pin, sync::atomic::AtomicUsize}; #[test] @@ -26,6 +27,34 @@ mod tests { assert!(TestPair::handshake_with_config(&config).is_ok()); } + #[test] + fn kem_name_retrieval() -> Result<(), Error> { + // PQ isn't supported + { + let policy = Policy::from_version("20240501")?; + let config = build_config(&policy)?; + let mut pair = TestPair::from_config(&config); + + // before negotiation, kem_name is none + assert!(pair.client.kem_name()?.is_none()); + + pair.handshake().unwrap(); + assert!(pair.client.kem_name()?.is_none()); + } + + // PQ is supported + { + let policy = Policy::from_version("KMS-PQ-TLS-1-0-2020-07")?; + let config = build_config(&policy)?; + let mut pair = TestPair::from_config(&config); + + pair.handshake().unwrap(); + assert_eq!(pair.client.kem_name()?, Some("kyber512r3")); + } + + Ok(()) + } + #[test] fn default_config_and_clone_interaction() -> Result<(), Error> { let config = build_config(&security::DEFAULT_TLS13)?; diff --git a/tests/integrationv2/test_well_known_endpoints.py b/tests/integrationv2/test_well_known_endpoints.py deleted file mode 100644 index 21869520da0..00000000000 --- a/tests/integrationv2/test_well_known_endpoints.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: Apache-2.0 -import pytest - -from constants import TRUST_STORE_BUNDLE, TRUST_STORE_TRUSTED_BUNDLE -from configuration import PROTOCOLS -from common import ProviderOptions, Ciphers, pq_enabled -from fixtures import managed_process # lgtm [py/unused-import] -from global_flags import get_flag, is_criterion_on, S2N_FIPS_MODE, S2N_USE_CRITERION -from providers import Provider, S2N -from test_pq_handshake import PQ_ENABLED_FLAG -from utils import invalid_test_parameters, get_parameter_name, to_bytes - - -ENDPOINTS = [ - "www.akamai.com", - "www.amazon.com", - "kms.us-east-1.amazonaws.com", - "s3.us-west-2.amazonaws.com", - "www.apple.com", - "www.att.com", - # "www.badssl.com", - # "mozilla-intermediate.badssl.com", - # "mozilla-modern.badssl.com", - # "rsa2048.badssl.com", - # "rsa4096.badssl.com", - # "sha256.badssl.com", - # "sha384.badssl.com", - # "sha512.badssl.com", - # "tls-v1-0.badssl.com", - # "tls-v1-1.badssl.com", - # "tls-v1-2.badssl.com", - "www.cloudflare.com", - "www.ebay.com", - "www.f5.com", - "www.facebook.com", - "www.google.com", - "www.github.com", - "www.ibm.com", - "www.microsoft.com", - # https://github.com/aws/s2n-tls/issues/4879 - # "www.mozilla.org", - "www.netflix.com", - "www.openssl.org", - "www.samsung.com", - "www.t-mobile.com", - "www.twitter.com", - "www.verizon.com", - "www.wikipedia.org", - "www.yahoo.com", - "www.youtube.com", -] - -CIPHERS = [ - None, # `None` will default to the appropriate `test_all` cipher preference in the S2N client provider - Ciphers.KMS_PQ_TLS_1_0_2019_06, - Ciphers.PQ_SIKE_TEST_TLS_1_0_2019_11, - Ciphers.KMS_PQ_TLS_1_0_2020_07, - Ciphers.KMS_PQ_TLS_1_0_2020_02, - Ciphers.PQ_SIKE_TEST_TLS_1_0_2020_02 -] - - -if pq_enabled(): - EXPECTED_RESULTS = { - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2019_06): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.PQ_SIKE_TEST_TLS_1_0_2019_11): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2020_07): - {"cipher": "ECDHE-KYBER-RSA-AES256-GCM-SHA384", "kem": "kyber512r3"}, - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2020_02): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.PQ_SIKE_TEST_TLS_1_0_2020_02): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - } -else: - EXPECTED_RESULTS = { - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2019_06): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.PQ_SIKE_TEST_TLS_1_0_2019_11): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2020_07): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2020_02): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.PQ_SIKE_TEST_TLS_1_0_2020_02): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - } - - -@pytest.mark.uncollect_if(func=invalid_test_parameters) -@pytest.mark.parametrize("protocol", PROTOCOLS, ids=get_parameter_name) -@pytest.mark.parametrize("endpoint", ENDPOINTS, ids=get_parameter_name) -@pytest.mark.parametrize("provider", [S2N], ids=get_parameter_name) -@pytest.mark.parametrize("cipher", CIPHERS, ids=get_parameter_name) -@pytest.mark.flaky(reruns=5, reruns_delay=4) -def test_well_known_endpoints(managed_process, protocol, endpoint, provider, cipher): - port = "443" - - client_options = ProviderOptions( - mode=Provider.ClientMode, - host=endpoint, - port=port, - insecure=False, - trust_store=TRUST_STORE_BUNDLE, - protocol=protocol, - cipher=cipher) - - if get_flag(S2N_FIPS_MODE) is True: - client_options.trust_store = TRUST_STORE_TRUSTED_BUNDLE - - # TODO: Understand the failure with criterion and this endpoint. - if is_criterion_on() and 'www.netflix.com' in endpoint: - pytest.skip() - - # expect_stderr=True because S2N sometimes receives OCSP responses: - # https://github.com/aws/s2n-tls/blob/14ed186a13c1ffae7fbb036ed5d2849ce7c17403/bin/echo.c#L180-L184 - client = managed_process(provider, client_options, - timeout=5, expect_stderr=True) - - expected_result = EXPECTED_RESULTS.get((endpoint, cipher), None) - - for results in client.get_results(): - results.assert_success() - - if expected_result is not None: - assert to_bytes(expected_result['cipher']) in results.stdout - if expected_result['kem']: - assert to_bytes(expected_result['kem']) in results.stdout - assert to_bytes(PQ_ENABLED_FLAG) in results.stdout - else: - assert to_bytes('KEM:') not in results.stdout - assert to_bytes(PQ_ENABLED_FLAG) not in results.stdout From 8b54cdb9acf8b9b430628ccd1da90c3903c759e3 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Sat, 9 Nov 2024 02:00:33 +0000 Subject: [PATCH 02/13] feature gate network tests --- .github/workflows/ci_rust.yml | 4 + bindings/rust/integration/Cargo.toml | 5 + .../integration/tests/internet_http_client.rs | 167 +++++++++--------- .../tests/internet_kms_pq_client.rs | 152 ++++++++-------- 4 files changed, 172 insertions(+), 156 deletions(-) diff --git a/.github/workflows/ci_rust.yml b/.github/workflows/ci_rust.yml index 97637855b09..2e9e8c692be 100644 --- a/.github/workflows/ci_rust.yml +++ b/.github/workflows/ci_rust.yml @@ -58,6 +58,10 @@ jobs: working-directory: ${{env.ROOT_PATH}} run: cargo test --features unstable-renegotiate + - name: Network Tests + working-directory: ${{env.ROOT_PATH}}/integration + run: cargo test --features network-tests + - name: Test external build # if this test is failing, make sure that api headers are appropriately # included. For a symbol to be visible in a shared lib, the diff --git a/bindings/rust/integration/Cargo.toml b/bindings/rust/integration/Cargo.toml index 3eca15ee249..4f1b6486e45 100644 --- a/bindings/rust/integration/Cargo.toml +++ b/bindings/rust/integration/Cargo.toml @@ -5,6 +5,11 @@ authors = ["AWS s2n"] edition = "2021" publish = false +[features] +# network tests are useful but expensive in terms of duration and inherent flakiness. +# Accordingly they are gated behind this feature flag. +network-tests = [] + [dependencies] s2n-tls = { path = "../s2n-tls"} s2n-tls-hyper = { path = "../s2n-tls-hyper" } diff --git a/bindings/rust/integration/tests/internet_http_client.rs b/bindings/rust/integration/tests/internet_http_client.rs index e783f3aac48..e664a544e6b 100644 --- a/bindings/rust/integration/tests/internet_http_client.rs +++ b/bindings/rust/integration/tests/internet_http_client.rs @@ -1,104 +1,107 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use bytes::Bytes; -use http::{StatusCode, Uri}; -use http_body_util::{BodyExt, Empty}; -use hyper_util::{client::legacy::Client, rt::TokioExecutor}; -use s2n_tls::{config::Config, security}; -use s2n_tls_hyper::connector::HttpsConnector; -use std::{error::Error, str::FromStr}; -use tracing_subscriber::filter::LevelFilter; +/// Purpose: ensure that s2n-tls is compatible with other http/TLS implementations +/// +/// This test uses s2n-tls-hyper to make http requests over a TLS connection to +/// a number of well known http sites. +#[cfg(feature = "network-tests")] +mod http_get { + use bytes::Bytes; + use http::{StatusCode, Uri}; + use http_body_util::{BodyExt, Empty}; + use hyper_util::{client::legacy::Client, rt::TokioExecutor}; + use s2n_tls::{config::Config, security}; + use s2n_tls_hyper::connector::HttpsConnector; + use std::{error::Error, str::FromStr}; + use tracing_subscriber::filter::LevelFilter; -#[derive(Debug)] -struct TestCase { - pub query_target: &'static str, - pub expected_status_code: u16, -} + #[derive(Debug)] + struct TestCase { + pub query_target: &'static str, + pub expected_status_code: u16, + } -impl TestCase { - const fn new(domain: &'static str, expected_status_code: u16) -> Self { - TestCase { - query_target: domain, - expected_status_code, + impl TestCase { + const fn new(domain: &'static str, expected_status_code: u16) -> Self { + TestCase { + query_target: domain, + expected_status_code, + } } } -} -const TEST_CASES: &[TestCase] = &[ - // Akamai hangs indefinitely. This is also observed with curl and chrome - // https://github.com/aws/s2n-tls/issues/4883 - // TestCase::new("https://www.akamai.com/", 200), + const TEST_CASES: &[TestCase] = &[ + // Akamai hangs indefinitely. This is also observed with curl and chrome + // https://github.com/aws/s2n-tls/issues/4883 + // TestCase::new("https://www.akamai.com/", 200), - // this is a link to the s2n-tls unit test coverage report, hosted on cloudfront - TestCase::new("https://dx1inn44oyl7n.cloudfront.net/main/index.html", 200), - // this is a link to a non-existent S3 item - TestCase::new("https://notmybucket.s3.amazonaws.com/folder/afile.jpg", 403), - TestCase::new("https://www.amazon.com", 200), - TestCase::new("https://www.apple.com", 200), - TestCase::new("https://www.att.com", 200), - TestCase::new("https://www.cloudflare.com", 200), - TestCase::new("https://www.ebay.com", 200), - TestCase::new("https://www.google.com", 200), - TestCase::new("https://www.mozilla.org", 200), - TestCase::new("https://www.netflix.com", 200), - TestCase::new("https://www.openssl.org", 200), - TestCase::new("https://www.t-mobile.com", 200), - TestCase::new("https://www.verizon.com", 200), - TestCase::new("https://www.wikipedia.org", 200), - TestCase::new("https://www.yahoo.com", 200), - TestCase::new("https://www.youtube.com", 200), - TestCase::new("https://www.github.com", 301), - TestCase::new("https://www.samsung.com", 301), - TestCase::new("https://www.twitter.com", 301), - TestCase::new("https://www.facebook.com", 302), - TestCase::new("https://www.microsoft.com", 302), - TestCase::new("https://www.ibm.com", 303), - TestCase::new("https://www.f5.com", 403), -]; + // this is a link to the s2n-tls unit test coverage report, hosted on cloudfront + TestCase::new("https://dx1inn44oyl7n.cloudfront.net/main/index.html", 200), + // this is a link to a non-existent S3 item + TestCase::new("https://notmybucket.s3.amazonaws.com/folder/afile.jpg", 403), + TestCase::new("https://www.amazon.com", 200), + TestCase::new("https://www.apple.com", 200), + TestCase::new("https://www.att.com", 200), + TestCase::new("https://www.cloudflare.com", 200), + TestCase::new("https://www.ebay.com", 200), + TestCase::new("https://www.google.com", 200), + TestCase::new("https://www.mozilla.org", 200), + TestCase::new("https://www.netflix.com", 200), + TestCase::new("https://www.openssl.org", 200), + TestCase::new("https://www.t-mobile.com", 200), + TestCase::new("https://www.verizon.com", 200), + TestCase::new("https://www.wikipedia.org", 200), + TestCase::new("https://www.yahoo.com", 200), + TestCase::new("https://www.youtube.com", 200), + TestCase::new("https://www.github.com", 301), + TestCase::new("https://www.samsung.com", 301), + TestCase::new("https://www.twitter.com", 301), + TestCase::new("https://www.facebook.com", 302), + TestCase::new("https://www.microsoft.com", 302), + TestCase::new("https://www.ibm.com", 303), + TestCase::new("https://www.f5.com", 403), + ]; -/// Purpose: ensure that s2n-tls is compatible with other http/TLS implementations -/// -/// This test uses s2n-tls-hyper to make http requests over a TLS connection to -/// a number of well known http sites. -#[tokio::test] -async fn http_get() -> Result<(), Box> { - async fn get(test_case: &TestCase) -> Result<(), Box> { - for p in [security::DEFAULT, security::DEFAULT_TLS13] { - tracing::info!("executing test case {:#?} with {:?}", test_case, p); + #[tokio::test] + async fn http_get_test() -> Result<(), Box> { + async fn get(test_case: &TestCase) -> Result<(), Box> { + for p in [security::DEFAULT, security::DEFAULT_TLS13] { + tracing::info!("executing test case {:#?} with {:?}", test_case, p); - let mut config = Config::builder(); - config.set_security_policy(&p)?; + let mut config = Config::builder(); + config.set_security_policy(&p)?; - let connector = HttpsConnector::new(config.build()?); - let client: Client<_, Empty> = - Client::builder(TokioExecutor::new()).build(connector); + let connector = HttpsConnector::new(config.build()?); + let client: Client<_, Empty> = + Client::builder(TokioExecutor::new()).build(connector); - let uri = Uri::from_str(test_case.query_target)?; - let response = client.get(uri).await?; + let uri = Uri::from_str(test_case.query_target)?; + let response = client.get(uri).await?; - let expected_status = StatusCode::from_u16(test_case.expected_status_code).unwrap(); - assert_eq!(response.status(), expected_status); + let expected_status = StatusCode::from_u16(test_case.expected_status_code).unwrap(); + assert_eq!(response.status(), expected_status); - if expected_status == StatusCode::OK { - let body = response.into_body().collect().await?.to_bytes(); - assert!(!body.is_empty()); + if expected_status == StatusCode::OK { + let body = response.into_body().collect().await?.to_bytes(); + assert!(!body.is_empty()); + } } + + Ok(()) } - Ok(()) - } + // enable tracing metrics. hyper/http has extensive logging, so these logs + // are very useful if failures happen in CI. + tracing_subscriber::fmt() + .with_max_level(LevelFilter::TRACE) + .with_test_writer() + .init(); - // enable tracing metrics. hyper/http has extensive logging, so these logs - // are very useful if failures happen in CI. - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_test_writer() - .init(); + for case in TEST_CASES { + get(case).await?; + } - for case in TEST_CASES { - get(case).await?; + Ok(()) } - - Ok(()) } diff --git a/bindings/rust/integration/tests/internet_kms_pq_client.rs b/bindings/rust/integration/tests/internet_kms_pq_client.rs index 7593cddd7c4..119df7057cd 100644 --- a/bindings/rust/integration/tests/internet_kms_pq_client.rs +++ b/bindings/rust/integration/tests/internet_kms_pq_client.rs @@ -1,89 +1,93 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use s2n_tls::{config::Config, security::Policy}; -use s2n_tls_tokio::TlsConnector; -use std::error::Error; -use tokio::net::TcpStream; -use tracing::level_filters::LevelFilter; +#[cfg(feature = "network-tests")] +mod pq_kms { + use s2n_tls::{config::Config, security::Policy}; + use s2n_tls_tokio::TlsConnector; + use tokio::net::TcpStream; + use tracing::level_filters::LevelFilter; -#[derive(Debug)] -struct TestCase { - s2n_security_policy: &'static str, - expected_cipher: &'static str, - expected_kem: Option<&'static str>, -} + #[derive(Debug)] + struct TestCase { + s2n_security_policy: &'static str, + expected_cipher: &'static str, + expected_kem: Option<&'static str>, + } -// When KMS moves to standardized ML-KEM, this test will fail. The test should -// then be updated with the new security policy that supports ML-KEM. -const TEST_CASES: &[TestCase] = &[ - // positive case: negotiates kyber - TestCase { - s2n_security_policy: "KMS-PQ-TLS-1-0-2020-07", - expected_cipher: "ECDHE-KYBER-RSA-AES256-GCM-SHA384", - expected_kem: Some("kyber512r3"), - }, - // negative cases: these policies support a variety of early kyber drafts. - // We want to confirm that non-supported kyber drafts successfully fall - // back to a full handshake. - TestCase { - s2n_security_policy: "KMS-PQ-TLS-1-0-2019-06", - expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", - expected_kem: None, - }, - TestCase { - s2n_security_policy: "PQ-SIKE-TEST-TLS-1-0-2019-11", - expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", - expected_kem: None, - }, - TestCase { - s2n_security_policy: "KMS-PQ-TLS-1-0-2020-02", - expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", - expected_kem: None, - }, - TestCase { - s2n_security_policy: "PQ-SIKE-TEST-TLS-1-0-2020-02", - expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", - expected_kem: None, - }, -]; + // When KMS moves to standardized ML-KEM, this test will fail. The test should + // then be updated with the new security policy that supports ML-KEM. + const TEST_CASES: &[TestCase] = &[ + // positive case: negotiates kyber + TestCase { + s2n_security_policy: "KMS-PQ-TLS-1-0-2020-07", + expected_cipher: "ECDHE-KYBER-RSA-AES256-GCM-SHA384", + expected_kem: Some("kyber512r3"), + }, + // negative cases: these policies support a variety of early kyber drafts. + // We want to confirm that non-supported kyber drafts successfully fall + // back to a full handshake. + TestCase { + s2n_security_policy: "KMS-PQ-TLS-1-0-2019-06", + expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", + expected_kem: None, + }, + TestCase { + s2n_security_policy: "PQ-SIKE-TEST-TLS-1-0-2019-11", + expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", + expected_kem: None, + }, + TestCase { + s2n_security_policy: "KMS-PQ-TLS-1-0-2020-02", + expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", + expected_kem: None, + }, + TestCase { + s2n_security_policy: "PQ-SIKE-TEST-TLS-1-0-2020-02", + expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", + expected_kem: None, + }, + ]; -/// Purpose: ensure that we remain compatible with existing pq AWS deployments. -/// -/// This test makes network calls over the public internet. -/// -/// KMS is has PQ support. Assert that we successfully negotiate PQ key exchange. -#[tokio::test] -async fn pq_kms_test() -> Result<(), Box> { - const DOMAIN: &str = "kms.us-east-1.amazonaws.com"; - const SOCKET_ADDR: (&str, u16) = (DOMAIN, 443); + /// Purpose: ensure that we remain compatible with existing pq AWS deployments. + /// + /// This test makes network calls over the public internet. + /// + /// KMS is has PQ support. Assert that we successfully negotiate PQ key exchange. + // Everything is included in the same block to prevent "unused" errors when + // "network-tests" aren't enabled. + #[tokio::test] + async fn pq_kms_test() -> Result<(), Box> { + const DOMAIN: &str = "kms.us-east-1.amazonaws.com"; + const SOCKET_ADDR: (&str, u16) = (DOMAIN, 443); - async fn test(test_case: &TestCase) -> Result<(), Box> { - tracing::info!("executing test case: {:#?}", test_case); + async fn test(test_case: &TestCase) -> Result<(), Box> { + tracing::info!("executing test case: {:#?}", test_case); - let mut config = Config::builder(); - config.set_security_policy(&Policy::from_version(test_case.s2n_security_policy)?)?; + let mut config = Config::builder(); + config.set_security_policy(&Policy::from_version(test_case.s2n_security_policy)?)?; - let client = TlsConnector::new(config.build()?); - // open the TCP stream - let stream = TcpStream::connect(SOCKET_ADDR).await?; - // complete the TLS handshake - let tls = client.connect(DOMAIN, stream).await?; + let client = TlsConnector::new(config.build()?); + // open the TCP stream + let stream = TcpStream::connect(SOCKET_ADDR).await?; + // complete the TLS handshake + let tls = client.connect(DOMAIN, stream).await?; - assert_eq!(tls.as_ref().cipher_suite()?, test_case.expected_cipher); - assert_eq!(tls.as_ref().kem_name()?, test_case.expected_kem); + assert_eq!(tls.as_ref().cipher_suite()?, test_case.expected_cipher); + assert_eq!(tls.as_ref().kem_name()?, test_case.expected_kem); - Ok(()) - } + Ok(()) + } - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_test_writer() - .init(); + tracing_subscriber::fmt() + .with_max_level(LevelFilter::TRACE) + .with_test_writer() + .init(); - for test_case in TEST_CASES { - test(test_case).await? - } + for test_case in TEST_CASES { + test(test_case).await? + } - Ok(()) + Ok(()) + } } From 880de7a965130f0cfed23e4f685489528981e973 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Sat, 9 Nov 2024 02:03:41 +0000 Subject: [PATCH 03/13] clean up dependencies --- bindings/rust/integration/Cargo.toml | 4 ++-- bindings/rust/s2n-tls-hyper/Cargo.toml | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bindings/rust/integration/Cargo.toml b/bindings/rust/integration/Cargo.toml index 4f1b6486e45..662e66a6618 100644 --- a/bindings/rust/integration/Cargo.toml +++ b/bindings/rust/integration/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" publish = false [features] -# network tests are useful but expensive in terms of duration and inherent flakiness. -# Accordingly they are gated behind this feature flag. +# Network tests are useful but relatively slow and inherently flaky. So they are +# behind this feature flag. network-tests = [] [dependencies] diff --git a/bindings/rust/s2n-tls-hyper/Cargo.toml b/bindings/rust/s2n-tls-hyper/Cargo.toml index c8cdb8a0ced..8c474a9e739 100644 --- a/bindings/rust/s2n-tls-hyper/Cargo.toml +++ b/bindings/rust/s2n-tls-hyper/Cargo.toml @@ -19,9 +19,6 @@ hyper = { version = "1" } hyper-util = { version = "0.1", features = ["client-legacy", "tokio", "http1"] } tower-service = { version = "0.3" } http = { version= "1" } -tracing-appender = "0.2.3" -tracing-subscriber = "0.3.18" -tracing = "0.1.40" [dev-dependencies] tokio = { version = "1", features = ["macros", "test-util"] } From c8a1545577de4921636c77fa5942d68871faaf13 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Sun, 10 Nov 2024 02:15:15 +0000 Subject: [PATCH 04/13] address ci failure * rustfmt --- bindings/rust/rust-toolchain | 2 +- bindings/rust/s2n-tls/src/connection.rs | 9 +++++---- bindings/rust/s2n-tls/src/testing/s2n_tls.rs | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bindings/rust/rust-toolchain b/bindings/rust/rust-toolchain index 2bf5ad0447d..af92bdd9f58 100644 --- a/bindings/rust/rust-toolchain +++ b/bindings/rust/rust-toolchain @@ -1 +1 @@ -stable +1.63.0 diff --git a/bindings/rust/s2n-tls/src/connection.rs b/bindings/rust/s2n-tls/src/connection.rs index 8d0c6b60a3c..56f701accd5 100644 --- a/bindings/rust/s2n-tls/src/connection.rs +++ b/bindings/rust/s2n-tls/src/connection.rs @@ -973,16 +973,17 @@ impl Connection { } pub fn kem_name(&self) -> Result, Error> { - let kem_name = unsafe {s2n_connection_get_kem_name(self.connection.as_ptr()).into_result()?}; - let name = unsafe { + let name_bytes = + unsafe { s2n_connection_get_kem_name(self.connection.as_ptr()).into_result()? }; + let name_str = unsafe { // SAFETY: The data is null terminated because it is declared as a C // string literal. // SAFETY: kem_name has a static lifetime because it lives on a const // struct s2n_kem with file scope. - const_str!(kem_name) + const_str!(name_bytes) }; - match name { + match name_str { Ok("NONE") => Ok(None), Ok(name) => Ok(Some(name)), Err(e) => Err(e), diff --git a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs index 87609322187..1d8d714c1ac 100644 --- a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs +++ b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs @@ -34,7 +34,7 @@ mod tests { let policy = Policy::from_version("20240501")?; let config = build_config(&policy)?; let mut pair = TestPair::from_config(&config); - + // before negotiation, kem_name is none assert!(pair.client.kem_name()?.is_none()); From 783b10cefe2ba2eb6024275fad6f8752a87fa6a0 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Mon, 11 Nov 2024 15:15:53 -0800 Subject: [PATCH 05/13] Update bindings/rust/integration/tests/internet_kms_pq_client.rs Co-authored-by: Sam Clark <3758302+goatgoose@users.noreply.github.com> --- bindings/rust/integration/tests/internet_kms_pq_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/rust/integration/tests/internet_kms_pq_client.rs b/bindings/rust/integration/tests/internet_kms_pq_client.rs index 119df7057cd..cda634bec54 100644 --- a/bindings/rust/integration/tests/internet_kms_pq_client.rs +++ b/bindings/rust/integration/tests/internet_kms_pq_client.rs @@ -53,7 +53,7 @@ mod pq_kms { /// /// This test makes network calls over the public internet. /// - /// KMS is has PQ support. Assert that we successfully negotiate PQ key exchange. + /// KMS has PQ support. Assert that we successfully negotiate PQ key exchange. // Everything is included in the same block to prevent "unused" errors when // "network-tests" aren't enabled. #[tokio::test] From f597fd50d5477b11aa8bc16c92883e5995cd5552 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Tue, 12 Nov 2024 00:31:21 +0000 Subject: [PATCH 06/13] address pr feedback * move akamai to more general tls client test * make logging handle concurrent tests * make kem_name return an Option<&str> --- .github/workflows/ci_rust.yml | 4 +- bindings/rust/integration/Cargo.toml | 3 +- ...ttp_client.rs => internet_https_client.rs} | 16 +--- .../tests/internet_kms_pq_client.rs | 93 ------------------- .../integration/tests/internet_tls_client.rs | 90 ++++++++++++++++++ bindings/rust/s2n-tls/src/connection.rs | 22 +++-- bindings/rust/s2n-tls/src/testing/s2n_tls.rs | 6 +- 7 files changed, 114 insertions(+), 120 deletions(-) rename bindings/rust/integration/tests/{internet_http_client.rs => internet_https_client.rs} (87%) delete mode 100644 bindings/rust/integration/tests/internet_kms_pq_client.rs create mode 100644 bindings/rust/integration/tests/internet_tls_client.rs diff --git a/.github/workflows/ci_rust.yml b/.github/workflows/ci_rust.yml index 2e9e8c692be..2ced1e01d7e 100644 --- a/.github/workflows/ci_rust.yml +++ b/.github/workflows/ci_rust.yml @@ -58,9 +58,9 @@ jobs: working-directory: ${{env.ROOT_PATH}} run: cargo test --features unstable-renegotiate - - name: Network Tests + - name: Network-enabled integration tests working-directory: ${{env.ROOT_PATH}}/integration - run: cargo test --features network-tests + run: RUST_LOG=TRACE cargo test --features network-tests - name: Test external build # if this test is failing, make sure that api headers are appropriately diff --git a/bindings/rust/integration/Cargo.toml b/bindings/rust/integration/Cargo.toml index 662e66a6618..b1045db569d 100644 --- a/bindings/rust/integration/Cargo.toml +++ b/bindings/rust/integration/Cargo.toml @@ -34,8 +34,7 @@ tokio = { version = "1", features = ["macros", "test-util"] } tracing = "0.1" tracing-subscriber = "0.3" -tracing-appender = "0.2" -test-log = "0.2" +test-log = { version = "0.2", features = ["log", "trace"]} http = "1.1" http-body-util = "0.1" diff --git a/bindings/rust/integration/tests/internet_http_client.rs b/bindings/rust/integration/tests/internet_https_client.rs similarity index 87% rename from bindings/rust/integration/tests/internet_http_client.rs rename to bindings/rust/integration/tests/internet_https_client.rs index e664a544e6b..420e751aa97 100644 --- a/bindings/rust/integration/tests/internet_http_client.rs +++ b/bindings/rust/integration/tests/internet_https_client.rs @@ -6,7 +6,7 @@ /// This test uses s2n-tls-hyper to make http requests over a TLS connection to /// a number of well known http sites. #[cfg(feature = "network-tests")] -mod http_get { +mod https_client { use bytes::Bytes; use http::{StatusCode, Uri}; use http_body_util::{BodyExt, Empty}; @@ -14,7 +14,6 @@ mod http_get { use s2n_tls::{config::Config, security}; use s2n_tls_hyper::connector::HttpsConnector; use std::{error::Error, str::FromStr}; - use tracing_subscriber::filter::LevelFilter; #[derive(Debug)] struct TestCase { @@ -32,10 +31,6 @@ mod http_get { } const TEST_CASES: &[TestCase] = &[ - // Akamai hangs indefinitely. This is also observed with curl and chrome - // https://github.com/aws/s2n-tls/issues/4883 - // TestCase::new("https://www.akamai.com/", 200), - // this is a link to the s2n-tls unit test coverage report, hosted on cloudfront TestCase::new("https://dx1inn44oyl7n.cloudfront.net/main/index.html", 200), // this is a link to a non-existent S3 item @@ -63,7 +58,7 @@ mod http_get { TestCase::new("https://www.f5.com", 403), ]; - #[tokio::test] + #[test_log::test(tokio::test)] async fn http_get_test() -> Result<(), Box> { async fn get(test_case: &TestCase) -> Result<(), Box> { for p in [security::DEFAULT, security::DEFAULT_TLS13] { @@ -91,13 +86,6 @@ mod http_get { Ok(()) } - // enable tracing metrics. hyper/http has extensive logging, so these logs - // are very useful if failures happen in CI. - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_test_writer() - .init(); - for case in TEST_CASES { get(case).await?; } diff --git a/bindings/rust/integration/tests/internet_kms_pq_client.rs b/bindings/rust/integration/tests/internet_kms_pq_client.rs deleted file mode 100644 index cda634bec54..00000000000 --- a/bindings/rust/integration/tests/internet_kms_pq_client.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(feature = "network-tests")] -mod pq_kms { - use s2n_tls::{config::Config, security::Policy}; - use s2n_tls_tokio::TlsConnector; - use tokio::net::TcpStream; - use tracing::level_filters::LevelFilter; - - #[derive(Debug)] - struct TestCase { - s2n_security_policy: &'static str, - expected_cipher: &'static str, - expected_kem: Option<&'static str>, - } - - // When KMS moves to standardized ML-KEM, this test will fail. The test should - // then be updated with the new security policy that supports ML-KEM. - const TEST_CASES: &[TestCase] = &[ - // positive case: negotiates kyber - TestCase { - s2n_security_policy: "KMS-PQ-TLS-1-0-2020-07", - expected_cipher: "ECDHE-KYBER-RSA-AES256-GCM-SHA384", - expected_kem: Some("kyber512r3"), - }, - // negative cases: these policies support a variety of early kyber drafts. - // We want to confirm that non-supported kyber drafts successfully fall - // back to a full handshake. - TestCase { - s2n_security_policy: "KMS-PQ-TLS-1-0-2019-06", - expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", - expected_kem: None, - }, - TestCase { - s2n_security_policy: "PQ-SIKE-TEST-TLS-1-0-2019-11", - expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", - expected_kem: None, - }, - TestCase { - s2n_security_policy: "KMS-PQ-TLS-1-0-2020-02", - expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", - expected_kem: None, - }, - TestCase { - s2n_security_policy: "PQ-SIKE-TEST-TLS-1-0-2020-02", - expected_cipher: "ECDHE-RSA-AES256-GCM-SHA384", - expected_kem: None, - }, - ]; - - /// Purpose: ensure that we remain compatible with existing pq AWS deployments. - /// - /// This test makes network calls over the public internet. - /// - /// KMS has PQ support. Assert that we successfully negotiate PQ key exchange. - // Everything is included in the same block to prevent "unused" errors when - // "network-tests" aren't enabled. - #[tokio::test] - async fn pq_kms_test() -> Result<(), Box> { - const DOMAIN: &str = "kms.us-east-1.amazonaws.com"; - const SOCKET_ADDR: (&str, u16) = (DOMAIN, 443); - - async fn test(test_case: &TestCase) -> Result<(), Box> { - tracing::info!("executing test case: {:#?}", test_case); - - let mut config = Config::builder(); - config.set_security_policy(&Policy::from_version(test_case.s2n_security_policy)?)?; - - let client = TlsConnector::new(config.build()?); - // open the TCP stream - let stream = TcpStream::connect(SOCKET_ADDR).await?; - // complete the TLS handshake - let tls = client.connect(DOMAIN, stream).await?; - - assert_eq!(tls.as_ref().cipher_suite()?, test_case.expected_cipher); - assert_eq!(tls.as_ref().kem_name()?, test_case.expected_kem); - - Ok(()) - } - - tracing_subscriber::fmt() - .with_max_level(LevelFilter::TRACE) - .with_test_writer() - .init(); - - for test_case in TEST_CASES { - test(test_case).await? - } - - Ok(()) - } -} diff --git a/bindings/rust/integration/tests/internet_tls_client.rs b/bindings/rust/integration/tests/internet_tls_client.rs new file mode 100644 index 00000000000..481f0ff032f --- /dev/null +++ b/bindings/rust/integration/tests/internet_tls_client.rs @@ -0,0 +1,90 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(feature = "network-tests")] +mod tls_client { + use s2n_tls::{config::Config, enums::Version, security::Policy}; + use s2n_tls_tokio::{TlsConnector, TlsStream}; + use tokio::net::TcpStream; + + /// Perform a TLS handshake with port 443 of `domain`. + /// + /// * `domain`: The domain to perform the handshake with + /// * `security_policy`: The security policy to set on the handshaking client. + /// + /// Returns an open `TlsStream` if the handshake was successful, otherwise an + /// `Err``. + async fn handshake_with_domain(domain: &str, security_policy: &str) -> Result, Box> { + tracing::info!("querying {domain} with {security_policy}"); + const PORT: u16 = 443; + + let mut config = Config::builder(); + config.set_security_policy(&Policy::from_version(security_policy)?)?; + + let client = TlsConnector::new(config.build()?); + // open the TCP stream + let stream = TcpStream::connect((domain, PORT)).await?; + // complete the TLS handshake + Ok(client.connect(domain, stream).await?) + } + + /// Purpose: ensure that we remain compatible with existing pq AWS deployments. + /// + /// KMS is has PQ support. Assert that we successfully negotiate PQ key exchange. + mod kms { + use crate::tls_client::handshake_with_domain; + + const DOMAIN: &str = "kms.us-east-1.amazonaws.com"; + + // confirm that we successfully negotiate a supported PQ key exchange. + // + // Note: In the future KMS will deprecate kyber_r3 in favor of ML-KEM. + // At that point this test should be updated with a security policy that + // supports ML-KEM. + #[test_log::test(tokio::test)] + async fn pq_handshake() -> Result<(), Box> { + let tls = handshake_with_domain(DOMAIN, "KMS-PQ-TLS-1-0-2020-07").await?; + + assert_eq!(tls.as_ref().cipher_suite()?, "ECDHE-KYBER-RSA-AES256-GCM-SHA384"); + assert_eq!(tls.as_ref().kem_name(), Some("kyber512r3")); + + Ok(()) + } + + // We want to confirm that non-supported kyber drafts successfully fall + // back to a full handshake. + #[test_log::test(tokio::test)] + async fn early_draft_falls_back_to_classical() -> Result<(), Box> { + const EARLY_DRAFT_PQ_POLICIES: &[&str] = &[ + "KMS-PQ-TLS-1-0-2019-06", + "PQ-SIKE-TEST-TLS-1-0-2019-11", + "KMS-PQ-TLS-1-0-2020-02", + "PQ-SIKE-TEST-TLS-1-0-2020-02", + ]; + + for security_policy in EARLY_DRAFT_PQ_POLICIES { + let tls = handshake_with_domain(DOMAIN, security_policy).await?; + + assert_eq!(tls.as_ref().cipher_suite()?, "ECDHE-RSA-AES256-GCM-SHA384"); + assert_eq!(tls.as_ref().kem_name(), None); + } + Ok(()) + } + } + + // This should be an http test in internet_https_client.rs but Akamai + // http requests hang indefinitely. This behavior is also observed with curl + // and chrome. https://github.com/aws/s2n-tls/issues/4883 + #[test_log::test(tokio::test)] + async fn akamai() -> Result<(), Box> { + const DOMAIN: &str = "www.akamai.com"; + + let tls12 = handshake_with_domain(DOMAIN, "default").await?; + assert_eq!(tls12.as_ref().actual_protocol_version()?, Version::TLS12); + + let tls12 = handshake_with_domain(DOMAIN, "default_tls13").await?; + assert_eq!(tls12.as_ref().actual_protocol_version()?, Version::TLS13); + + Ok(()) + } +} diff --git a/bindings/rust/s2n-tls/src/connection.rs b/bindings/rust/s2n-tls/src/connection.rs index 56f701accd5..d8e8f14f460 100644 --- a/bindings/rust/s2n-tls/src/connection.rs +++ b/bindings/rust/s2n-tls/src/connection.rs @@ -972,9 +972,15 @@ impl Connection { } } - pub fn kem_name(&self) -> Result, Error> { - let name_bytes = - unsafe { s2n_connection_get_kem_name(self.connection.as_ptr()).into_result()? }; + pub fn kem_name(&self) -> Option<&str> { + let name_bytes = { + let name = unsafe { s2n_connection_get_kem_name(self.connection.as_ptr()) }; + if name.is_null() { + return None; + } + name + }; + let name_str = unsafe { // SAFETY: The data is null terminated because it is declared as a C // string literal. @@ -984,9 +990,13 @@ impl Connection { }; match name_str { - Ok("NONE") => Ok(None), - Ok(name) => Ok(Some(name)), - Err(e) => Err(e), + Ok("NONE") => None, + Ok(name) => Some(name), + Err(_) => { + // Unreachable: This would indicate a non-utf-8 string literal in + // the s2n-tls C codebase. + None + }, } } diff --git a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs index 1d8d714c1ac..1d5d920c384 100644 --- a/bindings/rust/s2n-tls/src/testing/s2n_tls.rs +++ b/bindings/rust/s2n-tls/src/testing/s2n_tls.rs @@ -36,10 +36,10 @@ mod tests { let mut pair = TestPair::from_config(&config); // before negotiation, kem_name is none - assert!(pair.client.kem_name()?.is_none()); + assert!(pair.client.kem_name().is_none()); pair.handshake().unwrap(); - assert!(pair.client.kem_name()?.is_none()); + assert!(pair.client.kem_name().is_none()); } // PQ is supported @@ -49,7 +49,7 @@ mod tests { let mut pair = TestPair::from_config(&config); pair.handshake().unwrap(); - assert_eq!(pair.client.kem_name()?, Some("kyber512r3")); + assert_eq!(pair.client.kem_name(), Some("kyber512r3")); } Ok(()) From 88edcb2e7e465fc3606195d25f8464a165277f76 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Tue, 12 Nov 2024 00:35:25 +0000 Subject: [PATCH 07/13] address ci failure * cargo fmt --- .../rust/integration/tests/internet_tls_client.rs | 14 ++++++++++---- bindings/rust/s2n-tls/src/connection.rs | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/bindings/rust/integration/tests/internet_tls_client.rs b/bindings/rust/integration/tests/internet_tls_client.rs index 481f0ff032f..7b7504a4b15 100644 --- a/bindings/rust/integration/tests/internet_tls_client.rs +++ b/bindings/rust/integration/tests/internet_tls_client.rs @@ -8,13 +8,16 @@ mod tls_client { use tokio::net::TcpStream; /// Perform a TLS handshake with port 443 of `domain`. - /// + /// /// * `domain`: The domain to perform the handshake with /// * `security_policy`: The security policy to set on the handshaking client. - /// + /// /// Returns an open `TlsStream` if the handshake was successful, otherwise an /// `Err``. - async fn handshake_with_domain(domain: &str, security_policy: &str) -> Result, Box> { + async fn handshake_with_domain( + domain: &str, + security_policy: &str, + ) -> Result, Box> { tracing::info!("querying {domain} with {security_policy}"); const PORT: u16 = 443; @@ -45,7 +48,10 @@ mod tls_client { async fn pq_handshake() -> Result<(), Box> { let tls = handshake_with_domain(DOMAIN, "KMS-PQ-TLS-1-0-2020-07").await?; - assert_eq!(tls.as_ref().cipher_suite()?, "ECDHE-KYBER-RSA-AES256-GCM-SHA384"); + assert_eq!( + tls.as_ref().cipher_suite()?, + "ECDHE-KYBER-RSA-AES256-GCM-SHA384" + ); assert_eq!(tls.as_ref().kem_name(), Some("kyber512r3")); Ok(()) diff --git a/bindings/rust/s2n-tls/src/connection.rs b/bindings/rust/s2n-tls/src/connection.rs index d8e8f14f460..e65225cf111 100644 --- a/bindings/rust/s2n-tls/src/connection.rs +++ b/bindings/rust/s2n-tls/src/connection.rs @@ -996,7 +996,7 @@ impl Connection { // Unreachable: This would indicate a non-utf-8 string literal in // the s2n-tls C codebase. None - }, + } } } From b5db489bfc325b05ff76cc396db678a1d20c0bf2 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Tue, 12 Nov 2024 19:01:08 +0000 Subject: [PATCH 08/13] add well known endpoints stub --- .../test_well_known_endpoints.py | 135 ++---------------- 1 file changed, 9 insertions(+), 126 deletions(-) diff --git a/tests/integrationv2/test_well_known_endpoints.py b/tests/integrationv2/test_well_known_endpoints.py index 7bce5c2248a..c5b63432650 100644 --- a/tests/integrationv2/test_well_known_endpoints.py +++ b/tests/integrationv2/test_well_known_endpoints.py @@ -1,130 +1,13 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -import pytest -from constants import TRUST_STORE_BUNDLE, TRUST_STORE_TRUSTED_BUNDLE -from configuration import PROTOCOLS -from common import ProviderOptions, Ciphers, pq_enabled -from fixtures import managed_process # lgtm [py/unused-import] -from global_flags import get_flag, S2N_FIPS_MODE -from providers import Provider, S2N -from test_pq_handshake import PQ_ENABLED_FLAG -from utils import invalid_test_parameters, get_parameter_name, to_bytes +''' +This is a stub test, which allows the existing CI to continue passing while +https://github.com/aws/s2n-tls/pull/4884 is merged in. - -ENDPOINTS = [ - "www.akamai.com", - "www.amazon.com", - "kms.us-east-1.amazonaws.com", - "s3.us-west-2.amazonaws.com", - "www.apple.com", - "www.att.com", - # "www.badssl.com", - # "mozilla-intermediate.badssl.com", - # "mozilla-modern.badssl.com", - # "rsa2048.badssl.com", - # "rsa4096.badssl.com", - # "sha256.badssl.com", - # "sha384.badssl.com", - # "sha512.badssl.com", - # "tls-v1-0.badssl.com", - # "tls-v1-1.badssl.com", - # "tls-v1-2.badssl.com", - "www.cloudflare.com", - "www.ebay.com", - "www.f5.com", - "www.facebook.com", - "www.google.com", - "www.github.com", - "www.ibm.com", - "www.microsoft.com", - # https://github.com/aws/s2n-tls/issues/4879 - # "www.mozilla.org", - "www.netflix.com", - "www.openssl.org", - "www.samsung.com", - "www.t-mobile.com", - "www.twitter.com", - "www.verizon.com", - "www.wikipedia.org", - "www.yahoo.com", - "www.youtube.com", -] - -CIPHERS = [ - None, # `None` will default to the appropriate `test_all` cipher preference in the S2N client provider - Ciphers.KMS_PQ_TLS_1_0_2019_06, - Ciphers.PQ_SIKE_TEST_TLS_1_0_2019_11, - Ciphers.KMS_PQ_TLS_1_0_2020_07, - Ciphers.KMS_PQ_TLS_1_0_2020_02, - Ciphers.PQ_SIKE_TEST_TLS_1_0_2020_02 -] - - -if pq_enabled(): - EXPECTED_RESULTS = { - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2019_06): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.PQ_SIKE_TEST_TLS_1_0_2019_11): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2020_07): - {"cipher": "ECDHE-KYBER-RSA-AES256-GCM-SHA384", "kem": "kyber512r3"}, - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2020_02): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.PQ_SIKE_TEST_TLS_1_0_2020_02): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - } -else: - EXPECTED_RESULTS = { - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2019_06): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.PQ_SIKE_TEST_TLS_1_0_2019_11): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2020_07): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.KMS_PQ_TLS_1_0_2020_02): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - ("kms.us-east-1.amazonaws.com", Ciphers.PQ_SIKE_TEST_TLS_1_0_2020_02): - {"cipher": "ECDHE-RSA-AES256-GCM-SHA384", "kem": None}, - } - - -@pytest.mark.uncollect_if(func=invalid_test_parameters) -@pytest.mark.parametrize("protocol", PROTOCOLS, ids=get_parameter_name) -@pytest.mark.parametrize("endpoint", ENDPOINTS, ids=get_parameter_name) -@pytest.mark.parametrize("provider", [S2N], ids=get_parameter_name) -@pytest.mark.parametrize("cipher", CIPHERS, ids=get_parameter_name) -@pytest.mark.flaky(reruns=5, reruns_delay=4) -def test_well_known_endpoints(managed_process, protocol, endpoint, provider, cipher): - port = "443" - - client_options = ProviderOptions( - mode=Provider.ClientMode, - host=endpoint, - port=port, - insecure=False, - trust_store=TRUST_STORE_BUNDLE, - protocol=protocol, - cipher=cipher) - - if get_flag(S2N_FIPS_MODE) is True: - client_options.trust_store = TRUST_STORE_TRUSTED_BUNDLE - - # expect_stderr=True because S2N sometimes receives OCSP responses: - # https://github.com/aws/s2n-tls/blob/14ed186a13c1ffae7fbb036ed5d2849ce7c17403/bin/echo.c#L180-L184 - client = managed_process(provider, client_options, - timeout=5, expect_stderr=True) - - expected_result = EXPECTED_RESULTS.get((endpoint, cipher), None) - - for results in client.get_results(): - results.assert_success() - - if expected_result is not None: - assert to_bytes(expected_result['cipher']) in results.stdout - if expected_result['kem']: - assert to_bytes(expected_result['kem']) in results.stdout - assert to_bytes(PQ_ENABLED_FLAG) in results.stdout - else: - assert to_bytes('KEM:') not in results.stdout - assert to_bytes(PQ_ENABLED_FLAG) not in results.stdout +Once the PR is merged, the Codebuild spec for NixIntegV2Batch will be updated +to remove the "well_known_endpoints" argument (manual process) and then this test +can be fully removed (PR). +''' +def test_well_known_endpoints(): + assert 1 == 1 From 21919aef74f9c48b8d5884999a1da40204a0a7be Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Tue, 12 Nov 2024 19:41:57 +0000 Subject: [PATCH 09/13] address ci failures * pin test-log for msrv issues * pep8 python code --- bindings/rust/integration/Cargo.toml | 4 +++- tests/integrationv2/test_well_known_endpoints.py | 16 ++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bindings/rust/integration/Cargo.toml b/bindings/rust/integration/Cargo.toml index 8c2e22eb9fc..5b7ce5f5a8b 100644 --- a/bindings/rust/integration/Cargo.toml +++ b/bindings/rust/integration/Cargo.toml @@ -21,7 +21,9 @@ tokio = { version = "1", features = ["macros", "test-util"] } tracing = "0.1" tracing-subscriber = "0.3" -test-log = { version = "0.2", features = ["log", "trace"]} +# TODO: Unpin when s2n-tls MSRV > 1.71 +test-log = { version = "=0.2.14", default-features = false, features = ["trace"]} +test-log-macros = "=0.2.14" http = "1.1" http-body-util = "0.1" diff --git a/tests/integrationv2/test_well_known_endpoints.py b/tests/integrationv2/test_well_known_endpoints.py index c5b63432650..6d5d7e9d5a3 100644 --- a/tests/integrationv2/test_well_known_endpoints.py +++ b/tests/integrationv2/test_well_known_endpoints.py @@ -1,13 +1,13 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: Apache-2.0 -''' -This is a stub test, which allows the existing CI to continue passing while -https://github.com/aws/s2n-tls/pull/4884 is merged in. - -Once the PR is merged, the Codebuild spec for NixIntegV2Batch will be updated -to remove the "well_known_endpoints" argument (manual process) and then this test -can be fully removed (PR). -''' def test_well_known_endpoints(): + ''' + This is a stub test, which allows the existing CI to continue passing while + https://github.com/aws/s2n-tls/pull/4884 is merged in. + + Once the PR is merged, the Codebuild spec for NixIntegV2Batch will be updated + to remove the "well_known_endpoints" argument (manual process) and then this test + can be fully removed (PR). + ''' assert 1 == 1 From 5e6faa030b3b6c905bfed13a7a29466102c434af Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Tue, 12 Nov 2024 16:27:52 -0800 Subject: [PATCH 10/13] Update bindings/rust/integration/tests/internet_tls_client.rs Co-authored-by: Lindsay Stewart --- bindings/rust/integration/tests/internet_tls_client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/rust/integration/tests/internet_tls_client.rs b/bindings/rust/integration/tests/internet_tls_client.rs index 7b7504a4b15..979678f85a1 100644 --- a/bindings/rust/integration/tests/internet_tls_client.rs +++ b/bindings/rust/integration/tests/internet_tls_client.rs @@ -33,7 +33,7 @@ mod tls_client { /// Purpose: ensure that we remain compatible with existing pq AWS deployments. /// - /// KMS is has PQ support. Assert that we successfully negotiate PQ key exchange. + /// KMS has PQ support. Assert that we successfully negotiate PQ key exchange. mod kms { use crate::tls_client::handshake_with_domain; From d351c8f8604ab01f88864bd19f92ca7b42eec7d0 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Wed, 13 Nov 2024 09:06:47 +0000 Subject: [PATCH 11/13] address pr feedback * make tls client more general * add PQ feature --- bindings/rust/integration/Cargo.toml | 11 ++++- bindings/rust/integration/src/lib.rs | 25 ++++++++++ .../tests/internet_https_client.rs | 48 +++++++++++-------- .../integration/tests/internet_tls_client.rs | 22 +++++---- 4 files changed, 74 insertions(+), 32 deletions(-) create mode 100644 bindings/rust/integration/src/lib.rs diff --git a/bindings/rust/integration/Cargo.toml b/bindings/rust/integration/Cargo.toml index 5b7ce5f5a8b..8c1f75470a0 100644 --- a/bindings/rust/integration/Cargo.toml +++ b/bindings/rust/integration/Cargo.toml @@ -6,12 +6,18 @@ edition = "2021" publish = false [features] +default = ["pq"] + # Network tests are useful but relatively slow and inherently flaky. So they are # behind this feature flag. network-tests = [] +# Not all libcryptos support PQ capabilities. Tests relying on PQ functionality +# can be disabled by turning off this feature. +pq = [] + [dependencies] -s2n-tls = { path = "../s2n-tls"} +s2n-tls = { path = "../s2n-tls", features = ["unstable-testing"]} s2n-tls-hyper = { path = "../s2n-tls-hyper" } s2n-tls-tokio = { path = "../s2n-tls-tokio" } s2n-tls-sys = { path = "../s2n-tls-sys" } @@ -21,11 +27,12 @@ tokio = { version = "1", features = ["macros", "test-util"] } tracing = "0.1" tracing-subscriber = "0.3" -# TODO: Unpin when s2n-tls MSRV > 1.71 +# TODO: Unpin when s2n-tls MSRV >= 1.71, https://github.com/aws/s2n-tls/issues/4893 test-log = { version = "=0.2.14", default-features = false, features = ["trace"]} test-log-macros = "=0.2.14" http = "1.1" http-body-util = "0.1" bytes = "1.8" +hyper = "1.5" hyper-util = "0.1" diff --git a/bindings/rust/integration/src/lib.rs b/bindings/rust/integration/src/lib.rs new file mode 100644 index 00000000000..fd8b3f7d499 --- /dev/null +++ b/bindings/rust/integration/src/lib.rs @@ -0,0 +1,25 @@ +#[cfg(test)] +mod tests { + use s2n_tls::{ + security::Policy, + testing::{self, TestPair}, + }; + + /// This test provides a helpful debug message if the PQ feature is incorrectly + /// configured. + #[cfg(feature = "pq")] + #[test] + fn pq_sanity_check() -> Result<(), Box> { + let config = testing::build_config(&Policy::from_version("KMS-PQ-TLS-1-0-2020-07")?)?; + let mut pair = TestPair::from_config(&config); + pair.handshake()?; + + if pair.client.kem_name().is_none() { + panic!( + "PQ tests are enabled, but PQ functionality is unavailable. \ + Are you sure that the libcrypto supports PQ?" + ); + } + Ok(()) + } +} diff --git a/bindings/rust/integration/tests/internet_https_client.rs b/bindings/rust/integration/tests/internet_https_client.rs index 420e751aa97..8c77ae0c982 100644 --- a/bindings/rust/integration/tests/internet_https_client.rs +++ b/bindings/rust/integration/tests/internet_https_client.rs @@ -8,12 +8,16 @@ #[cfg(feature = "network-tests")] mod https_client { use bytes::Bytes; - use http::{StatusCode, Uri}; + use http::{Response, StatusCode, Uri}; use http_body_util::{BodyExt, Empty}; + use hyper::body::Incoming; use hyper_util::{client::legacy::Client, rt::TokioExecutor}; - use s2n_tls::{config::Config, security}; + use s2n_tls::{ + config::Config, + security::{self, Policy}, + }; use s2n_tls_hyper::connector::HttpsConnector; - use std::{error::Error, str::FromStr}; + use std::str::FromStr; #[derive(Debug)] struct TestCase { @@ -58,22 +62,30 @@ mod https_client { TestCase::new("https://www.f5.com", 403), ]; - #[test_log::test(tokio::test)] - async fn http_get_test() -> Result<(), Box> { - async fn get(test_case: &TestCase) -> Result<(), Box> { - for p in [security::DEFAULT, security::DEFAULT_TLS13] { - tracing::info!("executing test case {:#?} with {:?}", test_case, p); + /// perform an HTTP GET request against `uri` using an s2n-tls config with + /// `security_policy`. + async fn https_get( + uri: &str, + security_policy: &Policy, + ) -> Result, hyper_util::client::legacy::Error> { + let mut config = Config::builder(); + config.set_security_policy(security_policy).unwrap(); - let mut config = Config::builder(); - config.set_security_policy(&p)?; + let connector = HttpsConnector::new(config.build().unwrap()); + let client: Client<_, Empty> = + Client::builder(TokioExecutor::new()).build(connector); - let connector = HttpsConnector::new(config.build()?); - let client: Client<_, Empty> = - Client::builder(TokioExecutor::new()).build(connector); + let uri = Uri::from_str(uri).unwrap(); + client.get(uri).await + } - let uri = Uri::from_str(test_case.query_target)?; - let response = client.get(uri).await?; + #[test_log::test(tokio::test)] + async fn http_get_test() -> Result<(), Box> { + for test_case in TEST_CASES { + for policy in [security::DEFAULT, security::DEFAULT_TLS13] { + tracing::info!("executing test case {:#?} with {:?}", test_case, policy); + let response = https_get(test_case.query_target, &policy).await?; let expected_status = StatusCode::from_u16(test_case.expected_status_code).unwrap(); assert_eq!(response.status(), expected_status); @@ -82,12 +94,6 @@ mod https_client { assert!(!body.is_empty()); } } - - Ok(()) - } - - for case in TEST_CASES { - get(case).await?; } Ok(()) diff --git a/bindings/rust/integration/tests/internet_tls_client.rs b/bindings/rust/integration/tests/internet_tls_client.rs index 979678f85a1..ae923f3e1e7 100644 --- a/bindings/rust/integration/tests/internet_tls_client.rs +++ b/bindings/rust/integration/tests/internet_tls_client.rs @@ -78,18 +78,22 @@ mod tls_client { } } - // This should be an http test in internet_https_client.rs but Akamai - // http requests hang indefinitely. This behavior is also observed with curl - // and chrome. https://github.com/aws/s2n-tls/issues/4883 #[test_log::test(tokio::test)] - async fn akamai() -> Result<(), Box> { - const DOMAIN: &str = "www.akamai.com"; + async fn tls_client() -> Result<(), Box> { + // The akamai request should be in internet_https_client.rs but Akamai + // http requests hang indefinitely. This behavior is also observed with + // curl and chrome. https://github.com/aws/s2n-tls/issues/4883 + const DOMAINS: &[&str] = &["www.akamai.com"]; - let tls12 = handshake_with_domain(DOMAIN, "default").await?; - assert_eq!(tls12.as_ref().actual_protocol_version()?, Version::TLS12); + for domain in DOMAINS { + tracing::info!("querying {domain}"); - let tls12 = handshake_with_domain(DOMAIN, "default_tls13").await?; - assert_eq!(tls12.as_ref().actual_protocol_version()?, Version::TLS13); + let tls12 = handshake_with_domain(domain, "default").await?; + assert_eq!(tls12.as_ref().actual_protocol_version()?, Version::TLS12); + + let tls12 = handshake_with_domain(domain, "default_tls13").await?; + assert_eq!(tls12.as_ref().actual_protocol_version()?, Version::TLS13); + } Ok(()) } From 45ba22b2d4d6154d74b10ea4d359d1d0018c871a Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Wed, 13 Nov 2024 21:56:21 +0000 Subject: [PATCH 12/13] address ci failure * add copyright header --- bindings/rust/integration/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/rust/integration/src/lib.rs b/bindings/rust/integration/src/lib.rs index fd8b3f7d499..c041790d767 100644 --- a/bindings/rust/integration/src/lib.rs +++ b/bindings/rust/integration/src/lib.rs @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + #[cfg(test)] mod tests { use s2n_tls::{ From 74f06a9301a7e6ae28a18c685ab5adc7b1928b66 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Wed, 13 Nov 2024 22:28:24 +0000 Subject: [PATCH 13/13] address pr feedback * move tests to specific network module --- bindings/rust/integration/src/lib.rs | 3 + .../integration/src/network/https_client.rs | 97 +++++++++++++++++ bindings/rust/integration/src/network/mod.rs | 5 + .../integration/src/network/tls_client.rs | 95 ++++++++++++++++ .../tests/internet_https_client.rs | 101 ------------------ .../integration/tests/internet_tls_client.rs | 100 ----------------- 6 files changed, 200 insertions(+), 201 deletions(-) create mode 100644 bindings/rust/integration/src/network/https_client.rs create mode 100644 bindings/rust/integration/src/network/mod.rs create mode 100644 bindings/rust/integration/src/network/tls_client.rs delete mode 100644 bindings/rust/integration/tests/internet_https_client.rs delete mode 100644 bindings/rust/integration/tests/internet_tls_client.rs diff --git a/bindings/rust/integration/src/lib.rs b/bindings/rust/integration/src/lib.rs index c041790d767..612f362e2dd 100644 --- a/bindings/rust/integration/src/lib.rs +++ b/bindings/rust/integration/src/lib.rs @@ -1,6 +1,9 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +#[cfg(all(feature = "network-tests", test))] +mod network; + #[cfg(test)] mod tests { use s2n_tls::{ diff --git a/bindings/rust/integration/src/network/https_client.rs b/bindings/rust/integration/src/network/https_client.rs new file mode 100644 index 00000000000..84c90e5a10a --- /dev/null +++ b/bindings/rust/integration/src/network/https_client.rs @@ -0,0 +1,97 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use bytes::Bytes; +use http::{Response, StatusCode, Uri}; +use http_body_util::{BodyExt, Empty}; +use hyper::body::Incoming; +use hyper_util::{client::legacy::Client, rt::TokioExecutor}; +use s2n_tls::{ + config::Config, + security::{self, Policy}, +}; +use s2n_tls_hyper::connector::HttpsConnector; +use std::str::FromStr; + +#[derive(Debug)] +struct TestCase { + pub query_target: &'static str, + pub expected_status_code: u16, +} + +impl TestCase { + const fn new(domain: &'static str, expected_status_code: u16) -> Self { + TestCase { + query_target: domain, + expected_status_code, + } + } +} + +const TEST_CASES: &[TestCase] = &[ + // this is a link to the s2n-tls unit test coverage report, hosted on cloudfront + TestCase::new("https://dx1inn44oyl7n.cloudfront.net/main/index.html", 200), + // this is a link to a non-existent S3 item + TestCase::new("https://notmybucket.s3.amazonaws.com/folder/afile.jpg", 403), + TestCase::new("https://www.amazon.com", 200), + TestCase::new("https://www.apple.com", 200), + TestCase::new("https://www.att.com", 200), + TestCase::new("https://www.cloudflare.com", 200), + TestCase::new("https://www.ebay.com", 200), + TestCase::new("https://www.google.com", 200), + TestCase::new("https://www.mozilla.org", 200), + TestCase::new("https://www.netflix.com", 200), + TestCase::new("https://www.openssl.org", 200), + TestCase::new("https://www.t-mobile.com", 200), + TestCase::new("https://www.verizon.com", 200), + TestCase::new("https://www.wikipedia.org", 200), + TestCase::new("https://www.yahoo.com", 200), + TestCase::new("https://www.youtube.com", 200), + TestCase::new("https://www.github.com", 301), + TestCase::new("https://www.samsung.com", 301), + TestCase::new("https://www.twitter.com", 301), + TestCase::new("https://www.facebook.com", 302), + TestCase::new("https://www.microsoft.com", 302), + TestCase::new("https://www.ibm.com", 303), + TestCase::new("https://www.f5.com", 403), +]; + +/// perform an HTTP GET request against `uri` using an s2n-tls config with +/// `security_policy`. +async fn https_get( + uri: &str, + security_policy: &Policy, +) -> Result, hyper_util::client::legacy::Error> { + let mut config = Config::builder(); + config.set_security_policy(security_policy).unwrap(); + + let connector = HttpsConnector::new(config.build().unwrap()); + let client: Client<_, Empty> = Client::builder(TokioExecutor::new()).build(connector); + + let uri = Uri::from_str(uri).unwrap(); + client.get(uri).await +} + +/// Ensure that s2n-tls is compatible with other http/TLS implementations. +/// +/// This test uses s2n-tls-hyper to make http requests over a TLS connection to +/// a number of well known http sites. +#[test_log::test(tokio::test)] +async fn http_get_test() -> Result<(), Box> { + for test_case in TEST_CASES { + for policy in [security::DEFAULT, security::DEFAULT_TLS13] { + tracing::info!("executing test case {:#?} with {:?}", test_case, policy); + + let response = https_get(test_case.query_target, &policy).await?; + let expected_status = StatusCode::from_u16(test_case.expected_status_code).unwrap(); + assert_eq!(response.status(), expected_status); + + if expected_status == StatusCode::OK { + let body = response.into_body().collect().await?.to_bytes(); + assert!(!body.is_empty()); + } + } + } + + Ok(()) +} diff --git a/bindings/rust/integration/src/network/mod.rs b/bindings/rust/integration/src/network/mod.rs new file mode 100644 index 00000000000..07d0cabf7de --- /dev/null +++ b/bindings/rust/integration/src/network/mod.rs @@ -0,0 +1,5 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +mod https_client; +mod tls_client; diff --git a/bindings/rust/integration/src/network/tls_client.rs b/bindings/rust/integration/src/network/tls_client.rs new file mode 100644 index 00000000000..c94325a7692 --- /dev/null +++ b/bindings/rust/integration/src/network/tls_client.rs @@ -0,0 +1,95 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use s2n_tls::{config::Config, enums::Version, security::Policy}; +use s2n_tls_tokio::{TlsConnector, TlsStream}; +use tokio::net::TcpStream; + +/// Perform a TLS handshake with port 443 of `domain`. +/// +/// * `domain`: The domain to perform the handshake with +/// * `security_policy`: The security policy to set on the handshaking client. +/// +/// Returns an open `TlsStream` if the handshake was successful, otherwise an +/// `Err``. +async fn handshake_with_domain( + domain: &str, + security_policy: &str, +) -> Result, Box> { + tracing::info!("querying {domain} with {security_policy}"); + const PORT: u16 = 443; + + let mut config = Config::builder(); + config.set_security_policy(&Policy::from_version(security_policy)?)?; + + let client = TlsConnector::new(config.build()?); + // open the TCP stream + let stream = TcpStream::connect((domain, PORT)).await?; + // complete the TLS handshake + Ok(client.connect(domain, stream).await?) +} + +#[cfg(feature = "pq")] +mod kms_pq { + use super::*; + + const DOMAIN: &str = "kms.us-east-1.amazonaws.com"; + + // confirm that we successfully negotiate a supported PQ key exchange. + // + // Note: In the future KMS will deprecate kyber_r3 in favor of ML-KEM. + // At that point this test should be updated with a security policy that + // supports ML-KEM. + #[test_log::test(tokio::test)] + async fn pq_handshake() -> Result<(), Box> { + let tls = handshake_with_domain(DOMAIN, "KMS-PQ-TLS-1-0-2020-07").await?; + + assert_eq!( + tls.as_ref().cipher_suite()?, + "ECDHE-KYBER-RSA-AES256-GCM-SHA384" + ); + assert_eq!(tls.as_ref().kem_name(), Some("kyber512r3")); + + Ok(()) + } + + // We want to confirm that non-supported kyber drafts successfully fall + // back to a full handshake. + #[test_log::test(tokio::test)] + async fn early_draft_falls_back_to_classical() -> Result<(), Box> { + const EARLY_DRAFT_PQ_POLICIES: &[&str] = &[ + "KMS-PQ-TLS-1-0-2019-06", + "PQ-SIKE-TEST-TLS-1-0-2019-11", + "KMS-PQ-TLS-1-0-2020-02", + "PQ-SIKE-TEST-TLS-1-0-2020-02", + ]; + + for security_policy in EARLY_DRAFT_PQ_POLICIES { + let tls = handshake_with_domain(DOMAIN, security_policy).await?; + + assert_eq!(tls.as_ref().cipher_suite()?, "ECDHE-RSA-AES256-GCM-SHA384"); + assert_eq!(tls.as_ref().kem_name(), None); + } + Ok(()) + } +} + +#[test_log::test(tokio::test)] +async fn tls_client() -> Result<(), Box> { + // The akamai request should be in internet_https_client.rs but Akamai + // http requests hang indefinitely. This behavior is also observed with + // curl and chrome. https://github.com/aws/s2n-tls/issues/4883 + const DOMAINS: &[&str] = &["www.akamai.com"]; + + for domain in DOMAINS { + tracing::info!("querying {domain}"); + + let tls12 = handshake_with_domain(domain, "default").await?; + assert_eq!(tls12.as_ref().actual_protocol_version()?, Version::TLS12); + + let tls13 = handshake_with_domain(domain, "default_tls13").await?; + assert_eq!(tls13.as_ref().actual_protocol_version()?, Version::TLS13); + } + + Ok(()) +} diff --git a/bindings/rust/integration/tests/internet_https_client.rs b/bindings/rust/integration/tests/internet_https_client.rs deleted file mode 100644 index 8c77ae0c982..00000000000 --- a/bindings/rust/integration/tests/internet_https_client.rs +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -/// Purpose: ensure that s2n-tls is compatible with other http/TLS implementations -/// -/// This test uses s2n-tls-hyper to make http requests over a TLS connection to -/// a number of well known http sites. -#[cfg(feature = "network-tests")] -mod https_client { - use bytes::Bytes; - use http::{Response, StatusCode, Uri}; - use http_body_util::{BodyExt, Empty}; - use hyper::body::Incoming; - use hyper_util::{client::legacy::Client, rt::TokioExecutor}; - use s2n_tls::{ - config::Config, - security::{self, Policy}, - }; - use s2n_tls_hyper::connector::HttpsConnector; - use std::str::FromStr; - - #[derive(Debug)] - struct TestCase { - pub query_target: &'static str, - pub expected_status_code: u16, - } - - impl TestCase { - const fn new(domain: &'static str, expected_status_code: u16) -> Self { - TestCase { - query_target: domain, - expected_status_code, - } - } - } - - const TEST_CASES: &[TestCase] = &[ - // this is a link to the s2n-tls unit test coverage report, hosted on cloudfront - TestCase::new("https://dx1inn44oyl7n.cloudfront.net/main/index.html", 200), - // this is a link to a non-existent S3 item - TestCase::new("https://notmybucket.s3.amazonaws.com/folder/afile.jpg", 403), - TestCase::new("https://www.amazon.com", 200), - TestCase::new("https://www.apple.com", 200), - TestCase::new("https://www.att.com", 200), - TestCase::new("https://www.cloudflare.com", 200), - TestCase::new("https://www.ebay.com", 200), - TestCase::new("https://www.google.com", 200), - TestCase::new("https://www.mozilla.org", 200), - TestCase::new("https://www.netflix.com", 200), - TestCase::new("https://www.openssl.org", 200), - TestCase::new("https://www.t-mobile.com", 200), - TestCase::new("https://www.verizon.com", 200), - TestCase::new("https://www.wikipedia.org", 200), - TestCase::new("https://www.yahoo.com", 200), - TestCase::new("https://www.youtube.com", 200), - TestCase::new("https://www.github.com", 301), - TestCase::new("https://www.samsung.com", 301), - TestCase::new("https://www.twitter.com", 301), - TestCase::new("https://www.facebook.com", 302), - TestCase::new("https://www.microsoft.com", 302), - TestCase::new("https://www.ibm.com", 303), - TestCase::new("https://www.f5.com", 403), - ]; - - /// perform an HTTP GET request against `uri` using an s2n-tls config with - /// `security_policy`. - async fn https_get( - uri: &str, - security_policy: &Policy, - ) -> Result, hyper_util::client::legacy::Error> { - let mut config = Config::builder(); - config.set_security_policy(security_policy).unwrap(); - - let connector = HttpsConnector::new(config.build().unwrap()); - let client: Client<_, Empty> = - Client::builder(TokioExecutor::new()).build(connector); - - let uri = Uri::from_str(uri).unwrap(); - client.get(uri).await - } - - #[test_log::test(tokio::test)] - async fn http_get_test() -> Result<(), Box> { - for test_case in TEST_CASES { - for policy in [security::DEFAULT, security::DEFAULT_TLS13] { - tracing::info!("executing test case {:#?} with {:?}", test_case, policy); - - let response = https_get(test_case.query_target, &policy).await?; - let expected_status = StatusCode::from_u16(test_case.expected_status_code).unwrap(); - assert_eq!(response.status(), expected_status); - - if expected_status == StatusCode::OK { - let body = response.into_body().collect().await?.to_bytes(); - assert!(!body.is_empty()); - } - } - } - - Ok(()) - } -} diff --git a/bindings/rust/integration/tests/internet_tls_client.rs b/bindings/rust/integration/tests/internet_tls_client.rs deleted file mode 100644 index ae923f3e1e7..00000000000 --- a/bindings/rust/integration/tests/internet_tls_client.rs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(feature = "network-tests")] -mod tls_client { - use s2n_tls::{config::Config, enums::Version, security::Policy}; - use s2n_tls_tokio::{TlsConnector, TlsStream}; - use tokio::net::TcpStream; - - /// Perform a TLS handshake with port 443 of `domain`. - /// - /// * `domain`: The domain to perform the handshake with - /// * `security_policy`: The security policy to set on the handshaking client. - /// - /// Returns an open `TlsStream` if the handshake was successful, otherwise an - /// `Err``. - async fn handshake_with_domain( - domain: &str, - security_policy: &str, - ) -> Result, Box> { - tracing::info!("querying {domain} with {security_policy}"); - const PORT: u16 = 443; - - let mut config = Config::builder(); - config.set_security_policy(&Policy::from_version(security_policy)?)?; - - let client = TlsConnector::new(config.build()?); - // open the TCP stream - let stream = TcpStream::connect((domain, PORT)).await?; - // complete the TLS handshake - Ok(client.connect(domain, stream).await?) - } - - /// Purpose: ensure that we remain compatible with existing pq AWS deployments. - /// - /// KMS has PQ support. Assert that we successfully negotiate PQ key exchange. - mod kms { - use crate::tls_client::handshake_with_domain; - - const DOMAIN: &str = "kms.us-east-1.amazonaws.com"; - - // confirm that we successfully negotiate a supported PQ key exchange. - // - // Note: In the future KMS will deprecate kyber_r3 in favor of ML-KEM. - // At that point this test should be updated with a security policy that - // supports ML-KEM. - #[test_log::test(tokio::test)] - async fn pq_handshake() -> Result<(), Box> { - let tls = handshake_with_domain(DOMAIN, "KMS-PQ-TLS-1-0-2020-07").await?; - - assert_eq!( - tls.as_ref().cipher_suite()?, - "ECDHE-KYBER-RSA-AES256-GCM-SHA384" - ); - assert_eq!(tls.as_ref().kem_name(), Some("kyber512r3")); - - Ok(()) - } - - // We want to confirm that non-supported kyber drafts successfully fall - // back to a full handshake. - #[test_log::test(tokio::test)] - async fn early_draft_falls_back_to_classical() -> Result<(), Box> { - const EARLY_DRAFT_PQ_POLICIES: &[&str] = &[ - "KMS-PQ-TLS-1-0-2019-06", - "PQ-SIKE-TEST-TLS-1-0-2019-11", - "KMS-PQ-TLS-1-0-2020-02", - "PQ-SIKE-TEST-TLS-1-0-2020-02", - ]; - - for security_policy in EARLY_DRAFT_PQ_POLICIES { - let tls = handshake_with_domain(DOMAIN, security_policy).await?; - - assert_eq!(tls.as_ref().cipher_suite()?, "ECDHE-RSA-AES256-GCM-SHA384"); - assert_eq!(tls.as_ref().kem_name(), None); - } - Ok(()) - } - } - - #[test_log::test(tokio::test)] - async fn tls_client() -> Result<(), Box> { - // The akamai request should be in internet_https_client.rs but Akamai - // http requests hang indefinitely. This behavior is also observed with - // curl and chrome. https://github.com/aws/s2n-tls/issues/4883 - const DOMAINS: &[&str] = &["www.akamai.com"]; - - for domain in DOMAINS { - tracing::info!("querying {domain}"); - - let tls12 = handshake_with_domain(domain, "default").await?; - assert_eq!(tls12.as_ref().actual_protocol_version()?, Version::TLS12); - - let tls12 = handshake_with_domain(domain, "default_tls13").await?; - assert_eq!(tls12.as_ref().actual_protocol_version()?, Version::TLS13); - } - - Ok(()) - } -}