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
2 changes: 1 addition & 1 deletion dc/s2n-quic-dc/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub mod shared;
pub mod socket;
pub(crate) mod tls;

pub use tls::{Conn as TlsConnection, ConnectionBuilder as TlsConnectionBuilder};
pub use tls::{CertificateChain, Conn as TlsConnection, ConnectionBuilder as TlsConnectionBuilder};

#[cfg(any(test, feature = "testing"))]
pub mod testing;
Expand Down
9 changes: 9 additions & 0 deletions dc/s2n-quic-dc/src/stream/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,15 @@ where
self.read.path_secret_id()
}

/// Returns the validated peer certificate chain, if available.
///
/// Currently this is only available for TLS streams, but in the future it may be opt-in
/// exposed for dcQUIC streams (at the cost of memory usage).
#[inline]
pub fn peer_cert_chain(&self) -> Option<&crate::stream::tls::CertificateChain> {
self.read.peer_cert_chain()
}

#[inline]
pub fn protocol(&self) -> socket::Protocol {
self.read.protocol()
Expand Down
4 changes: 4 additions & 0 deletions dc/s2n-quic-dc/src/stream/recv/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ where
Err(_) => None,
}
}

pub fn peer_cert_chain(&self) -> Option<&crate::stream::tls::CertificateChain> {
self.0.shared.s2n_connection.as_ref()?.peer_cert_chain()
}
}

impl<Sub> Inner<Sub>
Expand Down
4 changes: 4 additions & 0 deletions dc/s2n-quic-dc/src/stream/send/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ where
Err(_) => None,
}
}

pub fn peer_cert_chain(&self) -> Option<&crate::stream::tls::CertificateChain> {
self.0.shared.s2n_connection.as_ref()?.peer_cert_chain()
}
}

impl<Sub> Inner<Sub>
Expand Down
18 changes: 16 additions & 2 deletions dc/s2n-quic-dc/src/stream/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,14 @@ use crate::{
},
};

mod cert_chain;

pub use cert_chain::CertificateChain;

pub struct S2nTlsConnection {
socket: Arc<Single<TcpStream>>,
connection: Mutex<(Conn, ReadState)>,
cert_chain: Option<CertificateChain>,
}

struct ReadState {
Expand Down Expand Up @@ -86,6 +91,7 @@ impl S2nTlsConnection {
buffer: bytes::BytesMut::with_capacity(8192),
},
)),
cert_chain: None,
})
}

Expand All @@ -94,7 +100,7 @@ impl S2nTlsConnection {
mut initial_read_buffer: Option<crate::msg::recv::Message>,
) -> io::Result<()> {
std::future::poll_fn(|cx| -> Poll<io::Result<()>> {
let connection = &mut self.connection.get_mut().unwrap().0;
let s2n_connection = &mut self.connection.get_mut().unwrap().0;

let context = NegotiateContext {
socket: &self.socket,
Expand All @@ -103,7 +109,7 @@ impl S2nTlsConnection {
};

let mut connection = CallbackResetGuard {
conn: (**connection).as_mut(),
conn: (**s2n_connection).as_mut(),
reset_write: true,
reset_read: true,
};
Expand Down Expand Up @@ -133,6 +139,10 @@ impl S2nTlsConnection {
}
}

self.cert_chain = Some(CertificateChain::new(
(**s2n_connection).as_mut().peer_cert_chain()?,
)?);

Poll::Ready(Ok(()))
}
Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())),
Expand Down Expand Up @@ -286,6 +296,10 @@ impl S2nTlsConnection {

Ok(())
}

pub(crate) fn peer_cert_chain(&self) -> Option<&CertificateChain> {
self.cert_chain.as_ref()
}
}

/// `NegotiateContext` for poll_negotiate.
Expand Down
29 changes: 29 additions & 0 deletions dc/s2n-quic-dc/src/stream/tls/cert_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

//! Wraps the `s2n_tls::cert_chain` module to avoid placing those s2n-tls types in the public API.
//!
//! Unlike the s2n-tls configuration which is shared across connections, once a connection is
//! established we'd expect to drop the s2n-tls Connection struct eventually. So we'll need our own
//! container for any information that we wish to retain after the connection ends.

#[derive(Clone)]
pub struct CertificateChain {
der_certs: Vec<Vec<u8>>,
}

impl CertificateChain {
pub(crate) fn new(
chain: s2n_tls::cert_chain::CertificateChain<'_>,
) -> Result<Self, s2n_tls::error::Error> {
let mut der_certs = Vec::with_capacity(chain.len());
for cert in chain.iter() {
der_certs.push(cert?.der()?.to_vec());
}
Ok(Self { der_certs })
}

pub fn iter_der(&self) -> impl Iterator<Item = &'_ [u8]> + '_ {
self.der_certs.iter().map(|v| &v[..])
}
}
10 changes: 10 additions & 0 deletions dc/s2n-quic-dc/src/stream/tls/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ async fn check_server(message: &[u8]) {
let acceptor_addr = server.acceptor_addr().unwrap();
tokio::spawn(async move {
while let Ok((mut stream, _)) = server.accept().await {
if let Some(chain) = stream.peer_cert_chain() {
assert_eq!(chain.iter_der().count(), 1);
}

tracing::info!("server accepted stream!");
let mut buffer = vec![];
stream.read_to_end(&mut buffer).await.unwrap();
Expand All @@ -182,6 +186,9 @@ async fn check_server(message: &[u8]) {
)
.await
.unwrap();

assert_eq!(stream.peer_cert_chain().unwrap().iter_der().count(), 1);

let (mut reader, mut writer) = stream.into_split();

writer.write_all_from(&mut &message[..]).await.unwrap();
Expand All @@ -204,6 +211,9 @@ async fn check_server(message: &[u8]) {
)
.await
.unwrap();

assert!(stream.peer_cert_chain().is_none());

let (mut reader, mut writer) = stream.into_split();

writer.write_all_from(&mut &message[..]).await.unwrap();
Expand Down
Loading