Skip to content

Commit c3e1885

Browse files
authored
Merge pull request #55 from sippejw/quic
QUIC Initial Packet Decryption and Parsing
2 parents 6a55b41 + 50bb314 commit c3e1885

File tree

3 files changed

+324
-5
lines changed

3 files changed

+324
-5
lines changed

core/Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ thiserror = "1.0"
4646
tls-parser = { git = "https://github.com/stanford-esrg/tls-parser" }
4747
toml = "0.5.11"
4848
x509-parser = "0.13.2"
49+
ring = "0.17.8"
50+
aes-gcm = "0.10.3"
4951

5052
[features]
5153
timing = []
+321
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
1+
// crypto.rs contains the cryptograpic functions needed to derive QUIC
2+
// initial keys. These keys can be used to remove header protection and
3+
// decrypt QUIC initial packets. This file is heavily based on Cloudflare's
4+
// crypto module in their Rust implementation of QUIC, known as Quiche.
5+
// Therefore, the original license from https://github.com/cloudflare/quiche/blob/master/quiche/src/crypto/mod.rs is below:
6+
7+
// Copyright (C) 2018-2019, Cloudflare, Inc.
8+
// All rights reserved.
9+
//
10+
// Redistribution and use in source and binary forms, with or without
11+
// modification, are permitted provided that the following conditions are
12+
// met:
13+
//
14+
// * Redistributions of source code must retain the above copyright notice,
15+
// this list of conditions and the following disclaimer.
16+
//
17+
// * Redistributions in binary form must reproduce the above copyright
18+
// notice, this list of conditions and the following disclaimer in the
19+
// documentation and/or other materials provided with the distribution.
20+
//
21+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
22+
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
23+
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24+
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
25+
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26+
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27+
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
28+
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
29+
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
30+
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31+
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32+
33+
use aes_gcm::{
34+
aead::{AeadMutInPlace, KeyInit},
35+
Aes128Gcm, Nonce, Tag,
36+
};
37+
use ring::aead;
38+
use ring::hkdf;
39+
use serde::Serialize;
40+
41+
use crate::protocols::stream::quic::parser::QuicVersion;
42+
use crate::protocols::stream::quic::QuicError;
43+
44+
// The algorithm enum defines the available
45+
// cryptographic algorithms used to secure
46+
// QUIC packets.
47+
#[derive(Copy, Clone, Debug, Serialize)]
48+
pub enum Algorithm {
49+
AES128GCM,
50+
}
51+
52+
impl Algorithm {
53+
fn get_ring_hp(self) -> &'static aead::quic::Algorithm {
54+
match self {
55+
Algorithm::AES128GCM => &aead::quic::AES_128,
56+
}
57+
}
58+
59+
fn get_ring_digest(self) -> hkdf::Algorithm {
60+
match self {
61+
Algorithm::AES128GCM => hkdf::HKDF_SHA256,
62+
}
63+
}
64+
65+
pub fn key_len(self) -> usize {
66+
match self {
67+
Algorithm::AES128GCM => 16,
68+
}
69+
}
70+
71+
pub fn tag_len(self) -> usize {
72+
match self {
73+
Algorithm::AES128GCM => 16,
74+
}
75+
}
76+
77+
pub fn nonce_len(self) -> usize {
78+
match self {
79+
Algorithm::AES128GCM => 12,
80+
}
81+
}
82+
}
83+
84+
// The Open struct gives a return value
85+
// that contains all of the components
86+
// needed for HP removal and decryption
87+
#[derive(Serialize)]
88+
pub struct Open {
89+
alg: Algorithm,
90+
91+
initial_key: Vec<u8>,
92+
93+
#[serde(skip_serializing)]
94+
hp_key: aead::quic::HeaderProtectionKey,
95+
96+
iv: Vec<u8>,
97+
}
98+
99+
impl Open {
100+
pub fn new(alg: Algorithm, key: &[u8], iv: &[u8], hp_key: &[u8]) -> Result<Open, QuicError> {
101+
Ok(Open {
102+
alg,
103+
104+
initial_key: key.to_vec(),
105+
106+
hp_key: aead::quic::HeaderProtectionKey::new(alg.get_ring_hp(), hp_key)
107+
.map_err(|_| QuicError::CryptoFail)?,
108+
109+
iv: iv.to_vec(),
110+
})
111+
}
112+
113+
pub fn open_with_u64_counter(
114+
&self,
115+
counter: u64,
116+
ad: &[u8],
117+
buf: &mut [u8],
118+
tag: &[u8],
119+
) -> Result<Vec<u8>, QuicError> {
120+
let mut cipher = match self.alg {
121+
Algorithm::AES128GCM => {
122+
let res = Aes128Gcm::new_from_slice(&self.initial_key);
123+
if res.is_err() {
124+
return Err(QuicError::CryptoFail);
125+
}
126+
res.unwrap()
127+
}
128+
};
129+
let rc = cipher.decrypt_in_place_detached(
130+
&Nonce::clone_from_slice(&make_nonce(&self.iv, counter)),
131+
ad,
132+
buf,
133+
&Tag::clone_from_slice(tag),
134+
);
135+
136+
if rc.is_err() {
137+
return Err(QuicError::CryptoFail);
138+
}
139+
140+
Ok(buf.to_vec())
141+
}
142+
143+
pub fn new_mask(&self, sample: &[u8]) -> Result<[u8; 5], QuicError> {
144+
let mask = self
145+
.hp_key
146+
.new_mask(sample)
147+
.map_err(|_| QuicError::CryptoFail)?;
148+
149+
Ok(mask)
150+
}
151+
152+
pub fn alg(&self) -> Algorithm {
153+
self.alg
154+
}
155+
156+
pub fn sample_len(&self) -> usize {
157+
self.hp_key.algorithm().sample_len()
158+
}
159+
}
160+
impl std::fmt::Debug for Open {
161+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162+
f.debug_struct("Point")
163+
.field("alg", &self.alg)
164+
.field("iv", &self.iv)
165+
.finish()
166+
}
167+
}
168+
169+
pub fn calc_init_keys(cid: &[u8], version: u32) -> Result<[Open; 2], QuicError> {
170+
let aead = Algorithm::AES128GCM;
171+
let key_len = aead.key_len();
172+
let nonce_len = aead.nonce_len();
173+
let initial_secret = derive_initial_secret(cid, version);
174+
175+
let mut secret = [0; 32];
176+
let mut client_key = vec![0; key_len];
177+
let mut client_iv = vec![0; nonce_len];
178+
let mut client_hp_key = vec![0; key_len];
179+
180+
derive_client_initial_secret(&initial_secret, &mut secret)?;
181+
derive_pkt_key(aead, &secret, &mut client_key)?;
182+
derive_pkt_iv(aead, &secret, &mut client_iv)?;
183+
derive_hdr_key(aead, &secret, &mut client_hp_key)?;
184+
185+
// Server.
186+
let mut server_key = vec![0; key_len];
187+
let mut server_iv = vec![0; nonce_len];
188+
let mut server_hp_key = vec![0; key_len];
189+
190+
derive_server_initial_secret(&initial_secret, &mut secret)?;
191+
derive_pkt_key(aead, &secret, &mut server_key)?;
192+
derive_pkt_iv(aead, &secret, &mut server_iv)?;
193+
derive_hdr_key(aead, &secret, &mut server_hp_key)?;
194+
195+
Ok([
196+
Open::new(aead, &client_key, &client_iv, &client_hp_key)?,
197+
Open::new(aead, &server_key, &server_iv, &server_hp_key)?,
198+
])
199+
}
200+
201+
fn derive_initial_secret(secret: &[u8], version: u32) -> hkdf::Prk {
202+
const INITIAL_SALT_RFC9000: [u8; 20] = [
203+
0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c,
204+
0xad, 0xcc, 0xbb, 0x7f, 0x0a,
205+
];
206+
207+
const INITIAL_SALT_RFC9369: [u8; 20] = [
208+
0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, 0x9d,
209+
0xcb, 0xf9, 0xbd, 0x2e, 0xd9,
210+
];
211+
212+
const INITIAL_SALT_DRAFT29: [u8; 20] = [
213+
0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, 0x11,
214+
0xe0, 0x43, 0x90, 0xa8, 0x99,
215+
];
216+
217+
const INITIAL_SALT_DRAFT27: [u8; 20] = [
218+
0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, 0x63,
219+
0x65, 0xbe, 0xf9, 0xf5, 0x02,
220+
];
221+
222+
let salt = match QuicVersion::from_u32(version) {
223+
QuicVersion::Rfc9000 => &INITIAL_SALT_RFC9000,
224+
QuicVersion::Rfc9369 => &INITIAL_SALT_RFC9369,
225+
QuicVersion::Draft29 => &INITIAL_SALT_DRAFT29,
226+
QuicVersion::Draft27 | QuicVersion::Draft28 | QuicVersion::Mvfst27 => &INITIAL_SALT_DRAFT27,
227+
_ => &INITIAL_SALT_RFC9000,
228+
};
229+
230+
let salt = hkdf::Salt::new(hkdf::HKDF_SHA256, salt);
231+
salt.extract(secret)
232+
}
233+
234+
fn derive_client_initial_secret(prk: &hkdf::Prk, out: &mut [u8]) -> Result<(), QuicError> {
235+
const LABEL: &[u8] = b"client in";
236+
hkdf_expand_label(prk, LABEL, out)
237+
}
238+
239+
fn derive_server_initial_secret(prk: &hkdf::Prk, out: &mut [u8]) -> Result<(), QuicError> {
240+
const LABEL: &[u8] = b"server in";
241+
hkdf_expand_label(prk, LABEL, out)
242+
}
243+
244+
pub fn derive_hdr_key(aead: Algorithm, secret: &[u8], out: &mut [u8]) -> Result<(), QuicError> {
245+
const LABEL: &[u8] = b"quic hp";
246+
247+
let key_len = aead.key_len();
248+
249+
if key_len > out.len() {
250+
return Err(QuicError::CryptoFail);
251+
}
252+
253+
let secret = hkdf::Prk::new_less_safe(aead.get_ring_digest(), secret);
254+
hkdf_expand_label(&secret, LABEL, &mut out[..key_len])
255+
}
256+
257+
pub fn derive_pkt_key(aead: Algorithm, secret: &[u8], out: &mut [u8]) -> Result<(), QuicError> {
258+
const LABEL: &[u8] = b"quic key";
259+
260+
let key_len = aead.key_len();
261+
262+
if key_len > out.len() {
263+
return Err(QuicError::CryptoFail);
264+
}
265+
266+
let secret = hkdf::Prk::new_less_safe(aead.get_ring_digest(), secret);
267+
hkdf_expand_label(&secret, LABEL, &mut out[..key_len])
268+
}
269+
270+
pub fn derive_pkt_iv(aead: Algorithm, secret: &[u8], out: &mut [u8]) -> Result<(), QuicError> {
271+
const LABEL: &[u8] = b"quic iv";
272+
273+
let nonce_len = aead.nonce_len();
274+
275+
if nonce_len > out.len() {
276+
return Err(QuicError::CryptoFail);
277+
}
278+
279+
let secret = hkdf::Prk::new_less_safe(aead.get_ring_digest(), secret);
280+
hkdf_expand_label(&secret, LABEL, &mut out[..nonce_len])
281+
}
282+
283+
fn hkdf_expand_label(prk: &hkdf::Prk, label: &[u8], out: &mut [u8]) -> Result<(), QuicError> {
284+
const LABEL_PREFIX: &[u8] = b"tls13 ";
285+
286+
let out_len = (out.len() as u16).to_be_bytes();
287+
let label_len = (LABEL_PREFIX.len() + label.len()) as u8;
288+
289+
let info = [&out_len, &[label_len][..], LABEL_PREFIX, label, &[0][..]];
290+
291+
prk.expand(&info, ArbitraryOutputLen(out.len()))
292+
.map_err(|_| QuicError::CryptoFail)?
293+
.fill(out)
294+
.map_err(|_| QuicError::CryptoFail)?;
295+
296+
Ok(())
297+
}
298+
299+
fn make_nonce(iv: &[u8], counter: u64) -> [u8; aead::NONCE_LEN] {
300+
let mut nonce = [0; aead::NONCE_LEN];
301+
nonce.copy_from_slice(iv);
302+
303+
// XOR the last bytes of the IV with the counter. This is equivalent to
304+
// left-padding the counter with zero bytes.
305+
for (a, b) in nonce[4..].iter_mut().zip(counter.to_be_bytes().iter()) {
306+
*a ^= b;
307+
}
308+
309+
nonce
310+
}
311+
312+
// The ring HKDF expand() API does not accept an arbitrary output length, so we
313+
// need to hide the `usize` length as part of a type that implements the trait
314+
// `ring::hkdf::KeyType` in order to trick ring into accepting it.
315+
struct ArbitraryOutputLen(usize);
316+
317+
impl hkdf::KeyType for ArbitraryOutputLen {
318+
fn len(&self) -> usize {
319+
self.0
320+
}
321+
}

core/src/protocols/stream/quic/mod.rs

+1-5
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,6 @@ impl QuicPacket {
105105

106106
/// Returns the number of bytes in the payload of the Quic packet
107107
pub fn payload_bytes_count(&self) -> u64 {
108-
if let Some(count) = self.payload_bytes_count {
109-
count
110-
} else {
111-
0
112-
}
108+
self.payload_bytes_count.unwrap_or_default()
113109
}
114110
}

0 commit comments

Comments
 (0)