Skip to content

Commit

Permalink
Merge branch 'main' into cbmc_6_minimal
Browse files Browse the repository at this point in the history
  • Loading branch information
lrstewart authored Aug 1, 2024
2 parents 660d4fb + 5c9d554 commit e4b76a3
Show file tree
Hide file tree
Showing 9 changed files with 415 additions and 4 deletions.
22 changes: 18 additions & 4 deletions .github/workflows/ci_rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
rustup override set stable
# https://github.com/aws/aws-lc-rs/blob/main/aws-lc-fips-sys/README.md#build-prerequisites
# go required to build aws-lc-rs in FIPS mode
# go required for generate.sh to build aws-lc-rs in FIPS mode
- name: Install go
uses: actions/setup-go@v4
with:
Expand All @@ -46,7 +46,8 @@ jobs:

- name: Tests
working-directory: ${{env.ROOT_PATH}}
run: cargo test --all-features
# Test all features except for FIPS, which is tested separately.
run: cargo test --features unstable-fingerprint,unstable-ktls,quic,pq

# Ensure that all tests pass with the default feature set
- name: Default Tests
Expand Down Expand Up @@ -159,7 +160,11 @@ jobs:
run: cargo test --all-features

fips:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macOS-latest]
steps:
- uses: actions/checkout@v3
with:
Expand All @@ -186,7 +191,16 @@ jobs:
- name: Test fips
working-directory: ${{env.ROOT_PATH}}
run: |
cargo test --features fips
# The doc tests fail to link to AWS-LC in FIPS mode due to
# https://github.com/rust-lang/cargo/issues/8531. The --tests flag is provided to disable
# the doc tests. The doc tests are tested in the generate test, where FIPS is disabled.
cargo test --tests --features fips
# Test all features, including FIPS
- name: Test all
working-directory: ${{env.ROOT_PATH}}
run: |
cargo test --tests --all-features
rustfmt:
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions bindings/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ members = [
"s2n-tls",
"s2n-tls-sys",
"s2n-tls-tokio",
"s2n-tls-hyper",
]
# generate can't be included in the workspace because of a bootstrapping problem
# s2n-tls-sys/Cargo.toml (part of the workspace) is generated by
Expand Down
25 changes: 25 additions & 0 deletions bindings/rust/s2n-tls-hyper/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "s2n-tls-hyper"
description = "A compatbility crate allowing s2n-tls to be used with the hyper HTTP library"
version = "0.0.1"
authors = ["AWS s2n"]
edition = "2021"
rust-version = "1.63.0"
repository = "https://github.com/aws/s2n-tls"
license = "Apache-2.0"

[features]
default = []

[dependencies]
s2n-tls = { version = "=0.2.9", path = "../s2n-tls" }
s2n-tls-tokio = { version = "=0.2.9", path = "../s2n-tls-tokio" }
hyper = { version = "1" }
hyper-util = { version = "0.1", features = ["client-legacy", "tokio", "http1"] }
tower-service = { version = "0.3" }
http = { version= "1" }

