Skip to content

Commit e4839ef

Browse files
Add the certififcate chain to TlsSession
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, but none of the API is actually exposed outside of s2n-quic-core, so this is all unstable if we want to revise in the future.
1 parent 7752afb commit e4839ef

File tree

7 files changed

+177
-0
lines changed

7 files changed

+177
-0
lines changed

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

+15
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ impl TlsExportError {
3535
}
3636
}
3737

38+
#[derive(Debug)]
39+
#[non_exhaustive]
40+
pub enum ChainError {
41+
#[non_exhaustive]
42+
Failure,
43+
}
44+
45+
impl ChainError {
46+
pub fn failure() -> Self {
47+
ChainError::Failure
48+
}
49+
}
50+
3851
pub trait TlsSession: Send {
3952
/// See <https://datatracker.ietf.org/doc/html/rfc5705> and <https://www.rfc-editor.org/rfc/rfc8446>.
4053
fn tls_exporter(
@@ -45,6 +58,8 @@ pub trait TlsSession: Send {
4558
) -> Result<(), TlsExportError>;
4659

4760
fn cipher_suite(&self) -> CipherSuite;
61+
62+
fn peer_cert_chain_der(&self) -> Result<Vec<Vec<u8>>, ChainError>;
4863
}
4964

5065
//= 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

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

44
use crate::{connection, endpoint};
5+
use alloc::vec::Vec;
56
use core::{ops::RangeInclusive, time::Duration};
67

78
mod generated;
@@ -149,6 +150,12 @@ impl<'a> TlsSession<'a> {
149150
self.session.tls_exporter(label, context, output)
150151
}
151152

153+
// Currently intended only for unstable usage
154+
#[doc(hidden)]
155+
pub fn peer_cert_chain_der(&self) -> Result<Vec<Vec<u8>>, crate::crypto::tls::ChainError> {
156+
self.session.peer_cert_chain_der()
157+
}
158+
152159
pub fn cipher_suite(&self) -> crate::event::api::CipherSuite {
153160
self.session.cipher_suite().into_event()
154161
}

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)