Skip to content

Commit fe96450

Browse files
feat(s2n-quic): Add the certififcate chain to TlsSession (#2349)
This allows users to query the certificate chain when in the TlsSession event callbacks (for dc or for on_tls_exporter_ready). The API added here allocates a Vec<Vec<u8>> which is plausibly quite wasteful, so the API is for now doc(hidden) to avoid stabilizing it.
1 parent 7752afb commit fe96450

File tree

7 files changed

+182
-0
lines changed

7 files changed

+182
-0
lines changed

quic/s2n-quic-core/src/crypto/tls.rs

+18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
#[cfg(feature = "alloc")]
5+
use alloc::vec::Vec;
46
#[cfg(feature = "alloc")]
57
pub use bytes::{Bytes, BytesMut};
68
use core::fmt::Debug;
@@ -35,6 +37,19 @@ impl TlsExportError {
3537
}
3638
}
3739

40+
#[derive(Debug)]
41+
#[non_exhaustive]
42+
pub enum ChainError {
43+
#[non_exhaustive]
44+
Failure,
45+
}
46+
47+
impl ChainError {
48+
pub fn failure() -> Self {
49+
ChainError::Failure
50+
}
51+
}
52+
3853
pub trait TlsSession: Send {
3954
/// See <https://datatracker.ietf.org/doc/html/rfc5705> and <https://www.rfc-editor.org/rfc/rfc8446>.
4055
fn tls_exporter(
@@ -45,6 +60,9 @@ pub trait TlsSession: Send {
4560
) -> Result<(), TlsExportError>;
4661

4762
fn cipher_suite(&self) -> CipherSuite;
63+
64+
#[cfg(feature = "alloc")]
65+
fn peer_cert_chain_der(&self) -> Result<Vec<Vec<u8>>, ChainError>;
4866
}
4967

5068
//= https://www.rfc-editor.org/rfc/rfc9000#section-4

quic/s2n-quic-core/src/crypto/tls/testing.rs

+4
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ impl TlsSession for Session {
123123
fn cipher_suite(&self) -> CipherSuite {
124124
CipherSuite::TLS_AES_128_GCM_SHA256
125125
}
126+
127+
fn peer_cert_chain_der(&self) -> Result<Vec<Vec<u8>>, tls::ChainError> {
128+
Err(tls::ChainError::failure())
129+
}
126130
}
127131

128132
#[derive(Debug)]

quic/s2n-quic-core/src/event.rs

+9
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
use crate::{connection, endpoint};
5+
#[cfg(feature = "alloc")]
6+
use alloc::vec::Vec;
57
use core::{ops::RangeInclusive, time::Duration};
68

79
mod generated;
@@ -149,6 +151,13 @@ impl<'a> TlsSession<'a> {
149151
self.session.tls_exporter(label, context, output)
150152
}
151153

154+
// Currently intended only for unstable usage
155+
#[doc(hidden)]
156+
#[cfg(feature = "alloc")]
157+
pub fn peer_cert_chain_der(&self) -> Result<Vec<Vec<u8>>, crate::crypto::tls::ChainError> {
158+
self.session.peer_cert_chain_der()
159+
}
160+
152161
pub fn cipher_suite(&self) -> crate::event::api::CipherSuite {
153162
self.session.cipher_suite().into_event()
154163
}

quic/s2n-quic-rustls/src/session.rs

+11
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ impl tls::TlsSession for Session {
5858
CipherSuite::Unknown
5959
}
6060
}
61+
62+
fn peer_cert_chain_der(&self) -> Result<Vec<Vec<u8>>, tls::ChainError> {
63+
let err = tls::ChainError::failure();
64+
Ok(self
65+
.connection
66+
.peer_certificates()
67+
.ok_or(err)?
68+
.iter()
69+
.map(|v| v.to_vec())
70+
.collect())
71+
}
6172
}
6273