[dev-dependencies]
tokio = { version = "1", features = ["macros", "test-util"] }
http-body-util = "0.1"
bytes = "1"
3 changes: 3 additions & 0 deletions bindings/rust/s2n-tls-hyper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
`s2n-tls-hyper` provides compatibility structs for [hyper](https://hyper.rs/), allowing s2n-tls to be used as the underlying TLS implementation with hyper clients.

This crate is currently being developed and is unstable.
168 changes: 168 additions & 0 deletions bindings/rust/s2n-tls-hyper/src/connector.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::{error::Error, stream::MaybeHttpsStream};
use http::uri::Uri;
use hyper::rt::{Read, Write};
use hyper_util::{
client::legacy::connect::{Connection, HttpConnector},
rt::TokioIo,
};
use s2n_tls::{config::Config, connection};
use s2n_tls_tokio::TlsConnector;
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use tower_service::Service;

/// hyper-compatible connector used to negotiate HTTPS.
///
/// hyper clients use a connector to send and receive HTTP requests over an underlying IO stream. By
/// default, hyper provides `hyper_util::client::legacy::connect::HttpConnector` for this purpose,
/// which sends and receives requests over TCP. The `HttpsConnector` struct wraps an HTTP connector,
/// and uses it to negotiate TLS when the HTTPS scheme is in use.
#[derive(Clone)]
pub struct HttpsConnector<Http, Builder = Config> {
http: Http,
conn_builder: Builder,
}

impl<Builder> HttpsConnector<HttpConnector, Builder>
where
Builder: connection::Builder,
<Builder as connection::Builder>::Output: Unpin,
{
/// Creates a new `HttpsConnector`.
///
/// `conn_builder` will be used to produce the s2n-tls Connections used for negotiating HTTPS,
/// which can be an `s2n_tls::config::Config` or other `s2n_tls::connection::Builder`.
///
/// This API creates an `HttpsConnector` using the default hyper `HttpConnector`. To use an
/// existing HTTP connector, use `HttpsConnector::new_with_http()`.
pub fn new(conn_builder: Builder) -> HttpsConnector<HttpConnector, Builder> {
let mut http = HttpConnector::new();

// By default, the `HttpConnector` only allows the HTTP URI scheme to be used. To negotiate
// HTTP over TLS via the HTTPS scheme, `enforce_http` must be disabled.
http.enforce_http(false);

Self { http, conn_builder }
}
}

impl<Http, Builder> HttpsConnector<Http, Builder>
where
Builder: connection::Builder,
<Builder as connection::Builder>::Output: Unpin,
{
/// Creates a new `HttpsConnector`.
///
/// `conn_builder` will be used to produce the s2n-tls Connections used for negotiating HTTPS,
/// which can be an `s2n_tls::config::Config` or other `s2n_tls::connection::Builder`.
///
/// This API allows an `HttpsConnector` to be constructed with an existing HTTP connector, as follows:
/// ```
/// use s2n_tls_hyper::connector::HttpsConnector;
/// use s2n_tls::config::Config;
/// use hyper_util::client::legacy::connect::HttpConnector;
///
/// let mut http = HttpConnector::new();
///
/// // Ensure that the HTTP connector permits the HTTPS scheme.
/// http.enforce_http(false);
///
/// let connector = HttpsConnector::new_with_http(http, Config::default());
/// ```
///
/// `HttpsConnector::new()` can be used to create the HTTP connector automatically.
pub fn new_with_http(http: Http, conn_builder: Builder) -> HttpsConnector<Http, Builder> {
Self { http, conn_builder }
}
}

// hyper connectors MUST implement `hyper_util::client::legacy::connect::Connect`, which is an alias
// for the `tower_service::Service` trait where `Service` is implemented for `http::uri::Uri`, and
// `Service::Response` implements traits for compatibility with hyper:
// https://docs.rs/hyper-util/latest/hyper_util/client/legacy/connect/trait.Connect.html
//
// The hyper compatibility traits for `Service::Response` are implemented in `MaybeHttpsStream`.
impl<Http, Builder> Service<Uri> for HttpsConnector<Http, Builder>
where
Http: Service<Uri>,
Http::Response: Read + Write + Connection + Unpin + Send + 'static,
Http::Future: Send + 'static,
Http::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
Builder: connection::Builder + Send + Sync + 'static,
<Builder as connection::Builder>::Output: Unpin + Send,
{
type Response = MaybeHttpsStream<Http::Response, Builder>;
type Error = Error;
type Future = Pin<
Box<dyn Future<Output = Result<MaybeHttpsStream<Http::Response, Builder>, Error>> + Send>,
>;

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
match self.http.poll_ready(cx) {
Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
Poll::Ready(Err(e)) => Poll::Ready(Err(Error::HttpError(e.into()))),
Poll::Pending => Poll::Pending,
}
}

fn call(&mut self, req: Uri) -> Self::Future {
// Currently, the only supported stream type is TLS. If the application attempts to
// negotiate HTTP over plain TCP, return an error.
if req.scheme() == Some(&http::uri::Scheme::HTTP) {
return Box::pin(async move { Err(Error::InvalidScheme) });
}

let builder = self.conn_builder.clone();
let host = req.host().unwrap_or("").to_owned();
let call = self.http.call(req);
Box::pin(async move {
// `HttpsConnector` wraps an HTTP connector that also implements `Service<Uri>`.
// `call()` is invoked on the wrapped connector to get the underlying hyper TCP stream,
// which is converted into a tokio-compatible stream with `hyper_util::rt::TokioIo`.
let tcp = call.await.map_err(|e| Error::HttpError(e.into()))?;
let tcp = TokioIo::new(tcp);

let connector = TlsConnector::new(builder);
let tls = connector
.connect(&host, tcp)
.await
.map_err(Error::TlsError)?;

Ok(MaybeHttpsStream::Https(TokioIo::new(tls)))
})
}
}

