Skip to content

Commit

Permalink
Merge branch 'main' into bindings-s2n-tls-ut-cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
jmayclin authored Jun 24, 2024
2 parents e2f7f2f + 917b7c6 commit 6b9f802
Show file tree
Hide file tree
Showing 21 changed files with 688 additions and 2 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/ci_rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ env:
RUST_NIGHTLY_TOOLCHAIN: nightly-2024-01-01
ROOT_PATH: bindings/rust
EXAMPLE_WORKSPACE: bindings/rust-examples
PCAP_TEST_PATH: tests/pcap

jobs:
generate:
Expand Down Expand Up @@ -256,3 +257,32 @@ jobs:
- name: Check MSRV of s2n-tokio
run: grep "rust-version = \"$(cat ${{env.ROOT_PATH}}/rust-toolchain)\"" ${{env.ROOT_PATH}}/s2n-tls-tokio/Cargo.toml

pcaps:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: true

- name: Install Rust toolchain
id: toolchain
run: |
rustup toolchain install stable --component clippy
rustup override set stable
- name: Install tshark
run: sudo apt-get install -y tshark

- name: Generate bindings
working-directory: ${{env.ROOT_PATH}}
run: ./generate.sh --skip-tests

- name: Run lints
working-directory: ${{env.PCAP_TEST_PATH}}
run: |
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
- name: Run tests
working-directory: ${{env.PCAP_TEST_PATH}}
run: cargo test
2 changes: 1 addition & 1 deletion .github/workflows/proof_ci_resources/config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
cadical-tag: latest
cbmc-version: latest
cbmc-version: "5.95.1"
cbmc-viewer-version: latest
kissat-tag: latest
litani-version: latest
Expand Down
2 changes: 2 additions & 0 deletions api/s2n.h
Original file line number Diff line number Diff line change
Expand Up @@ -1992,6 +1992,8 @@ S2N_API extern uint64_t s2n_connection_get_delay(struct s2n_connection *conn);
* @warning Do NOT set a lower blinding delay unless you understand the risks and have other
* mitigations for timing side channels in place.
*
* @note This delay needs to be set lower than any timeouts, such as your TCP socket timeout.
*
* @param config The config object being updated.
* @param seconds The maximum number of seconds that s2n-tls will delay for in the event of a
* sensitive error.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ TlsStream {
The server said Hello, you are speaking to www.wombat.com
```
Once again there is a successful handshake showing that the server responded with the proper certificate. In this case, the config that the server configured for `www.wombat.com` did not support TLS 1.3, so the TLS 1.2 was negotiated instead.

## Async Config Resolution
The [async load server](src/bin/async_load_server.rs) has the same functionality as the default [server](src/bin/server.rs), but implements the config resolution in an asynchronous manner. This allows the certificates to be loaded from disk without blocking the tokio runtime. A similar technique could be used to retrieve certificates over the network without blocking the runtime.
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use s2n_tls::{
callbacks::{ClientHelloCallback, ConfigResolver, ConnectionFuture},
security::{Policy, DEFAULT_TLS13},
};
use s2n_tls_tokio::TlsAcceptor;
use std::{error::Error, pin::Pin};
use tokio::{io::AsyncWriteExt, net::*, try_join};

const PORT: u16 = 1738;

#[derive(Clone)]
pub struct AsyncAnimalConfigResolver {
// the directory that contains the relevant certs
cert_directory: String,
}

impl AsyncAnimalConfigResolver {
fn new(cert_directory: String) -> Self {
AsyncAnimalConfigResolver { cert_directory }
}
}

impl ClientHelloCallback for AsyncAnimalConfigResolver {
fn on_client_hello(
&self,
connection: &mut s2n_tls::connection::Connection,
) -> Result<Option<Pin<Box<dyn ConnectionFuture>>>, s2n_tls::error::Error> {
let sni = match connection.server_name() {
Some(sni) => sni,
None => {
println!("connection contained no SNI");
return Err(s2n_tls::error::Error::application("no sni".into()));
}
};

// simple, limited logic to parse "animal" from "www.animal.com".
let mut tokens = sni.split('.');
tokens.next(); // "www"
let animal = match tokens.next() {
Some(animal) => animal.to_owned(), // "animal"
None => {
println!("unable to parse sni");
return Err(s2n_tls::error::Error::application(
format!("unable to parse sni: {}", sni).into(),
));
}
};

let config_future = server_config(animal, self.cert_directory.clone());
let config_resolver = ConfigResolver::new(config_future);
connection.server_name_extension_used();
Ok(Some(Box::pin(config_resolver)))
}
}

// This method will lookup the appropriate certificates and read them from disk
// in an async manner which won't block the tokio task.
//
// Note that this method takes `String` instead of `&str` like the synchronous
// version in server.rs. ConfigResolver requires a future that is `'static`.
async fn server_config(
animal: String,
cert_directory: String,
) -> Result<s2n_tls::config::Config, s2n_tls::error::Error> {
println!("asynchronously setting connection config associated with {animal}");

let cert_path = format!("{}/{}-chain.pem", cert_directory, animal);
let key_path = format!("{}/{}-key.pem", cert_directory, animal);
// we asynchronously read the cert chain and key from disk
let (cert, key) = try_join!(tokio::fs::read(cert_path), tokio::fs::read(key_path))
// we map any IO errors to the s2n-tls Error type, as required by the ConfigResolver bounds.
.map_err(|io_error| s2n_tls::error::Error::application(Box::new(io_error)))?;

let mut config = s2n_tls::config::Builder::new();
// we can set different policies for different configs. "20190214" doesn't
// support TLS 1.3, so any customer requesting www.wombat.com won't be able
// to negotiate TLS 1.3
let security_policy = match animal.as_str() {
"wombat" => Policy::from_version("20190214")?,
_ => DEFAULT_TLS13,
};
config.set_security_policy(&security_policy)?;
config.load_pem(&cert, &key)?;
config.build()
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let cert_directory = format!("{}/certs", env!("CARGO_MANIFEST_DIR"));
let resolver = AsyncAnimalConfigResolver::new(cert_directory);
let mut initial_config = s2n_tls::config::Builder::new();
initial_config.set_client_hello_callback(resolver)?;

let server = TlsAcceptor::new(initial_config.build()?);

let listener = TcpListener::bind(&format!("0.0.0.0:{PORT}")).await?;
loop {
let server = server.clone();
let (stream, _) = listener.accept().await?;
tokio::spawn(async move {
// handshake with the client
let handshake = server.accept(stream).await;
let mut tls = match handshake {
Ok(tls) => tls,
Err(e) => {
println!("error during handshake: {:?}", e);
return Ok(());
}
};

let connection = tls.as_ref();
let offered_sni = connection.server_name().unwrap();
let _ = tls
.write(format!("Hello, you are speaking to {offered_sni}").as_bytes())
.await?;
tls.shutdown().await?;
Ok::<(), Box<dyn Error + Send + Sync>>(())
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ impl Default for AnimalConfigResolver {
// using the ConfigResolver: https://docs.rs/s2n-tls/latest/s2n_tls/callbacks/struct.ConfigResolver.html#
// This is useful if servers need to read from disk or make network calls as part
// of the configuration, and want to avoid blocking the tokio task while doing so.
// An example of this implementation is contained in the "async_load_server".
impl ClientHelloCallback for AnimalConfigResolver {
fn on_client_hello(
&self,
Expand Down
8 changes: 8 additions & 0 deletions bindings/rust/s2n-tls/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,14 @@ impl Connection {
})
}

pub fn application_protocol(&self) -> Option<&[u8]> {
let protocol = unsafe { s2n_get_application_protocol(self.connection.as_ptr()) };
if protocol.is_null() {
return None;
}
Some(unsafe { CStr::from_ptr(protocol).to_bytes() })
}

/// Provides access to the TLS-Exporter functionality.
///
/// See https://datatracker.ietf.org/doc/html/rfc5705 and https://www.rfc-editor.org/rfc/rfc8446.
Expand Down
27 changes: 27 additions & 0 deletions bindings/rust/s2n-tls/src/testing/s2n_tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,4 +867,31 @@ mod tests {

Ok(())
}

#[test]
fn no_application_protocol() -> Result<(), Error> {
let config = config_builder(&security::DEFAULT)?.build()?;
let mut pair = tls_pair(config);
assert!(poll_tls_pair_result(&mut pair).is_ok());
assert!(pair.server.0.connection.application_protocol().is_none());
Ok(())
}

#[test]
fn application_protocol() -> Result<(), Error> {
let config = config_builder(&security::DEFAULT)?.build()?;
let mut pair = tls_pair(config);
pair.server
.0
.connection
.set_application_protocol_preference(["http/1.1", "h2"])?;
pair.client
.0
.connection
.append_application_protocol_preference(b"h2")?;
assert!(poll_tls_pair_result(&mut pair).is_ok());
let protocol = pair.server.0.connection.application_protocol().unwrap();
assert_eq!(protocol, b"h2");
Ok(())
}
}
2 changes: 1 addition & 1 deletion codebuild/spec/buildspec_musl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ phases:
on-failure: ABORT
commands:
# Install musl libc
- git clone https://git.musl-libc.org/git/musl $MUSL_DIR
- git clone --depth=1 https://git.musl-libc.org/git/musl $MUSL_DIR
- echo "Installing musl to $CODEBUILD_SRC_DIR/$MUSL_DIR"
- cd $MUSL_DIR
- ./configure --prefix=$CODEBUILD_SRC_DIR/$MUSL_DIR
Expand Down
2 changes: 2 additions & 0 deletions docs/usage-guide/topics/ch06-security-policies.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ In contrast, numbered or dated versions are fixed and will never change. The num
* "default_tls13": "20240503"
For previous defaults, see the "Default Policy History" section below.

"default_fips" does not currently support TLS1.3. If you need a policy that supports both FIPS and TLS1.3, choose "20230317". We plan to add TLS1.3 support to both "default" and "default_fips" in the future.

"rfc9151" is derived from [Commercial National Security Algorithm (CNSA) Suite Profile for TLS and DTLS 1.2 and 1.3](https://datatracker.ietf.org/doc/html/rfc9151). This policy restricts the algorithms allowed for signatures on certificates in the certificate chain to RSA or ECDSA with sha384, which may require you to update your certificates.
Like the default policies, this policy may also change if the source RFC definition changes.

Expand Down
2 changes: 2 additions & 0 deletions tests/pcap/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target/
Cargo.lock
15 changes: 15 additions & 0 deletions tests/pcap/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "pcap"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
anyhow = "1.0.86"
hex = "0.4.3"
rtshark = "2.7.1"

[dev-dependencies]
# We want to test against the latest, local version of s2n
s2n-tls-sys = { path = "../../bindings/rust/s2n-tls-sys" }
s2n-tls = { path = "../../bindings/rust/s2n-tls", features = ["unstable-fingerprint"] }
Binary file added tests/pcap/data/fragmented_ch.pcap
Binary file not shown.
Binary file added tests/pcap/data/multiple_hellos.pcap
Binary file not shown.
Binary file added tests/pcap/data/tls12.pcap
Binary file not shown.
Binary file added tests/pcap/data/tls13.pcap
Binary file not shown.
64 changes: 64 additions & 0 deletions tests/pcap/src/client_hello.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::handshake_message::HandshakeMessage;
use anyhow::*;
use std::option::Option;

#[derive(Debug, Clone, Default)]
pub struct ClientHello(HandshakeMessage);

impl ClientHello {
/// ClientHello message type, as defined in the TLS RFC.
/// See https://datatracker.ietf.org/doc/html/rfc8446#section-4
const MESSAGE_TYPE: u8 = 1;

const JA3_HASH: &'static str = "tls.handshake.ja3";
pub fn ja3_hash(&self) -> Option<String> {
self.0.packet.metadata(Self::JA3_HASH).map(str::to_owned)
}

const JA3_STR: &'static str = "tls.handshake.ja3_full";
pub fn ja3_string(&self) -> Option<String> {
self.0.packet.metadata(Self::JA3_STR).map(str::to_owned)
}

pub fn message(&self) -> &HandshakeMessage {
&self.0
}
}

impl crate::handshake_message::Builder {
pub fn build_client_hellos(mut self) -> Result<Vec<ClientHello>> {
self.set_type(ClientHello::MESSAGE_TYPE);
let client_hellos = self.build()?.into_iter().map(ClientHello).collect();
Ok(client_hellos)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::handshake_message::Builder;

#[test]
fn multiple_hellos() -> Result<()> {
let mut builder = Builder::default();
builder.set_capture_file("data/multiple_hellos.pcap");
let hellos = builder.build_client_hellos().unwrap();
assert_eq!(hellos.len(), 5);
Ok(())
}

#[test]
fn from_pcaps() -> Result<()> {
let pcaps = crate::all_pcaps();
for pcap in pcaps {
let mut builder = Builder::default();
builder.set_capture_file(&pcap);
let hellos = builder.build_client_hellos().unwrap();
assert!(!hellos.is_empty());
}
Ok(())
}
}
Loading

0 comments on commit 6b9f802

Please sign in to comment.