Skip to content

Commit 7557224

Browse files
committed
Add quinn keys and example
1 parent b82e85a commit 7557224

File tree

6 files changed

+325
-16
lines changed

6 files changed

+325
-16
lines changed

Cargo.toml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,47 @@ readme = "README.md"
1111

1212
[dependencies]
1313
foreign-types-shared = { version = "0.1.1", optional = true }
14+
once_cell = "1.8.0"
1415
openssl = "0.10.68"
1516
openssl-sys = "0.9.104"
17+
quinn = { version = "0.11.6", optional = true, default-features = false }
1618
rustls = { version = "0.23.0", default-features = false }
1719
rustls-webpki = { version = "0.102.2", default-features = false }
18-
once_cell = "1.8.0"
1920

2021
[features]
2122
default = ["tls12"]
2223
fips = []
2324
tls12 = ["rustls/tls12", "foreign-types-shared"]
25+
quinn = ["dep:quinn"]
2426

2527
[dev-dependencies]
28+
env_logger = "0.11.5"
2629
hex = "0.4.3"
30+
quinn = { version = "0.11.6", default-features = false, features = [
31+
"runtime-tokio",
32+
"log",
33+
] }
34+
quinn-proto = { version = "0.11.9", default-features = false, features = [
35+
"rustls",
36+
] }
2737
rcgen = { version = "0.13.1", default-features = false, features = [
2838
"aws_lc_rs",
2939
] }
3040
rstest = "0.23.0"
3141
# Use aws_lc_rs to test our provider
3242
rustls = { version = "0.23.0", features = ["aws_lc_rs"] }
3343
rustls-pemfile = "2"
44+
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }
3445
webpki-roots = "0.26"
3546
wycheproof = { version = "0.6.0", default-features = false, features = [
3647
"aead",
3748
"hkdf",
3849
] }
50+
51+
[[example]]
52+
name = "quinn"
53+
path = "examples/quinn.rs"
54+
required-features = ["quinn"]
55+
56+
[package.metadata.docs.rs]
57+
features = ["quinn"]

