Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions boring/src/ssl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3708,6 +3708,17 @@ impl SslRef {
pub fn ech_accepted(&self) -> bool {
unsafe { ffi::SSL_ech_accepted(self.as_ptr()) != 0 }
}

// Whether or not to enable ECH grease on `SSL`.
#[cfg(not(feature = "fips"))]
#[corresponds(SSL_set_enable_ech_grease)]
pub fn set_enable_ech_grease(&self, enable: bool) {
let enable = if enable { 1 } else { 0 };

unsafe {
ffi::SSL_set_enable_ech_grease(self.as_ptr(), enable);
}
}
}

/// An SSL stream midway through the handshake process.
Expand Down
47 changes: 26 additions & 21 deletions boring/src/ssl/test/ech.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::hpke::HpkeKey;
use crate::ssl::ech::SslEchKeys;
use crate::ssl::test::Server;
use crate::ssl::test::server::{ClientSslBuilder, Server};
use crate::ssl::HandshakeError;

// For future reference, these configs are generated by building the bssl tool (the binary is built
Expand All @@ -15,12 +15,11 @@ static ECH_KEY: &[u8] = include_bytes!("../../../test/echkey");
static ECH_CONFIG_2: &[u8] = include_bytes!("../../../test/echconfig-2");
static ECH_KEY_2: &[u8] = include_bytes!("../../../test/echkey-2");

#[test]
fn ech() {
fn bootstrap_ech(config: &[u8], key: &[u8], list: &[u8]) -> (Server, ClientSslBuilder) {
let server = {
let key = HpkeKey::dhkem_p256_sha256(ECH_KEY).unwrap();
let key = HpkeKey::dhkem_p256_sha256(key).unwrap();
let mut ech_keys = SslEchKeys::new().unwrap();
ech_keys.add_key(true, ECH_CONFIG, key).unwrap();
ech_keys.add_key(true, config, key).unwrap();

let mut builder = Server::builder();
builder.ctx().set_ech_keys(ech_keys).unwrap();
Expand All @@ -29,39 +28,45 @@ fn ech() {
};

let mut client = server.client_with_root_ca().build().builder();
client.ssl().set_ech_config_list(ECH_CONFIG_LIST).unwrap();
client.ssl().set_ech_config_list(list).unwrap();
client.ssl().set_hostname("foobar.com").unwrap();

(server, client)
}

#[test]
fn ech() {
let (_server, client) = bootstrap_ech(ECH_CONFIG, ECH_KEY, ECH_CONFIG_LIST);

let ssl_stream = client.connect();
assert!(ssl_stream.ssl().ech_accepted())
}

#[test]
fn ech_rejection() {
let server = {
let key = HpkeKey::dhkem_p256_sha256(ECH_KEY_2).unwrap();
let mut ech_keys = SslEchKeys::new().unwrap();
ech_keys.add_key(true, ECH_CONFIG_2, key).unwrap();

let mut builder = Server::builder();
builder.ctx().set_ech_keys(ech_keys).unwrap();

builder.build()
};

let mut client = server.client_with_root_ca().build().builder();
// Server is initialized using `ECH_CONFIG_2`, so using `ECH_CONFIG_LIST` instead of
// `ECH_CONFIG_LIST_2` should trigger rejection.
client.ssl().set_ech_config_list(ECH_CONFIG_LIST).unwrap();
client.ssl().set_hostname("foobar.com").unwrap();
let (_server, client) = bootstrap_ech(ECH_CONFIG_2, ECH_KEY_2, ECH_CONFIG_LIST);

let HandshakeError::Failure(failed_ssl_stream) = client.connect_err() else {
panic!("wrong HandshakeError failure variant!");
};

assert_eq!(
failed_ssl_stream.ssl().get_ech_name_override(),
Some(b"ech.com".to_vec().as_ref())
);
assert!(failed_ssl_stream.ssl().get_ech_retry_configs().is_some());
assert!(!failed_ssl_stream.ssl().ech_accepted())
}

#[test]
fn ech_grease() {
let server = Server::builder().build();

let mut client = server.client_with_root_ca().build().builder();
// Verified with a pcap locally that the ECH extension gets sent due to GREASE
client.ssl().set_enable_ech_grease(true);

let ssl_stream = client.connect();
assert!(!ssl_stream.ssl().ech_accepted())
}
Loading