#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
use http_body_util::Empty;
use hyper_util::{client::legacy::Client, rt::TokioExecutor};
use std::{error::Error as StdError, str::FromStr};

#[tokio::test]
async fn test_unsecure_http() -> Result<(), Box<dyn StdError>> {
let connector = HttpsConnector::new(Config::default());
let client: Client<_, Empty<Bytes>> =
Client::builder(TokioExecutor::new()).build(connector);

let uri = Uri::from_str("http://www.amazon.com")?;
let error = client.get(uri).await.unwrap_err();

// Ensure that an InvalidScheme error is returned when HTTP over TCP is attempted.
let error = error.source().unwrap().downcast_ref::<Error>().unwrap();
assert!(matches!(error, Error::InvalidScheme));

// Ensure that the error can produce a valid message
assert!(!error.to_string().is_empty());

Ok(())
}
}
28 changes: 28 additions & 0 deletions bindings/rust/s2n-tls-hyper/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::fmt::{Display, Formatter};

/// Indicates which error occurred.
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
/// Indicates that the scheme in the URI provided to the `HttpsConnector` is invalid.
InvalidScheme,
/// Indicates that an error occurred in the underlying `HttpConnector`.
HttpError(Box<dyn std::error::Error + Send + Sync>),
/// Indicates that an error occurred in s2n-tls.
TlsError(s2n_tls::error::Error),
}

impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Error::InvalidScheme => write!(f, "The provided URI contains an invalid scheme."),
Error::HttpError(err) => write!(f, "{}", err),
Error::TlsError(err) => write!(f, "{}", err),
}
}
}

impl std::error::Error for Error {}
47 changes: 47 additions & 0 deletions bindings/rust/s2n-tls-hyper/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

#![warn(missing_docs)]

//! This crate provides compatibility structs for the [hyper](https://hyper.rs/) HTTP library,
//! allowing s2n-tls to be used as the underlying TLS implementation to negotiate HTTPS with hyper
//! clients.
//!
//! `s2n-tls-hyper` provides an `HttpsConnector` struct which is compatible with the
//! `hyper_util::client::legacy::Client` builder, allowing hyper clients to be constructed with
//! configurable s2n-tls connections. The following example demonstrates how to construct a hyper
//! client with s2n-tls:
//!
//! ```
//! use std::str::FromStr;
//! use hyper_util::{
//! client::legacy::Client,
//! rt::TokioExecutor,
//! };
//! use s2n_tls_hyper::connector::HttpsConnector;
//! use s2n_tls::config::Config;
//! use bytes::Bytes;
//! use http_body_util::Empty;
//! use http::uri::Uri;
//!
//! // An `HttpsConnector` is built with an `s2n_tls::connection::Builder`, such as an
//! // `s2n_tls::config::Config`, which allows for the underlying TLS connection to be configured.
//! let config = Config::default();
//!
//! // The `HttpsConnector` wraps hyper's `HttpConnector`. `HttpsConnector::new()` will create
//! // a new `HttpConnector` to wrap.
//! let connector = HttpsConnector::new(Config::default());
//!
//! // The `HttpsConnector` can then be provided to the hyper Client builder, which can be used to
//! // send HTTP requests over HTTPS by specifying the HTTPS scheme in the URL.
//! let client: Client<_, Empty<Bytes>> =
//! Client::builder(TokioExecutor::new()).build(connector);
//! ```
/// Provides the `HttpsConnector` struct.
pub mod connector;

/// Provides errors returned by s2n-tls-hyper.
pub mod error;

mod stream;
Loading

0 comments on commit e4b76a3

Please sign in to comment.