forked from aws/s2n-tls
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(bench): add openssl handshake to benchmarking (aws#4069)
- Loading branch information
Showing
6 changed files
with
188 additions
and
30 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |