Skip to content

Commit f6ca8f0

Browse files
authored
refactor(tls-harness): use single test pair IO to allow for decryption (#5648)
1 parent f1b90fe commit f6ca8f0

File tree

8 files changed

+156
-41
lines changed

8 files changed

+156
-41
lines changed

bindings/rust/standard/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,10 @@ members = [
55
"s2n-tls-hyper",
66
"tls-harness",
77
]
8+
9+
# brass-aphid-wire has a dependency on s2n-tls. This patch is necessary to unify
10+
# the crates.io dependency that brass-aphid-wire declares with the path-based
11+
# dependency that the integration tests have
12+
[patch.crates-io]
13+
s2n-tls-sys = { path = "../extended/s2n-tls-sys" }
14+
s2n-tls = { path = "../extended/s2n-tls" }

bindings/rust/standard/tls-harness/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ openssl = { version = "0.10.73", features = ["vendored"] }
1515
openssl-sys = "0.9.109"
1616
byteorder = "1.5.0"
1717

18+
brass-aphid-wire-decryption = "0.0.1"
19+
brass-aphid-wire-messages = "0.0.1"
20+
1821
[dev-dependencies]
1922
# env_logger and log are used to enable logging for rustls, which can help with
2023
# debugging interop failures

bindings/rust/standard/tls-harness/src/cohort/openssl.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use openssl::ssl::{
1313
use std::{
1414
error::Error,
1515
io::{Read, Write},
16+
rc::Rc,
1617
sync::{Arc, Mutex},
1718
};
1819

@@ -46,7 +47,7 @@ impl TlsConnection for OpenSslConnection {
4647
fn new_from_config(
4748
mode: harness::Mode,
4849
config: &Self::Config,
49-
io: &harness::TestPairIO,
50+
io: &Rc<harness::TestPairIO>,
5051
) -> Result<Self, Box<dyn Error>> {
5152
// check if there is a session ticket available
5253
// a session ticket will only be available if the Config was created

bindings/rust/standard/tls-harness/src/cohort/rustls.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use rustls::{
1616
use std::{
1717
error::Error,
1818
io::{BufReader, Read, Write},
19+
rc::Rc,
1920
sync::Arc,
2021
};
2122

@@ -134,7 +135,7 @@ impl TlsConnection for RustlsConnection {
134135
fn new_from_config(
135136
mode: harness::Mode,
136137
config: &Self::Config,
137-
io: &harness::TestPairIO,
138+
io: &Rc<harness::TestPairIO>,
138139
) -> Result<Self, Box<dyn Error>> {
139140
let connection = match config {
140141
RustlsConfig::Client(config) => Connection::Client(ClientConnection::new(

bindings/rust/standard/tls-harness/src/cohort/s2n_tls.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::{
1818
io::ErrorKind,
1919
os::raw::c_int,
2020
pin::Pin,
21+
rc::Rc,
2122
sync::{Arc, Mutex},
2223
task::Poll,
2324
};
@@ -137,7 +138,7 @@ impl TlsConnection for S2NConnection {
137138
fn new_from_config(
138139
mode: harness::Mode,
139140
config: &Self::Config,
140-
io: &harness::TestPairIO,
141+
io: &Rc<harness::TestPairIO>,
141142
) -> Result<Self, Box<dyn Error>> {
142143
let s2n_mode = match mode {
143144
Mode::Client => s2n_tls::enums::Mode::Client,

bindings/rust/standard/tls-harness/src/harness/io.rs

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,51 +9,66 @@ use std::{
99
sync::atomic::{AtomicBool, Ordering},
1010
};
1111

12+
use brass_aphid_wire_decryption::decryption::stream_decrypter::StreamDecrypter;
1213
use byteorder::{BigEndian, ReadBytesExt};
1314

15+
use crate::Mode;
16+
1417
pub type LocalDataBuffer = RefCell<VecDeque<u8>>;
1518

1619
#[derive(Debug, Default)]
1720
pub struct TestPairIO {
1821
/// a data buffer that the server writes to and the client reads from
19-
pub server_tx_stream: Rc<LocalDataBuffer>,
22+
pub server_tx_stream: LocalDataBuffer,
2023
/// a data buffer that the client writes to and the server reads from
21-
pub client_tx_stream: Rc<LocalDataBuffer>,
22-
23-
pub recording: Rc<AtomicBool>,
24-
pub client_tx_transcript: Rc<RefCell<Vec<u8>>>,
25-
pub server_tx_transcript: Rc<RefCell<Vec<u8>>>,
24+
pub client_tx_stream: LocalDataBuffer,
25+
26+
/// indicates whether all client/server writes should be stored to the
27+
/// transcript fields
28+
pub recording: AtomicBool,
29+
pub client_tx_transcript: RefCell<Vec<u8>>,
30+
pub server_tx_transcript: RefCell<Vec<u8>>,
31+
/// [`Self::enable_decryption`] will initialize the stream decrypter, which
32+
/// allows tests to make assertions on the decrypted TLS transcript.
33+
///
34+
/// This is especially useful for TLS 1.3 where much of the handshake is encrypted.
35+
pub decrypter: RefCell<Option<StreamDecrypter>>,
2636
}
2737

2838
impl TestPairIO {
29-
pub fn client_view(&self) -> ViewIO {
39+
pub fn client_view(self: &Rc<Self>) -> ViewIO {
3040
ViewIO {
31-
send_ctx: self.client_tx_stream.clone(),
32-
recv_ctx: self.server_tx_stream.clone(),
33-
recording: self.recording.clone(),
34-
send_transcript: self.client_tx_transcript.clone(),
41+
identity: Mode::Client,
42+
io: Rc::clone(self),
3543
}
3644
}
3745

38-
pub fn server_view(&self) -> ViewIO {
46+
pub fn server_view(self: &Rc<Self>) -> ViewIO {
3947
ViewIO {
40-
send_ctx: self.server_tx_stream.clone(),
41-
recv_ctx: self.client_tx_stream.clone(),
42-
recording: self.recording.clone(),
43-
send_transcript: self.server_tx_transcript.clone(),
48+
identity: Mode::Server,
49+
io: Rc::clone(self),
4450
}
4551
}
4652

47-
pub fn enable_recording(&mut self) {
53+
pub fn enable_recording(&self) {
4854
self.recording.store(true, Ordering::Relaxed);
4955
}
5056

57+
/// Note: this is only available for TLS 1.3
58+
pub fn enable_decryption(
59+
&self,
60+
keys: brass_aphid_wire_decryption::decryption::key_manager::KeyManager,
61+
) {
62+
let stream_decrypter = StreamDecrypter::new(keys);
63+
*self.decrypter.borrow_mut() = Some(stream_decrypter);
64+
}
65+
5166
pub fn client_record_sizes(&self) -> Vec<u16> {
52-
Self::record_sizes(self.client_tx_transcript.as_ref().borrow().as_slice()).unwrap()
67+
Self::record_sizes(self.client_tx_transcript.borrow().as_slice()).unwrap()
5368
}
5469

5570
pub fn server_record_sizes(&self) -> Vec<u16> {
56-
Self::record_sizes(self.server_tx_transcript.as_ref().borrow().as_slice()).unwrap()
71+
Self::record_sizes(self.server_tx_transcript.borrow().as_slice()).unwrap()
5772
}
5873

5974
/// Return a list of the record sizes contained in `buffer`.
@@ -80,20 +95,37 @@ impl TestPairIO {
8095
///
8196
/// This view is client/server specific, and notably implements the read and write
8297
/// traits.
83-
///
84-
// This struct is used by Openssl and Rustls which both rely on a "stream" abstraction
85-
// which implements read and write. This is not used by s2n-tls, which relies on
86-
// lower level callbacks.
8798
pub struct ViewIO {
88-
pub send_ctx: Rc<LocalDataBuffer>,
89-
pub recv_ctx: Rc<LocalDataBuffer>,
90-
pub recording: Rc<AtomicBool>,
91-
pub send_transcript: Rc<RefCell<Vec<u8>>>,
99+
pub identity: Mode,
100+
pub io: Rc<TestPairIO>,
101+
}
102+
103+
impl ViewIO {
104+
fn recv_ctx(&self) -> &LocalDataBuffer {
105+
match self.identity {
106+
Mode::Client => &self.io.server_tx_stream,
107+
Mode::Server => &self.io.client_tx_stream,
108+
}
109+
}
110+
111+
fn send_ctx(&self) -> &LocalDataBuffer {
112+
match self.identity {
113+
Mode::Client => &self.io.client_tx_stream,
114+
Mode::Server => &self.io.server_tx_stream,
115+
}
116+
}
117+
118+
fn send_transcript(&self) -> &RefCell<Vec<u8>> {
119+
match self.identity {
120+
Mode::Client => &self.io.client_tx_transcript,
121+
Mode::Server => &self.io.server_tx_transcript,
122+
}
123+
}
92124
}
93125

94126
impl std::io::Read for ViewIO {
95127
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
96-
let res = self.recv_ctx.borrow_mut().read(buf);
128+
let res = self.recv_ctx().borrow_mut().read(buf);
97129
if let Ok(0) = res {
98130
// We are "faking" a TcpStream, where a read of length 0 indicates
99131
// EoF. That is incorrect for this scenario. Instead we return WouldBlock
@@ -107,15 +139,29 @@ impl std::io::Read for ViewIO {
107139

108140
impl std::io::Write for ViewIO {
109141
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
110-
let write_result = self.send_ctx.borrow_mut().write(buf);
111-
112-
if self.recording.load(Ordering::Relaxed) {
113-
if let Ok(written) = write_result {
114-
self.send_transcript
142+
let write_result = self.send_ctx().borrow_mut().write(buf);
143+
144+
// if we successfully wrote data, we need to record it in the various test
145+
// utilities.
146+
if let Ok(written) = write_result {
147+
// recorder
148+
if self.io.recording.load(Ordering::Relaxed) {
149+
self.send_transcript()
115150
.borrow_mut()
116151
.write_all(&buf[0..written])
117152
.unwrap();
118153
}
154+
155+
// decrypter
156+
let mut decrypter = self.io.decrypter.borrow_mut();
157+
if let Some(decrypter) = decrypter.as_mut() {
158+
let wire_mode = match self.identity {
159+
Mode::Client => brass_aphid_wire_decryption::decryption::Mode::Client,
160+
Mode::Server => brass_aphid_wire_decryption::decryption::Mode::Server,
161+
};
162+
decrypter.record_tx(&buf[0..written], wire_mode);
163+
decrypter.decrypt_records(wire_mode).unwrap();
164+
}
119165
}
120166

121167
write_result

bindings/rust/standard/tls-harness/src/harness/mod.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
mod io;
44
pub use io::{LocalDataBuffer, TestPairIO, ViewIO};
55

6-
use std::{error::Error, fmt::Debug, fs::read_to_string};
6+
use std::{error::Error, fmt::Debug, fs::read_to_string, rc::Rc};
77
use strum::EnumIter;
88

99
#[derive(Clone, Copy, EnumIter)]
@@ -86,7 +86,7 @@ pub trait TlsConnection: Sized {
8686
fn new_from_config(
8787
mode: Mode,
8888
config: &Self::Config,
89-
io: &TestPairIO,
89+
io: &Rc<TestPairIO>,
9090
) -> Result<Self, Box<dyn Error>>;
9191

9292
/// Run one handshake step: receive msgs from other connection, process, and send new msgs
@@ -159,7 +159,7 @@ pub trait TlsConfigBuilder {
159159
pub struct TlsConnPair<C, S> {
160160
pub client: C,
161161
pub server: S,
162-
pub io: TestPairIO,
162+
pub io: Rc<TestPairIO>,
163163
}
164164

165165
impl<C, S> TlsConnPair<C, S>
@@ -168,7 +168,7 @@ where
168168
S: TlsConnection,
169169
{
170170
pub fn from_configs(client_config: &C::Config, server_config: &S::Config) -> Self {
171-
let io = TestPairIO::default();
171+
let io = Rc::new(TestPairIO::default());
172172
let client = C::new_from_config(Mode::Client, client_config, &io).unwrap();
173173
let server = S::new_from_config(Mode::Server, server_config, &io).unwrap();
174174
Self { client, server, io }
@@ -199,7 +199,6 @@ where
199199
self.client.handshake()?;
200200
self.server.handshake()?;
201201
}
202-
203202
Ok(())
204203
}
205204

bindings/rust/standard/tls-harness/src/test_utilities.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,60 @@ where
3636
conn_pair.round_trip_transfer(&mut data).unwrap();
3737
conn_pair.shutdown().unwrap();
3838
}
39+
40+
#[cfg(test)]
41+
mod tests {
42+
use super::*;
43+
use brass_aphid_wire_decryption::decryption::key_manager::KeyManager;
44+
use brass_aphid_wire_messages::iana;
45+
use openssl::ssl::SslContextBuilder;
46+
47+
use crate::{
48+
cohort::{OpenSslConnection, S2NConnection},
49+
TlsConnPair,
50+
};
51+
52+
/// make sure that the brass-aphid-wire integration is able to correctly decrypt
53+
/// TLS 1.3 conversations
54+
#[test]
55+
fn tls13_decryption() {
56+
let key_manager = KeyManager::new();
57+
let mut pair: TlsConnPair<OpenSslConnection, S2NConnection> = {
58+
let mut configs =
59+
TlsConfigBuilderPair::<SslContextBuilder, s2n_tls::config::Builder>::default();
60+
configs
61+
.server
62+
.set_security_policy(&s2n_tls::security::DEFAULT)
63+
.unwrap();
64+
key_manager.enable_s2n_logging(&mut configs.server);
65+
configs.client.set_groups_list("x448:secp256r1").unwrap();
66+
configs.connection_pair()
67+
};
68+
pair.io.enable_recording();
69+
pair.io.enable_decryption(key_manager.clone());
70+
71+
pair.handshake().unwrap();
72+
pair.shutdown().unwrap();
73+
74+
let transcript = pair.io.decrypter.borrow();
75+
let transcript = transcript.as_ref().unwrap().transcript();
76+
let ch = transcript.client_hellos().first().unwrap().clone();
77+
let client_key_shares = ch.key_share().unwrap();
78+
let client_supported_groups = ch.supported_groups().unwrap();
79+
80+
// openssl sends the most preferred group as a key share
81+
assert_eq!(client_key_shares, vec![iana::constants::x448]);
82+
assert_eq!(
83+
client_supported_groups,
84+
vec![iana::constants::x448, iana::constants::secp256r1]
85+
);
86+
87+
// s2n-tls selects secp256r1
88+
let sh = transcript.server_hello();
89+
let selected_group = sh.selected_group().unwrap().unwrap();
90+
assert_eq!(selected_group, iana::constants::secp256r1);
91+
92+
// there was an HRR
93+
assert!(transcript.hello_retry_request().is_some());
94+
}
95+
}

0 commit comments

Comments
 (0)