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) -> Vec<u8> {
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	out.extend_from_slice(&(len as u16).to_be_bytes());
52	out.push(full_label_len as u8);
53	out.extend_from_slice(b"tls13 ");
54	out.extend_from_slice(label);
55	out.push(context.len() as u8);
56	out.extend_from_slice(context);
57	out
58}
59
60fn remove_header_protection(
61	first_byte: u8,
62	payload: &[u8],
63	hp_key: &[u8],
64) -> Result<(u64, usize, u8), Error> {
65	if payload.len() < 20 {
66		return Err(Error::BufferTooShort {
67			need: 20,
68			have: payload.len(),
69		});
70	}
71
72	let cipher =
73		Aes128::new_from_slice(hp_key).map_err(|e| Error::DecryptionFailed(format!("HP key: {e}")))?;
74	let mut mask = [0u8; 16];
75	mask.copy_from_slice(&payload[4..20]);
76	cipher.encrypt_block(GenericArray::from_mut_slice(&mut mask));
77
78	let unprotected_first = first_byte ^ (mask[0] & 0x0f);
79	let pn_len = usize::from((unprotected_first & 0x03) + 1);
80
81	if pn_len > payload.len() {
82		return Err(Error::BufferTooShort {
83			need: pn_len,
84			have: payload.len(),
85		});
86	}
87
88	let mut pn = 0u64;
89	for i in 0..pn_len {
90		pn = (pn << 8) | u64::from(payload[i] ^ mask[1 + i]);
91	}
92
93	Ok((pn, pn_len, unprotected_first))
94}
95
96#[cfg(feature = "ring")]
97mod backend {
98	use crate::error::Error;
99	use ring::{aead, hkdf};
100
101	struct HkdfLen(usize);
102
103	impl hkdf::KeyType for HkdfLen {
104		fn len(&self) -> usize {
105			self.0
106		}
107	}
108
109	pub(super) fn derive_client_initial_secret(salt: &[u8], dcid: &[u8]) -> Result<Vec<u8>, Error> {
110		let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, salt);
111		let initial_secret = salt.extract(dcid);
112		let label = super::build_hkdf_label(b"client in", &[], 32);
113		expand_prk(&initial_secret, &label, 32)
114	}
115
116	pub(super) fn hkdf_expand_label(
117		secret: &[u8],
118		label: &[u8],
119		len: usize,
120	) -> Result<Vec<u8>, Error> {
121		let prk = hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, secret);
122		let info = super::build_hkdf_label(label, &[], len);
123		expand_prk(&prk, &info, len)
124	}
125
126	fn expand_prk(prk: &hkdf::Prk, info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
127		let mut out = vec![0u8; len];
128		prk
129			.expand(&[info], HkdfLen(len))
130			.and_then(|okm| okm.fill(&mut out))
131			.map_err(|_| Error::DecryptionFailed("HKDF expand failed".into()))?;
132		Ok(out)
133	}
134
135	pub(super) fn aead_open(
136		key: &[u8],
137		nonce_bytes: &[u8; 12],
138		aad: &[u8],
139		ciphertext: &[u8],
140	) -> Result<Vec<u8>, Error> {
141		let unbound = aead::UnboundKey::new(&aead::AES_128_GCM, key)
142			.map_err(|_| Error::DecryptionFailed("invalid AES-GCM key".into()))?;
143		let opening_key = aead::LessSafeKey::new(unbound);
144		let nonce = aead::Nonce::try_assume_unique_for_key(nonce_bytes)
145			.map_err(|_| Error::DecryptionFailed("invalid nonce".into()))?;
146		let mut buf = ciphertext.to_vec();
147		let plaintext_len = opening_key
148			.open_in_place(nonce, aead::Aad::from(aad), &mut buf)
149			.map_err(|_| Error::DecryptionFailed("AEAD decryption failed".into()))?
150			.len();
151		buf.truncate(plaintext_len);
152		Ok(buf)
153	}
154}
155
156#[cfg(feature = "aws-lc-rs")]
157mod backend {
158	use crate::error::Error;
159	use aws_lc_rs::{aead, hkdf};
160
161	struct HkdfLen(usize);
162
163	impl hkdf::KeyType for HkdfLen {
164		fn len(&self) -> usize {
165			self.0
166		}
167	}
168
169	pub(super) fn derive_client_initial_secret(salt: &[u8], dcid: &[u8]) -> Result<Vec<u8>, Error> {
170		let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, salt);
171		let initial_secret = salt.extract(dcid);
172		let label = super::build_hkdf_label(b"client in", &[], 32);
173		expand_prk(&initial_secret, &label, 32)
174	}
175
176	pub(super) fn hkdf_expand_label(
177		secret: &[u8],
178		label: &[u8],
179		len: usize,
180	) -> Result<Vec<u8>, Error> {
181		let prk = hkdf::Prk::new_less_safe(hkdf::HKDF_SHA256, secret);
182		let info = super::build_hkdf_label(label, &[], len);
183		expand_prk(&prk, &info, len)
184	}
185
186	fn expand_prk(prk: &hkdf::Prk, info: &[u8], len: usize) -> Result<Vec<u8>, Error> {
187		let mut out = vec![0u8; len];
188		prk
189			.expand(&[info], HkdfLen(len))
190			.and_then(|okm| okm.fill(&mut out))
191			.map_err(|_| Error::DecryptionFailed("HKDF expand failed".into()))?;
192		Ok(out)
193	}
194
195	pub(super) fn aead_open(
196		key: &[u8],
197		nonce_bytes: &[u8; 12],
198		aad: &[u8],
199		ciphertext: &[u8],
200	) -> Result<Vec<u8>, Error> {
201		let unbound = aead::UnboundKey::new(&aead::AES_128_GCM, key)
202			.map_err(|_| Error::DecryptionFailed("invalid AES-GCM key".into()))?;
203		let opening_key = aead::LessSafeKey::new(unbound);
204		let nonce = aead::Nonce::try_assume_unique_for_key(nonce_bytes)
205			.map_err(|_| Error::DecryptionFailed("invalid nonce".into()))?;
206		let mut buf = ciphertext.to_vec();
207		let plaintext_len = opening_key
208			.open_in_place(nonce, aead::Aad::from(aad), &mut buf)
209			.map_err(|_| Error::DecryptionFailed("AEAD decryption failed".into()))?
210			.len();
211		buf.truncate(plaintext_len);
212		Ok(buf)
213	}
214}
215
216/// Decrypt a QUIC Initial packet payload.
217///
218/// Performs header protection removal (AES-ECB), key derivation (HKDF-SHA256),
219/// and AEAD decryption (AES-128-GCM). Supports both QUIC v1 (RFC 9001) and
220/// v2 (RFC 9369).
221///
222/// The returned bytes contain the decrypted frames (PADDING, CRYPTO, ACK, etc.).
223///
224/// # Errors
225///
226/// Returns [`Error::UnsupportedVersion`] if the version is not v1 or v2.
227/// Returns [`Error::DecryptionFailed`] if any cryptographic operation fails.
228/// Returns [`Error::BufferTooShort`] if the payload is too short for header
229/// protection removal.
230pub fn decrypt_initial(packet: &[u8], header: &InitialHeader<'_>) -> Result<Vec<u8>, Error> {
231	let _ = packet;
232	let params = version_params(header.version)?;
233
234	let client_secret = backend::derive_client_initial_secret(params.salt, header.dcid)?;
235	let key = backend::hkdf_expand_label(&client_secret, params.key_label, 16)?;
236	let iv = backend::hkdf_expand_label(&client_secret, params.iv_label, 12)?;
237	let hp = backend::hkdf_expand_label(&client_secret, params.hp_label, 16)?;
238
239	let (pn, pn_len, unprotected_first) =
240		remove_header_protection(header.first_byte, header.payload, &hp)?;
241
242	let mut aad = Vec::with_capacity(header.header_bytes.len() + pn_len);
243	aad.push(unprotected_first);
244	aad.extend_from_slice(&header.header_bytes[1..]);
245	for i in 0..pn_len {
246		aad.push((pn >> (8 * (pn_len - 1 - i))) as u8);
247	}
248
249	let mut nonce = <[u8; 12]>::try_from(iv.as_slice())
250		.map_err(|_| Error::DecryptionFailed("unexpected IV length".into()))?;
251	let pn_offset = 12 - pn_len;
252	for i in 0..pn_len {
253		nonce[pn_offset + i] ^= (pn >> (8 * (pn_len - 1 - i))) as u8;
254	}
255
256	let encrypted_payload = &header.payload[pn_len..];
257
258	#[cfg(feature = "tracing")]
259	tracing::debug!(
260		version = header.version,
261		dcid_len = header.dcid.len(),
262		payload_len = encrypted_payload.len(),
263		"decrypting QUIC Initial packet"
264	);
265
266	backend::aead_open(&key, &nonce, &aad, encrypted_payload)
267}