1 | #![allow (clippy::duplicate_mod)] |
2 | |
3 | use crate::crypto::cipher::{AeadKey, Iv, Nonce}; |
4 | use crate::error::Error; |
5 | use crate::quic; |
6 | |
7 | use alloc::boxed::Box; |
8 | |
9 | use super::ring_like::aead; |
10 | |
11 | pub(crate) struct HeaderProtectionKey(aead::quic::HeaderProtectionKey); |
12 | |
13 | impl HeaderProtectionKey { |
14 | pub(crate) fn new(key: AeadKey, alg: &'static aead::quic::Algorithm) -> Self { |
15 | Self(aead::quic::HeaderProtectionKey::new(alg, key.as_ref()).unwrap()) |
16 | } |
17 | |
18 | fn xor_in_place( |
19 | &self, |
20 | sample: &[u8], |
21 | first: &mut u8, |
22 | packet_number: &mut [u8], |
23 | masked: bool, |
24 | ) -> Result<(), Error> { |
25 | // This implements "Header Protection Application" almost verbatim. |
26 | // <https://datatracker.ietf.org/doc/html/rfc9001#section-5.4.1> |
27 | |
28 | let mask = self |
29 | .0 |
30 | .new_mask(sample) |
31 | .map_err(|_| Error::General("sample of invalid length" .into()))?; |
32 | |
33 | // The `unwrap()` will not panic because `new_mask` returns a |
34 | // non-empty result. |
35 | let (first_mask, pn_mask) = mask.split_first().unwrap(); |
36 | |
37 | // It is OK for the `mask` to be longer than `packet_number`, |
38 | // but a valid `packet_number` will never be longer than `mask`. |
39 | if packet_number.len() > pn_mask.len() { |
40 | return Err(Error::General("packet number too long" .into())); |
41 | } |
42 | |
43 | // Infallible from this point on. Before this point, `first` and |
44 | // `packet_number` are unchanged. |
45 | |
46 | const LONG_HEADER_FORM: u8 = 0x80; |
47 | let bits = match *first & LONG_HEADER_FORM == LONG_HEADER_FORM { |
48 | true => 0x0f, // Long header: 4 bits masked |
49 | false => 0x1f, // Short header: 5 bits masked |
50 | }; |
51 | |
52 | let first_plain = match masked { |
53 | // When unmasking, use the packet length bits after unmasking |
54 | true => *first ^ (first_mask & bits), |
55 | // When masking, use the packet length bits before masking |
56 | false => *first, |
57 | }; |
58 | let pn_len = (first_plain & 0x03) as usize + 1; |
59 | |
60 | *first ^= first_mask & bits; |
61 | for (dst, m) in packet_number |
62 | .iter_mut() |
63 | .zip(pn_mask) |
64 | .take(pn_len) |
65 | { |
66 | *dst ^= m; |
67 | } |
68 | |
69 | Ok(()) |
70 | } |
71 | } |
72 | |
73 | impl quic::HeaderProtectionKey for HeaderProtectionKey { |
74 | fn encrypt_in_place( |
75 | &self, |
76 | sample: &[u8], |
77 | first: &mut u8, |
78 | packet_number: &mut [u8], |
79 | ) -> Result<(), Error> { |
80 | self.xor_in_place(sample, first, packet_number, masked:false) |
81 | } |
82 | |
83 | fn decrypt_in_place( |
84 | &self, |
85 | sample: &[u8], |
86 | first: &mut u8, |
87 | packet_number: &mut [u8], |
88 | ) -> Result<(), Error> { |
89 | self.xor_in_place(sample, first, packet_number, masked:true) |
90 | } |
91 | |
92 | #[inline ] |
93 | fn sample_len(&self) -> usize { |
94 | self.0.algorithm().sample_len() |
95 | } |
96 | } |
97 | |
98 | pub(crate) struct PacketKey { |
99 | /// Encrypts or decrypts a packet's payload |
100 | key: aead::LessSafeKey, |
101 | /// Computes unique nonces for each packet |
102 | iv: Iv, |
103 | } |
104 | |
105 | impl PacketKey { |
106 | pub(crate) fn new(key: AeadKey, iv: Iv, aead_algorithm: &'static aead::Algorithm) -> Self { |
107 | Self { |
108 | key: aead::LessSafeKey::new( |
109 | key:aead::UnboundKey::new(aead_algorithm, key_bytes:key.as_ref()).unwrap(), |
110 | ), |
111 | iv, |
112 | } |
113 | } |
114 | } |
115 | |
116 | impl quic::PacketKey for PacketKey { |
117 | fn encrypt_in_place( |
118 | &self, |
119 | packet_number: u64, |
120 | header: &[u8], |
121 | payload: &mut [u8], |
122 | ) -> Result<quic::Tag, Error> { |
123 | let aad = aead::Aad::from(header); |
124 | let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, packet_number).0); |
125 | let tag = self |
126 | .key |
127 | .seal_in_place_separate_tag(nonce, aad, payload) |
128 | .map_err(|_| Error::EncryptError)?; |
129 | Ok(quic::Tag::from(tag.as_ref())) |
130 | } |
131 | |
132 | /// Decrypt a QUIC packet |
133 | /// |
134 | /// Takes the packet `header`, which is used as the additional authenticated data, and the |
135 | /// `payload`, which includes the authentication tag. |
136 | /// |
137 | /// If the return value is `Ok`, the decrypted payload can be found in `payload`, up to the |
138 | /// length found in the return value. |
139 | fn decrypt_in_place<'a>( |
140 | &self, |
141 | packet_number: u64, |
142 | header: &[u8], |
143 | payload: &'a mut [u8], |
144 | ) -> Result<&'a [u8], Error> { |
145 | let payload_len = payload.len(); |
146 | let aad = aead::Aad::from(header); |
147 | let nonce = aead::Nonce::assume_unique_for_key(Nonce::new(&self.iv, packet_number).0); |
148 | self.key |
149 | .open_in_place(nonce, aad, payload) |
150 | .map_err(|_| Error::DecryptError)?; |
151 | |
152 | let plain_len = payload_len - self.key.algorithm().tag_len(); |
153 | Ok(&payload[..plain_len]) |
154 | } |
155 | |
156 | /// Tag length for the underlying AEAD algorithm |
157 | #[inline ] |
158 | fn tag_len(&self) -> usize { |
159 | self.key.algorithm().tag_len() |
160 | } |
161 | } |
162 | |
163 | pub(crate) struct KeyBuilder( |
164 | pub(crate) &'static aead::Algorithm, |
165 | pub(crate) &'static aead::quic::Algorithm, |
166 | ); |
167 | |
168 | impl crate::quic::Algorithm for KeyBuilder { |
169 | fn packet_key(&self, key: AeadKey, iv: Iv) -> Box<dyn quic::PacketKey> { |
170 | Box::new(super::quic::PacketKey::new(key, iv, self.0)) |
171 | } |
172 | |
173 | fn header_protection_key(&self, key: AeadKey) -> Box<dyn quic::HeaderProtectionKey> { |
174 | Box::new(super::quic::HeaderProtectionKey::new(key, self.1)) |
175 | } |
176 | |
177 | fn aead_key_len(&self) -> usize { |
178 | self.0.key_len() |
179 | } |
180 | } |
181 | |
182 | #[cfg (test)] |
183 | mod tests { |
184 | use crate::common_state::Side; |
185 | use crate::crypto::tls13::OkmBlock; |
186 | use crate::quic::*; |
187 | use crate::test_provider::tls13::{ |
188 | TLS13_AES_128_GCM_SHA256_INTERNAL, TLS13_CHACHA20_POLY1305_SHA256_INTERNAL, |
189 | }; |
190 | |
191 | fn test_short_packet(version: Version, expected: &[u8]) { |
192 | const PN: u64 = 654360564; |
193 | const SECRET: &[u8] = &[ |
194 | 0x9a, 0xc3, 0x12, 0xa7, 0xf8, 0x77, 0x46, 0x8e, 0xbe, 0x69, 0x42, 0x27, 0x48, 0xad, |
195 | 0x00, 0xa1, 0x54, 0x43, 0xf1, 0x82, 0x03, 0xa0, 0x7d, 0x60, 0x60, 0xf6, 0x88, 0xf3, |
196 | 0x0f, 0x21, 0x63, 0x2b, |
197 | ]; |
198 | |
199 | let secret = OkmBlock::new(SECRET); |
200 | let builder = KeyBuilder::new( |
201 | &secret, |
202 | version, |
203 | TLS13_CHACHA20_POLY1305_SHA256_INTERNAL |
204 | .quic |
205 | .unwrap(), |
206 | TLS13_CHACHA20_POLY1305_SHA256_INTERNAL.hkdf_provider, |
207 | ); |
208 | let packet = builder.packet_key(); |
209 | let hpk = builder.header_protection_key(); |
210 | |
211 | const PLAIN: &[u8] = &[0x42, 0x00, 0xbf, 0xf4, 0x01]; |
212 | |
213 | let mut buf = PLAIN.to_vec(); |
214 | let (header, payload) = buf.split_at_mut(4); |
215 | let tag = packet |
216 | .encrypt_in_place(PN, header, payload) |
217 | .unwrap(); |
218 | buf.extend(tag.as_ref()); |
219 | |
220 | let pn_offset = 1; |
221 | let (header, sample) = buf.split_at_mut(pn_offset + 4); |
222 | let (first, rest) = header.split_at_mut(1); |
223 | let sample = &sample[..hpk.sample_len()]; |
224 | hpk.encrypt_in_place(sample, &mut first[0], dbg!(rest)) |
225 | .unwrap(); |
226 | |
227 | assert_eq!(&buf, expected); |
228 | |
229 | let (header, sample) = buf.split_at_mut(pn_offset + 4); |
230 | let (first, rest) = header.split_at_mut(1); |
231 | let sample = &sample[..hpk.sample_len()]; |
232 | hpk.decrypt_in_place(sample, &mut first[0], rest) |
233 | .unwrap(); |
234 | |
235 | let (header, payload_tag) = buf.split_at_mut(4); |
236 | let plain = packet |
237 | .decrypt_in_place(PN, header, payload_tag) |
238 | .unwrap(); |
239 | |
240 | assert_eq!(plain, &PLAIN[4..]); |
241 | } |
242 | |
243 | #[test ] |
244 | fn short_packet_header_protection() { |
245 | // https://www.rfc-editor.org/rfc/rfc9001.html#name-chacha20-poly1305-short-hea |
246 | test_short_packet( |
247 | Version::V1, |
248 | &[ |
249 | 0x4c, 0xfe, 0x41, 0x89, 0x65, 0x5e, 0x5c, 0xd5, 0x5c, 0x41, 0xf6, 0x90, 0x80, 0x57, |
250 | 0x5d, 0x79, 0x99, 0xc2, 0x5a, 0x5b, 0xfb, |
251 | ], |
252 | ); |
253 | } |
254 | |
255 | #[test ] |
256 | fn key_update_test_vector() { |
257 | fn equal_okm(x: &OkmBlock, y: &OkmBlock) -> bool { |
258 | x.as_ref() == y.as_ref() |
259 | } |
260 | |
261 | let mut secrets = Secrets::new( |
262 | // Constant dummy values for reproducibility |
263 | OkmBlock::new( |
264 | &[ |
265 | 0xb8, 0x76, 0x77, 0x08, 0xf8, 0x77, 0x23, 0x58, 0xa6, 0xea, 0x9f, 0xc4, 0x3e, |
266 | 0x4a, 0xdd, 0x2c, 0x96, 0x1b, 0x3f, 0x52, 0x87, 0xa6, 0xd1, 0x46, 0x7e, 0xe0, |
267 | 0xae, 0xab, 0x33, 0x72, 0x4d, 0xbf, |
268 | ][..], |
269 | ), |
270 | OkmBlock::new( |
271 | &[ |
272 | 0x42, 0xdc, 0x97, 0x21, 0x40, 0xe0, 0xf2, 0xe3, 0x98, 0x45, 0xb7, 0x67, 0x61, |
273 | 0x34, 0x39, 0xdc, 0x67, 0x58, 0xca, 0x43, 0x25, 0x9b, 0x87, 0x85, 0x06, 0x82, |
274 | 0x4e, 0xb1, 0xe4, 0x38, 0xd8, 0x55, |
275 | ][..], |
276 | ), |
277 | TLS13_AES_128_GCM_SHA256_INTERNAL, |
278 | TLS13_AES_128_GCM_SHA256_INTERNAL |
279 | .quic |
280 | .unwrap(), |
281 | Side::Client, |
282 | Version::V1, |
283 | ); |
284 | secrets.update(); |
285 | |
286 | assert!(equal_okm( |
287 | &secrets.client, |
288 | &OkmBlock::new( |
289 | &[ |
290 | 0x42, 0xca, 0xc8, 0xc9, 0x1c, 0xd5, 0xeb, 0x40, 0x68, 0x2e, 0x43, 0x2e, 0xdf, |
291 | 0x2d, 0x2b, 0xe9, 0xf4, 0x1a, 0x52, 0xca, 0x6b, 0x22, 0xd8, 0xe6, 0xcd, 0xb1, |
292 | 0xe8, 0xac, 0xa9, 0x6, 0x1f, 0xce |
293 | ][..] |
294 | ) |
295 | )); |
296 | assert!(equal_okm( |
297 | &secrets.server, |
298 | &OkmBlock::new( |
299 | &[ |
300 | 0xeb, 0x7f, 0x5e, 0x2a, 0x12, 0x3f, 0x40, 0x7d, 0xb4, 0x99, 0xe3, 0x61, 0xca, |
301 | 0xe5, 0x90, 0xd4, 0xd9, 0x92, 0xe1, 0x4b, 0x7a, 0xce, 0x3, 0xc2, 0x44, 0xe0, |
302 | 0x42, 0x21, 0x15, 0xb6, 0xd3, 0x8a |
303 | ][..] |
304 | ) |
305 | )); |
306 | } |
307 | |
308 | #[test ] |
309 | fn short_packet_header_protection_v2() { |
310 | // https://www.ietf.org/archive/id/draft-ietf-quic-v2-10.html#name-chacha20-poly1305-short-head |
311 | test_short_packet( |
312 | Version::V2, |
313 | &[ |
314 | 0x55, 0x58, 0xb1, 0xc6, 0x0a, 0xe7, 0xb6, 0xb9, 0x32, 0xbc, 0x27, 0xd7, 0x86, 0xf4, |
315 | 0xbc, 0x2b, 0xb2, 0x0f, 0x21, 0x62, 0xba, |
316 | ], |
317 | ); |
318 | } |
319 | |
320 | #[test ] |
321 | fn initial_test_vector_v2() { |
322 | // https://www.ietf.org/archive/id/draft-ietf-quic-v2-10.html#name-sample-packet-protection-2 |
323 | let icid = [0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08]; |
324 | let server = Keys::initial( |
325 | Version::V2, |
326 | TLS13_AES_128_GCM_SHA256_INTERNAL, |
327 | TLS13_AES_128_GCM_SHA256_INTERNAL |
328 | .quic |
329 | .unwrap(), |
330 | &icid, |
331 | Side::Server, |
332 | ); |
333 | let mut server_payload = [ |
334 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x40, 0x5a, 0x02, 0x00, 0x00, 0x56, 0x03, |
335 | 0x03, 0xee, 0xfc, 0xe7, 0xf7, 0xb3, 0x7b, 0xa1, 0xd1, 0x63, 0x2e, 0x96, 0x67, 0x78, |
336 | 0x25, 0xdd, 0xf7, 0x39, 0x88, 0xcf, 0xc7, 0x98, 0x25, 0xdf, 0x56, 0x6d, 0xc5, 0x43, |
337 | 0x0b, 0x9a, 0x04, 0x5a, 0x12, 0x00, 0x13, 0x01, 0x00, 0x00, 0x2e, 0x00, 0x33, 0x00, |
338 | 0x24, 0x00, 0x1d, 0x00, 0x20, 0x9d, 0x3c, 0x94, 0x0d, 0x89, 0x69, 0x0b, 0x84, 0xd0, |
339 | 0x8a, 0x60, 0x99, 0x3c, 0x14, 0x4e, 0xca, 0x68, 0x4d, 0x10, 0x81, 0x28, 0x7c, 0x83, |
340 | 0x4d, 0x53, 0x11, 0xbc, 0xf3, 0x2b, 0xb9, 0xda, 0x1a, 0x00, 0x2b, 0x00, 0x02, 0x03, |
341 | 0x04, |
342 | ]; |
343 | let mut server_header = [ |
344 | 0xd1, 0x6b, 0x33, 0x43, 0xcf, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, |
345 | 0xb5, 0x00, 0x40, 0x75, 0x00, 0x01, |
346 | ]; |
347 | let tag = server |
348 | .local |
349 | .packet |
350 | .encrypt_in_place(1, &server_header, &mut server_payload) |
351 | .unwrap(); |
352 | let (first, rest) = server_header.split_at_mut(1); |
353 | let rest_len = rest.len(); |
354 | server |
355 | .local |
356 | .header |
357 | .encrypt_in_place( |
358 | &server_payload[2..18], |
359 | &mut first[0], |
360 | &mut rest[rest_len - 2..], |
361 | ) |
362 | .unwrap(); |
363 | let mut server_packet = server_header.to_vec(); |
364 | server_packet.extend(server_payload); |
365 | server_packet.extend(tag.as_ref()); |
366 | let expected_server_packet = [ |
367 | 0xdc, 0x6b, 0x33, 0x43, 0xcf, 0x00, 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, |
368 | 0xb5, 0x00, 0x40, 0x75, 0xd9, 0x2f, 0xaa, 0xf1, 0x6f, 0x05, 0xd8, 0xa4, 0x39, 0x8c, |
369 | 0x47, 0x08, 0x96, 0x98, 0xba, 0xee, 0xa2, 0x6b, 0x91, 0xeb, 0x76, 0x1d, 0x9b, 0x89, |
370 | 0x23, 0x7b, 0xbf, 0x87, 0x26, 0x30, 0x17, 0x91, 0x53, 0x58, 0x23, 0x00, 0x35, 0xf7, |
371 | 0xfd, 0x39, 0x45, 0xd8, 0x89, 0x65, 0xcf, 0x17, 0xf9, 0xaf, 0x6e, 0x16, 0x88, 0x6c, |
372 | 0x61, 0xbf, 0xc7, 0x03, 0x10, 0x6f, 0xba, 0xf3, 0xcb, 0x4c, 0xfa, 0x52, 0x38, 0x2d, |
373 | 0xd1, 0x6a, 0x39, 0x3e, 0x42, 0x75, 0x75, 0x07, 0x69, 0x80, 0x75, 0xb2, 0xc9, 0x84, |
374 | 0xc7, 0x07, 0xf0, 0xa0, 0x81, 0x2d, 0x8c, 0xd5, 0xa6, 0x88, 0x1e, 0xaf, 0x21, 0xce, |
375 | 0xda, 0x98, 0xf4, 0xbd, 0x23, 0xf6, 0xfe, 0x1a, 0x3e, 0x2c, 0x43, 0xed, 0xd9, 0xce, |
376 | 0x7c, 0xa8, 0x4b, 0xed, 0x85, 0x21, 0xe2, 0xe1, 0x40, |
377 | ]; |
378 | assert_eq!(server_packet[..], expected_server_packet[..]); |
379 | } |
380 | } |
381 | |