Skip to content

Commit

Permalink
test: add rust well-known-endpoint tests (#4884)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmayclin authored Nov 16, 2024
1 parent 2ba3dda commit 06015f9
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 152 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci_rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ jobs:
working-directory: ${{env.ROOT_PATH}}
run: cargo test --features unstable-renegotiate

- name: Network-enabled integration tests
working-directory: ${{env.ROOT_PATH}}/integration
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
# included. For a symbol to be visible in a shared lib, the
Expand Down
29 changes: 28 additions & 1 deletion bindings/rust/integration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,34 @@ authors = ["AWS s2n"]
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" }

[dev-dependencies]
tokio = { version = "1", features = ["macros", "test-util"] }

tracing = "0.1"
tracing-subscriber = "0.3"
# 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"
31 changes: 31 additions & 0 deletions bindings/rust/integration/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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::{
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<dyn std::error::Error>> {
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(())
}
}
97 changes: 97 additions & 0 deletions bindings/rust/integration/src/network/https_client.rs
Original file line number Diff line number Diff line change
@@ -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<Response<Incoming>, 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<Bytes>> = 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<dyn std::error::Error>> {
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(())
}
5 changes: 5 additions & 0 deletions bindings/rust/integration/src/network/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
95 changes: 95 additions & 0 deletions bindings/rust/integration/src/network/tls_client.rs
Original file line number Diff line number Diff line change
@@ -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<TlsStream<TcpStream>, Box<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
// 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(())
}
25 changes: 0 additions & 25 deletions bindings/rust/s2n-tls-hyper/tests/web_client.rs

This file was deleted.

28 changes: 28 additions & 0 deletions bindings/rust/s2n-tls/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,34 @@ impl Connection {
}
}

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.
// SAFETY: kem_name has a static lifetime because it lives on a const
// struct s2n_kem with file scope.
const_str!(name_bytes)
};

match name_str {
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
}
}
}

pub fn selected_curve(&self) -> Result<&str, Error> {
let curve = unsafe { s2n_connection_get_curve(self.connection.as_ptr()).into_result()? };
unsafe {
Expand Down
29 changes: 29 additions & 0 deletions bindings/rust/s2n-tls/src/testing/s2n_tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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)?;
Expand Down
Loading

0 comments on commit 06015f9

Please sign in to comment.