examples/quinn.rs

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
//! Example of creating Quinn client and server endpoints.
2+
//!
3+
//! Adapted from https://github.com/quinn-rs/quinn/blob/main/quinn/examples/single_socket.rs
4+
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
5+
use std::{error::Error, sync::Arc};
6+
7+
use quinn::crypto::rustls::{QuicClientConfig, QuicServerConfig};
8+
use quinn::{default_runtime, ClientConfig, Endpoint, EndpointConfig, ServerConfig};
9+
use rustls::client::WebPkiServerVerifier;
10+
use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer};
11+
use rustls_openssl::cipher_suite::TLS13_AES_128_GCM_SHA256;
12+
use rustls_openssl::quinn::reset_key;
13+
14+
/// Constructs a QUIC endpoint configured for use a client only.
15+
///
16+
/// ## Args
17+
/// - bind_addr: address to bind to.
18+
/// - server_certs: list of trusted certificates.
19+
fn make_client_endpoint(
20+
bind_addr: SocketAddr,
21+
server_certs: &[&[u8]],
22+
) -> Result<Endpoint, Box<dyn Error + Send + Sync + 'static>> {
23+
let client_cfg = configure_client(server_certs)?;
24+
let endpoint_config = EndpointConfig::new(reset_key());
25+
let socket = UdpSocket::bind(bind_addr).unwrap();
26+
let mut endpoint =
27+
Endpoint::new(endpoint_config, None, socket, default_runtime().unwrap()).unwrap();
28+
endpoint.set_default_client_config(client_cfg);
29+
Ok(endpoint)
30+
}
31+
32+
/// Constructs a QUIC endpoint configured to listen for incoming connections on a certain address
33+
/// and port.
34+
///
35+
/// ## Returns
36+
/// - a stream of incoming QUIC connections
37+
/// - server certificate serialized into DER format
38+
fn make_server_endpoint(
39+
bind_addr: SocketAddr,
40+
) -> Result<(Endpoint, CertificateDer<'static>), Box<dyn Error + Send + Sync + 'static>> {
41+
let (server_config, server_cert) = configure_server()?;
42+
let endpoint_config = EndpointConfig::new(reset_key());
43+
let socket = UdpSocket::bind(bind_addr).unwrap();
44+
let endpoint = Endpoint::new(
45+
endpoint_config,
46+
Some(server_config),
47+
socket,
48+
default_runtime().unwrap(),
49+
)?;
50+
Ok((endpoint, server_cert))
51+
}
52+
53+
/// Builds default quinn client config and trusts given certificates.
54+
///
55+
/// ## Args
56+
///
57+
/// - server_certs: a list of trusted certificates in DER format.
58+
fn configure_client(
59+
server_certs: &[&[u8]],
60+
) -> Result<ClientConfig, Box<dyn Error + Send + Sync + 'static>> {
61+
let mut certs = rustls::RootCertStore::empty();
62+
for cert in server_certs {
63+
certs.add(CertificateDer::from(*cert))?;
64+
}
65+
66+
let verifier = WebPkiServerVerifier::builder_with_provider(
67+
Arc::new(certs),
68+
Arc::new(rustls_openssl::default_provider()),
69+
)
70+
.build()
71+
.unwrap();
72+
73+
let mut rustls_config =
74+
rustls::ClientConfig::builder_with_provider(Arc::new(rustls_openssl::default_provider()))
75+
.with_protocol_versions(&[&rustls::version::TLS13])
76+
.unwrap()
77+
.dangerous()
78+
.with_custom_certificate_verifier(verifier)
79+
.with_no_client_auth();
80+
rustls_config.enable_early_data = true;
81+
82+
let suite = TLS13_AES_128_GCM_SHA256
83+
.tls13()
84+
.unwrap()
85+
.quic_suite()
86+
.unwrap();
87+
let quic_client_config =
88+
QuicClientConfig::with_initial(Arc::new(rustls_config), suite).unwrap();
89+
Ok(ClientConfig::new(Arc::new(quic_client_config)))
90+
}
91+
92+
/// Returns default server configuration along with its certificate.
93+
fn configure_server(
94+
) -> Result<(ServerConfig, CertificateDer<'static>), Box<dyn Error + Send + Sync + 'static>> {
95+
let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap();
96+
let cert_der = CertificateDer::from(cert.cert);
97+
let priv_key = PrivatePkcs8KeyDer::from(cert.key_pair.serialize_der());
98+
99+
let mut rustls_config =
100+
rustls::ServerConfig::builder_with_provider(Arc::new(rustls_openssl::default_provider()))
101+
.with_protocol_versions(&[&rustls::version::TLS13])
102+
.unwrap()
103+
.with_no_client_auth()
104+
.with_single_cert(vec![cert_der.clone()], priv_key.into())?;
105+
rustls_config.max_early_data_size = u32::MAX;
106+
107+
let quic_server_config = QuicServerConfig::with_initial(
108+
Arc::new(rustls_config),
109+
TLS13_AES_128_GCM_SHA256
110+
.tls13()
111+
.unwrap()
112+
.quic_suite()
113+
.unwrap(),
114+
)
115+
.unwrap();
116+
117+
let mut server_config = ServerConfig::new(
118+
Arc::new(quic_server_config),
119+
rustls_openssl::quinn::handshake_token_key(),
120+
);
121+
122+
let transport_config = Arc::get_mut(&mut server_config.transport).unwrap();
123+
transport_config.max_concurrent_uni_streams(0_u8.into());
124+
125+
Ok((server_config, cert_der))
126+
}
127+
128+
#[tokio::main]
129+
async fn main() -> Result<(), Box<dyn Error + Send + Sync + 'static>> {
130+
env_logger::init();
131+
let addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5000);
132+
let addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5001);
133+
let addr3 = SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 5002);
134+
let server1_cert = run_server(addr1)?;
135+
let server2_cert = run_server(addr2)?;
136+
let server3_cert = run_server(addr3)?;
137+
138+
let client = make_client_endpoint(
139+
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 0),
140+
&[&server1_cert, &server2_cert, &server3_cert],
141+
)?;
142+
143+
// connect to multiple endpoints using the same socket/endpoint
144+
tokio::join!(
145+
run_client(&client, addr1),
146+
run_client(&client, addr2),
147+
run_client(&client, addr3),
148+
);
149+
150+
// Make sure the server has a chance to clean up
151+
client.wait_idle().await;
152+
153+
Ok(())
154+
}
155+
156+
/// Runs a QUIC server bound to given address and returns server certificate.
157+
fn run_server(
158+
addr: SocketAddr,
159+
) -> Result<CertificateDer<'static>, Box<dyn Error + Send + Sync + 'static>> {
160+
let (endpoint, server_cert) = make_server_endpoint(addr)?;
161+
// accept a single connection
162+
tokio::spawn(async move {
163+
let connection = endpoint.accept().await.unwrap().await.unwrap();
164+
println!(
165+
"[server] incoming connection: addr={}",
166+
connection.remote_address()
167+
);
168+
});
169+
170+
Ok(server_cert)
171+
}
172+
173+
/// Attempt QUIC connection with the given server address.
174+
async fn run_client(endpoint: &Endpoint, server_addr: SocketAddr) {
175+
let connect = endpoint.connect(server_addr, "localhost").unwrap();
176+
let connection = connect.await.unwrap();
177+
println!("[client] connected: addr={}", connection.remote_address());
178+
}