6374
impl fmt::Debug for Session {

quic/s2n-quic-tls/src/session.rs

+10
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ impl tls::TlsSession for Session {
107107
fn cipher_suite(&self) -> CipherSuite {
108108
self.state.cipher_suite()
109109
}
110+
111+
fn peer_cert_chain_der(&self) -> Result<Vec<Vec<u8>>, tls::ChainError> {
112+
self.connection
113+
.peer_cert_chain()
114+
.map_err(|_| tls::ChainError::failure())?
115+
.iter()
116+
.map(|v| Ok(v?.der()?.to_vec()))
117+
.collect::<Result<Vec<Vec<u8>>, s2n_tls::error::Error>>()
118+
.map_err(|_| tls::ChainError::failure())
119+
}
110120
}
111121

112122
impl tls::Session for Session {

quic/s2n-quic/src/tests.rs

+2
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ mod skip_packets;
4545
// build options than s2n-tls. We should build the rustls provider with
4646
// mTLS enabled and remove the `cfg(target_os("windows"))`.
4747
#[cfg(not(target_os = "windows"))]
48+
mod chain;
49+
#[cfg(not(target_os = "windows"))]
4850
mod client_handshake_confirm;
4951
#[cfg(not(target_os = "windows"))]
5052
mod dc;

quic/s2n-quic/src/tests/chain.rs

+128
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
//! This module shows an example of an event provider that accesses certificate chains
5+
//! from QUIC connections on both client and server.
6+
7+
use super::*;
8+
use crate::provider::event::events::{self, ConnectionInfo, ConnectionMeta, Subscriber};
9+
10+
struct Chain;
11+
12+
#[derive(Default)]
13+
struct ChainContext {
14+
chain: Option<Vec<Vec<u8>>>,
15+
sender: Option<tokio::sync::mpsc::Sender<Vec<Vec<u8>>>>,
16+
}
17+
18+
impl Subscriber for Chain {
19+
type ConnectionContext = ChainContext;
20+
21+
#[inline]
22+
fn create_connection_context(
23+
&mut self,
24+
_: &ConnectionMeta,
25+
_info: &ConnectionInfo,
26+
) -> Self::ConnectionContext {
27+
ChainContext::default()
28+
}
29+
30+
fn on_tls_exporter_ready(
31+
&mut self,
32+
context: &mut Self::ConnectionContext,
33+
_meta: &ConnectionMeta,
34+
event: &events::TlsExporterReady,
35+
) {
36+
if let Some(sender) = context.sender.take() {
37+
sender
38+
.blocking_send(event.session.peer_cert_chain_der().unwrap())
39+
.unwrap();
40+
} else {
41+
context.chain = Some(event.session.peer_cert_chain_der().unwrap());
42+
}
43+
}
44+
}
45+
46+
fn start_server(
47+
mut server: Server,
48+
server_chain: tokio::sync::mpsc::Sender<Vec<Vec<u8>>>,
49+
) -> crate::provider::io::testing::Result<SocketAddr> {
50+
let server_addr = server.local_addr()?;
51+
52+
// accept connections and echo back
53+
spawn(async move {
54+
while let Some(mut connection) = server.accept().await {
55+
let chain = connection
56+
.query_event_context_mut(|ctx: &mut ChainContext| {
57+
if let Some(chain) = ctx.chain.take() {
58+
Some(chain)
59+
} else {
60+
ctx.sender = Some(server_chain.clone());
61+
None
62+
}
63+
})
64+
.unwrap();
65+
if let Some(chain) = chain {
66+
server_chain.send(chain).await.unwrap();
67+
}
68+
}
69+
});
70+
71+
Ok(server_addr)
72+
}
73+
74+
fn tls_test<C>(f: fn(crate::Connection, Vec<Vec<u8>>) -> C)
75+
where
76+
C: 'static + core::future::Future<Output = ()> + Send,
77+
{
78+
let model = Model::default();
79+
model.set_delay(Duration::from_millis(50));
80+
81+
test(model, |handle| {
82+
let server = Server::builder()
83+
.with_io(handle.builder().build()?)?
84+
.with_tls(build_server_mtls_provider(certificates::MTLS_CA_CERT)?)?
85+
.with_event((Chain, tracing_events()))?
86+
.start()?;
87+
let (send, server_chain) = tokio::sync::mpsc::channel(1);
88+
let server_chain = Arc::new(tokio::sync::Mutex::new(server_chain));
89+
90+
let addr = start_server(server, send)?;
91+
92+
let client = Client::builder()
93+
.with_io(handle.builder().build().unwrap())?
94+
.with_tls(build_client_mtls_provider(certificates::MTLS_CA_CERT)?)?
95+
.with_event((Chain, tracing_events()))?
96+
.start()?;
97+
98+
// show it working for several connections
99+
for _ in 0..10 {
100+
let client = client.clone();
101+
let server_chain = server_chain.clone();
102+
primary::spawn(async move {
103+
let connect = Connect::new(addr).with_server_name("localhost");
104+
let conn = client.connect(connect).await.unwrap();
105+
delay(Duration::from_millis(100)).await;
106+
let server_chain = server_chain.lock().await.recv().await.unwrap();
107+
f(conn, server_chain).await;
108+
});
109+
}
110+
111+
Ok(addr)
112+
})
113+
.unwrap();
114+
}
115+
116+
#[test]
117+
fn happy_case() {
118+
tls_test(|mut conn, server_chain| async move {
119+
let client_chain = conn
120+
.query_event_context_mut(|ctx: &mut ChainContext| ctx.chain.take().unwrap())
121+
.unwrap();
122+
// these are DER-encoded and we lack nice conversion functions, so just assert some simple
123+
// properties.
124+
assert!(server_chain.len() > 1);
125+
assert!(client_chain.len() > 1);
126+
assert_ne!(server_chain, client_chain);
127+
});
128+
}

0 commit comments

Comments
 (0)