Skip to content

Commit 9108258

Browse files
committed
Add quinn keys and example
1 parent 349427a commit 9108258

File tree

7 files changed

+348
-14
lines changed

7 files changed

+348
-14
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,21 @@ jobs:
4747
run: cargo clippy --all-targets -- -D warnings
4848
- name: cargo clippy --no-default-features (warnings)
4949
run: cargo clippy --no-default-features --all-targets -- -D warnings
50+
examples:
51+
name: Run examples
52+
runs-on: ubuntu-latest
53+
steps:
54+
- name: Check out repository
55+
uses: actions/checkout@v4
56+
- name: Install toolchain
57+
uses: dtolnay/rust-toolchain@v1
58+
with:
59+
toolchain: stable
60+
- name: Cache build artifacts
61+
uses: Swatinem/rust-cache@v2
62+
- name: cargo run --example quinn
63+
run: cargo run --example quinn --features quinn
64+
5065

5166
test-fips-1-1-1:
5267
name: Test using FIPS openssl 1.1.1

Cargo.toml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,29 +11,48 @@ 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
zeroize = "1.8.1"
2021

2122
[features]
2223
default = ["tls12"]
2324
fips = []
2425
tls12 = ["rustls/tls12", "foreign-types-shared"]
26+
quinn = ["dep:quinn"]
2527

2628
[dev-dependencies]
29+
env_logger = "0.11.5"
2730
hex = "0.4.3"
31+
quinn = { version = "0.11.6", default-features = false, features = [
32+
"runtime-tokio",
33+
"log",
34+
] }
35+
quinn-proto = { version = "0.11.9", default-features = false, features = [
36+
"rustls",
37+
] }
2838
rcgen = { version = "0.13.1", default-features = false, features = [
2939
"aws_lc_rs",
3040
] }
3141
rstest = "0.23.0"
3242
# Use aws_lc_rs to test our provider
3343
rustls = { version = "0.23.0", features = ["aws_lc_rs"] }
3444
rustls-pemfile = "2"
45+
tokio = { version = "1.41.1", features = ["macros", "rt-multi-thread"] }
3546
webpki-roots = "0.26"
3647
wycheproof = { version = "0.6.0", default-features = false, features = [
3748
"aead",
3849
"hkdf",
3950
] }
51+
52+
[[example]]
53+
name = "quinn"
54+
path = "examples/quinn.rs"
55+
required-features = ["quinn"]
56+
57+
[package.metadata.docs.rs]
58+
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
@@ -15,24 +15,18 @@ const MAX_MD_SIZE: usize = openssl_sys::EVP_MAX_MD_SIZE as usize;
1515
/// HKDF implementation using HMAC with the specified Hash Algorithm
1616
pub(crate) struct Hkdf(pub(crate) HashAlgorithm);
1717

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

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

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

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

src/hmac.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use crate::hash::Algorithm;
22
use openssl::pkey::{PKey, Private};
33
use openssl::sign::Signer;
4+
#[cfg(feature = "quinn")]
5+
use openssl::sign::Verifier;
46
use rustls::crypto::hash::Hash as _;
57
use rustls::crypto::hmac::{Key, Tag};
68

79
pub(crate) struct Hmac(pub(crate) Algorithm);
8-
struct HmacKey {
10+
pub(crate) struct HmacKey {
911
key: PKey<Private>,
1012
hash: Algorithm,
1113
}
@@ -49,3 +51,41 @@ impl Key for HmacKey {
4951
self.hash.output_len()
5052
}
5153
}
54+
55+
#[cfg(feature = "quinn")]
56+
impl HmacKey {
57+
pub(crate) fn sha256(key: PKey<Private>) -> Self {
58+
Self {
59+
key,
60+
hash: Algorithm::SHA256,
61+
}
62+
}
63+
}
64+
65+
#[cfg(feature = "quinn")]
66+
impl quinn::crypto::HmacKey for HmacKey {
67+
fn sign(&self, data: &[u8], signature_out: &mut [u8]) {
68+
let tag = rustls::crypto::hmac::Key::sign(self, &[data]);
69+
signature_out.copy_from_slice(tag.as_ref());
70+
}
71+
72+
fn signature_len(&self) -> usize {
73+
self.tag_len()
74+
}
75+
76+
fn verify(&self, data: &[u8], signature: &[u8]) -> Result<(), quinn::crypto::CryptoError> {
77+
Verifier::new(self.hash.message_digest(), &self.key)
78+
.and_then(|mut verifier| {
79+
verifier.update(data)?;
80+
verifier.verify(signature)
81+
})
82+
.map_err(|_| quinn::crypto::CryptoError)
83+
.and_then(|valid| {
84+
if valid {
85+
Ok(())
86+
} else {
87+
Err(quinn::crypto::CryptoError)
88+
}
89+
})
90+
}
91+
}

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)