src/hkdf.rs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,18 @@ const MAX_MD_SIZE: usize = openssl_sys::EVP_MAX_MD_SIZE as usize;
1414
/// HKDF implementation using HMAC with the specified Hash Algorithm
1515
pub(crate) struct Hkdf(pub(crate) HashAlgorithm);
1616

17-
struct HkdfExpander {
17+
pub(crate) struct HkdfExpander {
1818
private_key: [u8; MAX_MD_SIZE],
1919
size: usize,
2020
hash: HashAlgorithm,
2121
}
2222

23-
impl RustlsHkdf for Hkdf {
24-
fn extract_from_zero_ikm(&self, salt: Option<&[u8]>) -> Box<dyn RustlsHkdfExpander> {
25-
let hash_size = self.0.output_len();
26-
let secret = [0u8; MAX_MD_SIZE];
27-
self.extract_from_secret(salt, &secret[..hash_size])
28-
}
29-
30-
fn extract_from_secret(
23+
impl Hkdf {
24+
pub(crate) fn extract_from_secret_internal(
3125
&self,
3226
salt: Option<&[u8]>,
3327
secret: &[u8],
34-
) -> Box<dyn RustlsHkdfExpander> {
28+
) -> HkdfExpander {
3529
let hash_size = self.0.output_len();
3630
let mut private_key = [0u8; MAX_MD_SIZE];
3731
PkeyCtx::new_id(Id::HKDF)
@@ -50,11 +44,27 @@ impl RustlsHkdf for Hkdf {
5044
})
5145
.expect("HDKF-Extract failed");
5246

53-
Box::new(HkdfExpander {
47+
HkdfExpander {
5448
private_key,
5549
size: hash_size,
5650
hash: self.0,
57-
})
51+
}
52+
}
53+
}
54+
55+
impl RustlsHkdf for Hkdf {
56+
fn extract_from_zero_ikm(&self, salt: Option<&[u8]>) -> Box<dyn RustlsHkdfExpander> {
57+
let hash_size = self.0.output_len();
58+
let secret = [0u8; MAX_MD_SIZE];
59+
self.extract_from_secret(salt, &secret[..hash_size])
60+
}
61+
62+
fn extract_from_secret(
63+
&self,
64+
salt: Option<&[u8]>,
65+
secret: &[u8],
66+
) -> Box<dyn RustlsHkdfExpander> {
67+
Box::new(self.extract_from_secret_internal(salt, secret))
5868
}
5969

6070
fn expander_for_okm(&self, okm: &OkmBlock) -> Box<dyn RustlsHkdfExpander> {

src/hmac.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ use rustls::crypto::hash::Hash as _;
55
use rustls::crypto::hmac::{Key, Tag};
66

77
pub(crate) struct Hmac(pub(crate) Algorithm);
8-
struct HmacKey {
9-
key: PKey<Private>,
10-
hash: Algorithm,
8+
pub(crate) struct HmacKey {
9+
pub(crate) key: PKey<Private>,
10+
pub(crate) hash: Algorithm,
1111
}
1212

1313
impl rustls::crypto::hmac::Hmac for Hmac {

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ mod kx;
6868
#[cfg(feature = "tls12")]
6969
mod prf;
7070
mod quic;
71+
72+
#[cfg(feature = "quinn")]
73+
pub mod quinn;
7174
mod signer;
7275
#[cfg(feature = "tls12")]
7376
mod tls12;

0 commit comments

Comments
 (0)