Skip to main content

quic_parser/
crypto.rs

1/* src/crypto.rs */
2
3use aes::Aes128;
4use aes::cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray};
5
6use crate::error::Error;
7use crate::header::InitialHeader;
8
9const QUIC_V1: u32 = 0x0000_0001;
10const QUIC_V2: u32 = 0x6b33_43cf;
11
12const INITIAL_SALT_V1: [u8; 20] = [
13	0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad,
14	0xcc, 0xbb, 0x7f, 0x0a,
15];
16
17const INITIAL_SALT_V2: [u8; 20] = [
18	0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb,
19	0xf9, 0xbd, 0x2e, 0xd9,
20];
21
22struct VersionParams {
23	salt: &'static [u8; 20],
24	key_label: &'static [u8],
25	iv_label: &'static [u8],
26	hp_label: &'static [u8],
27}
28
29fn version_params(version: u32) -> Result<VersionParams, Error> {
30	match version {
31		QUIC_V1 => Ok(VersionParams {
32			salt: &INITIAL_SALT_V1,
33			key_label: b"quic key",
34			iv_label: b"quic iv",
35			hp_label: b"quic hp",
36		}),
37		QUIC_V2 => Ok(VersionParams {
38			salt: &INITIAL_SALT_V2,
39			key_label: b"quicv2 key",
40			iv_label: b"quicv2 iv",
41			hp_label: b"quicv2 hp",
42		}),
43		_ => Err(Error::UnsupportedVersion(version)),
44	}
45}
46
47fn build_hkdf_label(label: &[u8], context: &[u8], len: usize) -> Result<Vec<u8>, Error> {
48	let full_label_len = 6 + label.len();
49	let total = 2 + 1 + full_label_len + 1 + context.len();
50	let mut out = Vec::with_capacity(total);
51	let len_u16 = u16::try_from(len)
52		.map_err(|_| Error::DecryptionFailed("HKDF output length overflow".into()))?;
53	let label_u8 = u8::try_from(full_label_len)
54		.map_err(|_| Error::DecryptionFailed("HKDF label length overflow".into()))?;
55	let ctx_u8 = u8::try_from(context.len())
56		.map_err(|_| Error::DecryptionFailed("HKDF context length overflow".into()))?;
57	out.extend_from_slice(&len_u16.to_be_bytes());
58	out.push(label_u8);
59	out.extend_from_slice(b"tls13 ");
60	out.extend_from_slice(label);
61	out.push(ctx_u8);
62	out.extend_from_slice(context);
63	Ok(out)
64}
65
66fn remove_header_protection(
67	first_byte: u8,
68	payload: &[u8],
69	hp_key: &[u8],
70) -> Result<(u64, usize, u8), Error> {
71	if payload.len() < 20 {
72		return Err(Error::BufferTooShort {
73			need: 20,
74			have: payload.len(),
75		});
76	}
77
78	let cipher =
79		Aes128::new_from_slice(hp_key).map_err(|e| Error::DecryptionFailed(format!("HP key: {e}")))?;
80	let mut mask = [0u8; 16];
81	mask.copy_from_slice(&payload[4..20]);
82	cipher.encrypt_block(GenericArray::from_mut_slice(&mut mask));
83
84	let unprotected_first = first_byte ^ (mask[0] & 0x0f);
85	let pn_len = usize::from((unprotected_first & 0x03) + 1);
86
87	if pn_len > payload.len() {
88		return Err(Error::BufferTooShort {
89			need: pn_len,
90			have: payload.len(),
91		});
92	}
93
94	let mut pn = 0u64;
95	for i in 0..pn_len {
96		pn = (pn << 8) | u64::from(payload[i] ^ mask[1 + i]);
97	}
98
99	Ok((pn, pn_len, unprotected_first))
100}
101
102#[cfg(feature = "ring")]
103mod backend {
104	use crate::error::Error;
105	use ring::{aead, hkdf};
106
107	struct HkdfLen(usize);
108
109	impl hkdf::KeyType for HkdfLen {
110		fn len(&self) -> usize {
111			self.0
112		}
113	}
114
115	pub(super) fn derive_client_initial_secret(salt: &[u8], dcid: &[u8]) -> Result<Vec<u8>, Error> {
116		let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, salt);
117		let initial_secret = salt.extract(dcid);
118		let label = super::build_hkdf_label(b"client in", &[], 32)?;
119		expand_prk(&initial_secret, &label, 32)
120	}
121
122	pub(super) fn hkdf_expand_label(
123		secret: &[u8],
124		label: &[u8],
125		len: usize,
126	) -> Result<Vec<u8>, Error> {
127		let prk = hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, secret);
128		let info = super::build_hkdf_label(label, &[], len)?;
129		expand_prk(&prk, &info, len)
130	}
131
132	fn expand_prk(prk: &hkdf::Prk, info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
133		let mut out = vec![0u8; len];
134		prk
135			.expand(&[info], HkdfLen(len))
136			.and_then(|okm| okm.fill(&mut out))
137			.map_err(|_| Error::DecryptionFailed("HKDF expand failed".into()))?;
138		Ok(out)
139	}
140
141	pub(super) fn aead_open(
142		key: &[u8],
143		nonce_bytes: &[u8; 12],
144		aad: &[u8],
145		ciphertext: &[u8],
146	) -> Result<Vec<u8>, Error> {
147		let unbound = aead::UnboundKey::new(&aead::AES_128_GCM, key)
148			.map_err(|_| Error::DecryptionFailed("invalid AES-GCM key".into()))?;
149		let opening_key = aead::LessSafeKey::new(unbound);
150		let nonce = aead::Nonce::try_assume_unique_for_key(nonce_bytes)
151			.map_err(|_| Error::DecryptionFailed("invalid nonce".into()))?;
152		let mut buf = ciphertext.to_vec();
153		let plaintext_len = opening_key
154			.open_in_place(nonce, aead::Aad::from(aad), &mut buf)
155			.map_err(|_| Error::DecryptionFailed("AEAD decryption failed".into()))?
156			.len();
157		buf.truncate(plaintext_len);
158		Ok(buf)
159	}
160}
161
162#[cfg(feature = "aws-lc-rs")]
163mod backend {
164	use crate::error::Error;
165	use aws_lc_rs::{aead, hkdf};
166
167	struct HkdfLen(usize);
168
169	impl hkdf::KeyType for HkdfLen {
170		fn len(&self) -> usize {
171			self.0
172		}
173	}
174
175	pub(super) fn derive_client_initial_secret(salt: &[u8], dcid: &[u8]) -> Result<Vec<u8>, Error> {
176		let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, salt);
177		let initial_secret = salt.extract(dcid);
178		let label = super::build_hkdf_label(b"client in", &[], 32)?;
179		expand_prk(&initial_secret, &label, 32)
180	}
181
182	pub(super) fn hkdf_expand_label(
183		secret: &[u8],
184		label: &[u8],
185		len: usize,
186	) -> Result<Vec<u8>, Error> {
187		let prk = hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, secret);
188		let info = super::build_hkdf_label(label, &[], len)?;
189		expand_prk(&prk, &info, len)
190	}
191
192	fn expand_prk(prk: &hkdf::Prk, info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
193		let mut out = vec![0u8; len];
194		prk
195			.expand(&[info], HkdfLen(len))
196			.and_then(|okm| okm.fill(&mut out))
197			.map_err(|_| Error::DecryptionFailed("HKDF expand failed".into()))?;
198		Ok(out)
199	}
200
201	pub(super) fn aead_open(
202		key: &[u8],
203		nonce_bytes: &[u8; 12],
204		aad: &[u8],
205		ciphertext: &[u8],
206	) -> Result<Vec<u8>, Error> {
207		let unbound = aead::UnboundKey::new(&aead::AES_128_GCM, key)
208			.map_err(|_| Error::DecryptionFailed("invalid AES-GCM key".into()))?;
209		let opening_key = aead::LessSafeKey::new(unbound);
210		let nonce = aead::Nonce::try_assume_unique_for_key(nonce_bytes)
211			.map_err(|_| Error::DecryptionFailed("invalid nonce".into()))?;
212		let mut buf = ciphertext.to_vec();
213		let plaintext_len = opening_key
214			.open_in_place(nonce, aead::Aad::from(aad), &mut buf)
215			.map_err(|_| Error::DecryptionFailed("AEAD decryption failed".into()))?
216			.len();
217		buf.truncate(plaintext_len);
218		Ok(buf)
219	}
220}
221
222/// Decrypt a QUIC Initial packet payload.
223///
224/// Performs header protection removal (AES-ECB), key derivation (HKDF-SHA256),
225/// and AEAD decryption (AES-128-GCM). Supports both QUIC v1 (RFC 9001) and
226/// v2 (RFC 9369).
227///
228/// The returned bytes contain the decrypted frames (PADDING, CRYPTO, ACK, etc.).
229///
230/// # Errors
231///
232/// Returns [`Error::UnsupportedVersion`] if the version is not v1 or v2.
233/// Returns [`Error::DecryptionFailed`] if any cryptographic operation fails.
234/// Returns [`Error::BufferTooShort`] if the payload is too short for header
235/// protection removal.
236pub fn decrypt_initial(header: &InitialHeader<'_>) -> Result<Vec<u8>, Error> {
237	let params = version_params(header.version)?;
238
239	let client_secret = backend::derive_client_initial_secret(params.salt, header.dcid)?;
240	let key = backend::hkdf_expand_label(&client_secret, params.key_label, 16)?;
241	let iv = backend::hkdf_expand_label(&client_secret, params.iv_label, 12)?;
242	let hp = backend::hkdf_expand_label(&client_secret, params.hp_label, 16)?;
243
244	let (pn, pn_len, unprotected_first) =
245		remove_header_protection(header.first_byte, header.payload, &hp)?;
246
247	let mut aad = Vec::with_capacity(header.header_bytes.len() + pn_len);
248	aad.push(unprotected_first);
249	aad.extend_from_slice(&header.header_bytes[1..]);
250	for i in 0..pn_len {
251		aad.push((pn >> (8 * (pn_len - 1 - i))) as u8);
252	}
253
254	let mut nonce = <[u8; 12]>::try_from(iv.as_slice())
255		.map_err(|_| Error::DecryptionFailed("unexpected IV length".into()))?;
256	let pn_offset = 12 - pn_len;
257	for i in 0..pn_len {
258		nonce[pn_offset + i] ^= (pn >> (8 * (pn_len - 1 - i))) as u8;
259	}
260
261	let encrypted_payload = &header.payload[pn_len..];
262
263	#[cfg(feature = "tracing")]
264	tracing::debug!(
265		version = header.version,
266		dcid_len = header.dcid.len(),
267		payload_len = encrypted_payload.len(),
268		"decrypting QUIC Initial packet"
269	);
270
271	backend::aead_open(&key, &nonce, &aad, encrypted_payload)
272}