/src/suricata/rust/src/quic/crypto.rs
Line | Count | Source |
1 | | /* Copyright (C) 2021 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | |
18 | | use aes::cipher::{BlockEncrypt, KeyInit}; |
19 | | use aes::Aes128; |
20 | | use aes_gcm::aead::AeadInPlace; |
21 | | use aes_gcm::Aes128Gcm; |
22 | | use hkdf::Hkdf; |
23 | | use sha2::Sha256; |
24 | | use std::convert::TryInto; |
25 | | |
26 | | pub const AES128_KEY_LEN: usize = 16; |
27 | | pub const AES128_TAG_LEN: usize = 16; |
28 | | pub const AES128_IV_LEN: usize = 12; |
29 | | |
30 | | pub struct HeaderProtectionKey(Aes128); |
31 | | |
32 | | impl HeaderProtectionKey { |
33 | 1.55k | fn new(secret: &[u8], version: u32) -> Self { |
34 | 1.55k | let hk = Hkdf::<Sha256>::from_prk(secret).unwrap(); |
35 | 1.55k | let mut secret = [0u8; AES128_KEY_LEN]; |
36 | 1.55k | let quichp = if version == 0x6b3343cf { |
37 | 234 | b"quicv2 hp" as &[u8] |
38 | | } else { |
39 | 1.32k | b"quic hp" as &[u8] |
40 | | }; |
41 | 1.55k | hkdf_expand_label(&hk, quichp, &mut secret, AES128_KEY_LEN as u16); |
42 | 1.55k | return Self(Aes128::new((&secret).into())); |
43 | 1.55k | } |
44 | | |
45 | 1.23k | pub fn decrypt_in_place( |
46 | 1.23k | &self, sample: &[u8], first: &mut u8, packet_number: &mut [u8], |
47 | 1.23k | ) -> Result<(), ()> { |
48 | 1.23k | debug_validate_bug_on!(sample.len() != AES128_KEY_LEN); |
49 | 1.23k | let mut mask: [u8; AES128_KEY_LEN] = sample[..AES128_KEY_LEN].try_into().map_err(|_| ())?; |
50 | 1.23k | self.0.encrypt_block((&mut mask).into()); |
51 | | |
52 | 1.23k | let (first_mask, pn_mask) = mask.split_first().unwrap(); |
53 | | |
54 | 1.23k | let bits = if (*first & 0x80) != 0 { |
55 | 1.08k | 0x0f // Long header: 4 bits masked |
56 | | } else { |
57 | 148 | 0x1f // Short header: 5 bits masked |
58 | | }; |
59 | | |
60 | 1.23k | *first ^= first_mask & bits; |
61 | 1.23k | let pn_len = (*first & 0x03) as usize + 1; |
62 | | |
63 | 1.94k | for (dst, m) in packet_number.iter_mut().zip(pn_mask).take(pn_len) { |
64 | 1.94k | *dst ^= m; |
65 | 1.94k | } |
66 | | |
67 | 1.23k | Ok(()) |
68 | 1.23k | } |
69 | | } |
70 | | |
71 | | pub struct PacketKey { |
72 | | key: Aes128Gcm, |
73 | | iv: [u8; AES128_IV_LEN], |
74 | | } |
75 | | |
76 | | impl PacketKey { |
77 | 1.55k | fn new(secret: &[u8], version: u32) -> Self { |
78 | 1.55k | let hk = Hkdf::<Sha256>::from_prk(secret).unwrap(); |
79 | 1.55k | let mut secret = [0u8; AES128_KEY_LEN]; |
80 | 1.55k | let quickey = if version == 0x6b3343cf { |
81 | 234 | b"quicv2 key" as &[u8] |
82 | | } else { |
83 | 1.32k | b"quic key" as &[u8] |
84 | | }; |
85 | 1.55k | hkdf_expand_label(&hk, quickey, &mut secret, AES128_KEY_LEN as u16); |
86 | 1.55k | let key = Aes128Gcm::new((&secret).into()); |
87 | | |
88 | 1.55k | let mut r = PacketKey { |
89 | 1.55k | key, |
90 | 1.55k | iv: [0u8; AES128_IV_LEN], |
91 | 1.55k | }; |
92 | 1.55k | let quiciv = if version == 0x6b3343cf { |
93 | 234 | b"quicv2 iv" as &[u8] |
94 | | } else { |
95 | 1.32k | b"quic iv" as &[u8] |
96 | | }; |
97 | 1.55k | hkdf_expand_label(&hk, quiciv, &mut r.iv, AES128_IV_LEN as u16); |
98 | 1.55k | return r; |
99 | 1.55k | } |
100 | | |
101 | 1.23k | pub fn decrypt_in_place<'a>( |
102 | 1.23k | &self, packet_number: u64, header: &[u8], payload: &'a mut [u8], |
103 | 1.23k | ) -> Result<&'a [u8], ()> { |
104 | 1.23k | if payload.len() < AES128_TAG_LEN { |
105 | 0 | return Err(()); |
106 | 1.23k | } |
107 | 1.23k | let mut nonce = [0; AES128_IV_LEN]; |
108 | 1.23k | nonce[4..].copy_from_slice(&packet_number.to_be_bytes()); |
109 | 14.8k | for (nonce, inp) in nonce.iter_mut().zip(self.iv.iter()) { |
110 | 14.8k | *nonce ^= inp; |
111 | 14.8k | } |
112 | 1.23k | let tag_pos = payload.len() - AES128_TAG_LEN; |
113 | 1.23k | let (buffer, tag) = payload.split_at_mut(tag_pos); |
114 | 1.23k | self.key |
115 | 1.23k | .decrypt_in_place_detached((&nonce).into(), header, buffer, (&*tag).into()) |
116 | 1.23k | .map_err(|_| ())?; |
117 | 482 | Ok(&payload[..tag_pos]) |
118 | 1.23k | } |
119 | | } |
120 | | |
121 | | pub struct DirectionalKeys { |
122 | | pub header: HeaderProtectionKey, |
123 | | pub packet: PacketKey, |
124 | | } |
125 | | |
126 | | impl DirectionalKeys { |
127 | 1.55k | fn new(secret: &[u8], version: u32) -> Self { |
128 | 1.55k | Self { |
129 | 1.55k | header: HeaderProtectionKey::new(secret, version), |
130 | 1.55k | packet: PacketKey::new(secret, version), |
131 | 1.55k | } |
132 | 1.55k | } |
133 | | } |
134 | | |
135 | | pub struct QuicKeys { |
136 | | pub local: DirectionalKeys, |
137 | | pub remote: DirectionalKeys, |
138 | | } |
139 | | |
140 | 6.22k | fn hkdf_expand_label(hk: &Hkdf<Sha256>, label: &[u8], okm: &mut [u8], olen: u16) { |
141 | | const LABEL_PREFIX: &[u8] = b"tls13 "; |
142 | | |
143 | 6.22k | let output_len = u16::to_be_bytes(olen); |
144 | 6.22k | let label_len = u8::to_be_bytes((LABEL_PREFIX.len() + label.len()) as u8); |
145 | 6.22k | let context_len = u8::to_be_bytes(0); |
146 | | |
147 | 6.22k | let info = &[ |
148 | 6.22k | &output_len[..], |
149 | 6.22k | &label_len[..], |
150 | 6.22k | LABEL_PREFIX, |
151 | 6.22k | label, |
152 | 6.22k | &context_len[..], |
153 | 6.22k | ]; |
154 | | |
155 | 6.22k | hk.expand_multi_info(info, okm).unwrap(); |
156 | 6.22k | } |
157 | | |
158 | 1.42M | pub fn quic_keys_initial(version: u32, client_dst_connection_id: &[u8]) -> Option<QuicKeys> { |
159 | 1.42M | let salt = match version { |
160 | 37 | 0x51303530 => &[ |
161 | 37 | 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, 0x5C, 0xFC, 0xDB, 0xD3, |
162 | 37 | 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45, |
163 | 37 | ], |
164 | 337 | 0xff00_001d..=0xff00_0020 => &[ |
165 | 9 | // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-32#section-5.2 |
166 | 9 | 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, 0x86, 0xf1, 0x9c, 0x61, |
167 | 9 | 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99, |
168 | 9 | ], |
169 | 340 | 0xfaceb002 | 0xff00_0017..=0xff00_001c => &[ |
170 | 63 | // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-23#section-5.2 |
171 | 63 | 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, 0xd2, 0x43, 0x2b, 0xb4, |
172 | 63 | 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02, |
173 | 63 | ], |
174 | 552 | 0x0000_0001 | 0xff00_0021..=0xff00_0022 => &[ |
175 | 552 | // https://www.rfc-editor.org/rfc/rfc9001.html#name-initial-secrets |
176 | 552 | 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, |
177 | 552 | 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a, |
178 | 552 | ], |
179 | 117 | 0x6b3343cf => &[ |
180 | 117 | // https://www.rfc-editor.org/rfc/rfc9369.html#section-3.3.1 |
181 | 117 | 0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, 0x81, 0xbe, 0x6e, 0x26, |
182 | 117 | 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9, |
183 | 117 | ], |
184 | | _ => { |
185 | 1.42M | return None; |
186 | | } |
187 | | }; |
188 | 778 | let hk = Hkdf::<Sha256>::new(Some(salt), client_dst_connection_id); |
189 | 778 | let mut client_secret = [0u8; 32]; |
190 | 778 | hkdf_expand_label(&hk, b"client in", &mut client_secret, 32); |
191 | 778 | let mut server_secret = [0u8; 32]; |
192 | 778 | hkdf_expand_label(&hk, b"server in", &mut server_secret, 32); |
193 | | |
194 | 778 | return Some(QuicKeys { |
195 | 778 | local: DirectionalKeys::new(&server_secret, version), |
196 | 778 | remote: DirectionalKeys::new(&client_secret, version), |
197 | 778 | }); |
198 | 1.42M | } |