Skip to content

Commit

Permalink
feat(bench): add openssl handshake to benchmarking (aws#4069)
Browse files Browse the repository at this point in the history
  • Loading branch information
tinzh authored Jun 29, 2023
1 parent 90dd13a commit a23e8c5
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 30 deletions.
1 change: 1 addition & 0 deletions bindings/rust/bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ edition = "2021"
s2n-tls = { path = "../s2n-tls" }
rustls = "0.21"
rustls-pemfile = "1.0"
openssl = "0.10"
errno = "0.3"
libc = "0.2"

Expand Down
27 changes: 27 additions & 0 deletions bindings/rust/bench/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Benchmarking s2n-tls

We use to Criterion.rs to benchmark s2n-tls against two commonly used TLS libraries, Rustls and OpenSSL.

## Setup

Setup is easy! Just have OpenSSL installed and generate Rust bindings for s2n-tls using `bindings/rust/generate.sh`.

## Running benchmarks

The benchmarks can be run with the `cargo bench` command. Criterion will auto-generate an HTML report in `target/criterion/`.

## Implementation details

We use Rust bindings for s2n-tls and OpenSSL. All of our benchmarks are run in Rust on a single thread for consistency.

### IO

To remove external factors, we use custom IO with our benchmarks, bypassing the networking layer and having the client and server connections transfer data to each other via a local buffer.

### Certificate generation

All certs are stored in `certs/` and can be regenerated using `certs/generate_certs.sh`. There is one root cert that directly signs the server and client certs that are used in benchmarking. Currently, we use ECDSA with `secp384r1`.

### Negotiation parameters

The cipher suites benchmarked are `TLS_AES_128_GCM_SHA256` and `TLS_AES_256_GCM_SHA384`, and the key exchange methods benchmarked are ECDHE with `secp256r1` and with `x25519`.
62 changes: 36 additions & 26 deletions bindings/rust/bench/benches/handshake.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,47 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use bench::{RustlsHarness, S2NHarness, TlsBenchHarness};
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use std::any::type_name;

pub fn bench_handshake(c: &mut Criterion) {
let mut group = c.benchmark_group("handshake");
use bench::{
CipherSuite::*,
CryptoConfig,
ECGroup::{self, *},
OpenSslHarness, RustlsHarness, S2NHarness, TlsBenchHarness,
};
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BatchSize, BenchmarkGroup, Criterion,
};

macro_rules! bench_handshake_for_libraries {
($(($lib_name:expr, $lib_type:ty),)*) => {
$(
// generate all inputs (TlsBenchHarness structs) before benchmarking handshakes
// timing only includes negotiation, not config/connection initialization
group.bench_function($lib_name, |b| {
b.iter_batched_ref(
|| <$lib_type>::default().unwrap(),
|harness| {
harness.handshake().unwrap();
},
BatchSize::SmallInput,
)
});
)*
}
pub fn bench_handshake_key_exchange(c: &mut Criterion) {
fn bench_handshake_for_library<T: TlsBenchHarness>(
bench_group: &mut BenchmarkGroup<WallTime>,
ec_group: &ECGroup,
) {
bench_group.bench_function(type_name::<T>(), |b| {
b.iter_batched_ref(
|| {
T::new(&CryptoConfig {
cipher_suite: AES_128_GCM_SHA256,
ec_group: *ec_group,
})
.unwrap()
},
|harness| {
harness.handshake().unwrap();
},
BatchSize::SmallInput,
)
});
}

bench_handshake_for_libraries! {
("s2n-tls", S2NHarness),
("rustls", RustlsHarness),
for ec_group in [SECP256R1, X25519] {
let mut bench_group = c.benchmark_group(format!("handshake-{:?}", ec_group));
bench_handshake_for_library::<S2NHarness>(&mut bench_group, &ec_group);
bench_handshake_for_library::<RustlsHarness>(&mut bench_group, &ec_group);
bench_handshake_for_library::<OpenSslHarness>(&mut bench_group, &ec_group);
}

group.finish();
}

criterion_group!(benches, bench_handshake);
criterion_group!(benches, bench_handshake_key_exchange);
criterion_main!(benches);
9 changes: 5 additions & 4 deletions bindings/rust/bench/src/harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ pub enum Mode {
// these parameters were the only ones readily usable for all three libaries:
// s2n-tls, rustls, and openssl
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CipherSuite {
AES_128_GCM_SHA256,
AES_256_GCM_SHA384,
}

#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ECGroup {
SECP256R1,
X25519,
Expand All @@ -45,7 +45,7 @@ pub trait TlsBenchHarness: Sized {
fn default() -> Result<Self, Box<dyn Error>> {
Self::new(&CryptoConfig {
cipher_suite: CipherSuite::AES_128_GCM_SHA256,
ec_group: ECGroup::SECP256R1,
ec_group: ECGroup::X25519,
})
}

Expand Down Expand Up @@ -151,10 +151,11 @@ macro_rules! test_tls_bench_harnesses {
#[cfg(test)]
mod tests {
use super::*;
use crate::{RustlsHarness, S2NHarness, TlsBenchHarness};
use crate::{OpenSslHarness, RustlsHarness, S2NHarness, TlsBenchHarness};

test_tls_bench_harnesses! {
s2n_tls: S2NHarness,
rustls: RustlsHarness,
openssl: OpenSslHarness,
}
}
2 changes: 2 additions & 0 deletions bindings/rust/bench/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
// SPDX-License-Identifier: Apache-2.0

pub mod harness;
pub mod openssl;
pub mod rustls;
pub mod s2n_tls;
pub use crate::{
harness::{CipherSuite, CryptoConfig, ECGroup, TlsBenchHarness},
openssl::OpenSslHarness,
rustls::RustlsHarness,
s2n_tls::S2NHarness,
};
Expand Down
117 changes: 117 additions & 0 deletions bindings/rust/bench/src/openssl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::{
harness::{CipherSuite, ConnectedBuffer, CryptoConfig, ECGroup, Mode, TlsBenchHarness},
CA_CERT_PATH, SERVER_CERT_CHAIN_PATH, SERVER_KEY_PATH,
};
use openssl::ssl::{
ErrorCode, Ssl, SslContext, SslContextBuilder, SslFiletype, SslMethod, SslStream, SslVersion,
};
use std::error::Error;

pub struct OpenSslHarness {
client_conn: SslStream<ConnectedBuffer>,
server_conn: SslStream<ConnectedBuffer>,
}

impl OpenSslHarness {
fn common_config(
builder: &mut SslContextBuilder,
cipher_suite: &str,
ec_key: &str,
) -> Result<(), Box<dyn Error>> {
builder.set_min_proto_version(Some(SslVersion::TLS1_3))?;
builder.set_ciphersuites(cipher_suite)?;
builder.set_groups_list(ec_key)?;
Ok(())
}
/// Process handshake for one connection, treating blocking errors as `Ok`
fn handshake_conn(&mut self, mode: Mode) -> Result<(), Box<dyn Error>> {
let result = match mode {
Mode::Client => self.client_conn.connect(),
Mode::Server => self.server_conn.accept(),
};
match result {
Ok(_) => Ok(()),
Err(err) => {
if err.code() != ErrorCode::WANT_READ {
Err(err.into())
} else {
Ok(())
}
}
}
}
}

impl TlsBenchHarness for OpenSslHarness {
fn new(crypto_config: &CryptoConfig) -> Result<Self, Box<dyn Error>> {
let client_buf = ConnectedBuffer::new();
let server_buf = client_buf.clone_inverse();

let cipher_suite = match crypto_config.cipher_suite {
CipherSuite::AES_128_GCM_SHA256 => "TLS_AES_128_GCM_SHA256",
CipherSuite::AES_256_GCM_SHA384 => "TLS_AES_256_GCM_SHA384",
};

let ec_key = match crypto_config.ec_group {
ECGroup::SECP256R1 => "P-256",
ECGroup::X25519 => "X25519",
};

let mut client_builder = SslContext::builder(SslMethod::tls_client())?;
client_builder.set_ca_file(CA_CERT_PATH)?;
Self::common_config(&mut client_builder, cipher_suite, ec_key)?;

let mut server_builder = SslContext::builder(SslMethod::tls_server())?;
server_builder.set_certificate_chain_file(SERVER_CERT_CHAIN_PATH)?;
server_builder.set_private_key_file(SERVER_KEY_PATH, SslFiletype::PEM)?;
Self::common_config(&mut server_builder, cipher_suite, ec_key)?;

let client_config = client_builder.build();
let server_config = server_builder.build();

let client_conn = SslStream::new(Ssl::new(&client_config)?, client_buf)?;
let server_conn = SslStream::new(Ssl::new(&server_config)?, server_buf)?;

Ok(Self {
client_conn,
server_conn,
})
}

fn handshake(&mut self) -> Result<(), Box<dyn Error>> {
for _ in 0..2 {
self.handshake_conn(Mode::Client)?;
self.handshake_conn(Mode::Server)?;
}
Ok(())
}

fn handshake_completed(&self) -> bool {
self.client_conn.ssl().is_init_finished() && self.server_conn.ssl().is_init_finished()
}

fn get_negotiated_cipher_suite(&self) -> CipherSuite {
let cipher_suite = self
.client_conn
.ssl()
.current_cipher()
.expect("Handshake not completed")
.name();
match cipher_suite {
"TLS_AES_128_GCM_SHA256" => CipherSuite::AES_128_GCM_SHA256,
"TLS_AES_256_GCM_SHA384" => CipherSuite::AES_256_GCM_SHA384,
_ => panic!("Unknown cipher suite"),
}
}

fn negotiated_tls13(&self) -> bool {
self.client_conn
.ssl()
.version2() // version() -> &str is deprecated, version2() returns an enum instead
.expect("Handshake not completed")
== SslVersion::TLS1_3
}
}

0 comments on commit a23e8c5

Please sign in to comment.