Line data Source code
1 : #include "fd_quic_crypto_suites.h"
2 : #include "../fd_quic.h"
3 :
4 : #include "../../../ballet/aes/fd_aes_base.h"
5 : #include "../../../ballet/aes/fd_aes_gcm.h"
6 : #include "../../../ballet/hmac/fd_hmac.h"
7 : #include "../templ/fd_quic_parse_util.h"
8 :
9 : /* FD_QUIC_CRYPTO_V1_INITIAL_SALT is the salt to the initial secret
10 : HKDF in QUIC v1. */
11 :
12 : static uchar const FD_QUIC_CRYPTO_V1_INITIAL_SALT[ 20UL ] = {
13 : 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3,
14 : 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad,
15 : 0xcc, 0xbb, 0x7f, 0x0a };
16 :
17 : static inline void
18 : fd_quic_hkdf_extract( void * output,
19 : void const * salt, ulong salt_sz,
20 0 : void const * conn_id, ulong conn_id_sz ) {
21 0 : fd_hmac_sha256( conn_id, conn_id_sz, salt, salt_sz, output );
22 0 : }
23 :
24 : static inline void
25 : fd_quic_hkdf_expand_label( uchar * out,
26 : ulong out_sz,
27 : uchar const secret[ 32 ],
28 : char const * label,
29 0 : ulong label_sz ) {
30 0 : fd_tls_hkdf_expand_label( out, out_sz, secret, label, label_sz, NULL, 0UL );
31 0 : }
32 :
33 : void
34 : fd_quic_gen_initial_secrets(
35 : fd_quic_crypto_secrets_t * secrets,
36 : uchar const * conn_id,
37 : ulong conn_id_sz,
38 0 : int is_server ) {
39 : /* Initial Packets
40 : from rfc:
41 : initial_salt = 0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a */
42 0 : uchar const * initial_salt = FD_QUIC_CRYPTO_V1_INITIAL_SALT;
43 0 : ulong initial_salt_sz = sizeof(FD_QUIC_CRYPTO_V1_INITIAL_SALT);
44 0 : fd_quic_hkdf_extract( secrets->initial_secret,
45 0 : initial_salt, initial_salt_sz,
46 0 : conn_id, conn_id_sz );
47 :
48 0 : uchar * read_secret = secrets->secret[0][0];
49 0 : uchar * write_secret = secrets->secret[0][1];
50 0 : uchar * client_secret = is_server ? read_secret : write_secret;
51 0 : uchar * server_secret = is_server ? write_secret : read_secret;
52 :
53 0 : fd_quic_hkdf_expand_label(
54 0 : client_secret, FD_QUIC_SECRET_SZ,
55 0 : secrets->initial_secret,
56 0 : FD_QUIC_CRYPTO_LABEL_CLIENT_IN,
57 0 : FD_QUIC_CRYPTO_LABEL_CLIENT_IN_LEN );
58 :
59 0 : fd_quic_hkdf_expand_label(
60 0 : server_secret, FD_QUIC_SECRET_SZ,
61 0 : secrets->initial_secret,
62 0 : FD_QUIC_CRYPTO_LABEL_SERVER_IN,
63 0 : FD_QUIC_CRYPTO_LABEL_SERVER_IN_LEN );
64 0 : }
65 :
66 : void
67 : fd_quic_key_update_derive( fd_quic_crypto_secrets_t * secrets,
68 0 : fd_quic_crypto_keys_t new_keys[2] ) {
69 : /* Defined in RFC 9001 Section 6.1 as:
70 : secret_<n+1> = HKDF-Expand-Label(secret_<n>, "quic ku", "", Hash.length) */
71 0 : uint enc_level = fd_quic_enc_level_appdata_id;
72 :
73 0 : for( ulong j=0UL; j<2UL; j++ ) {
74 0 : fd_quic_hkdf_expand_label(
75 0 : secrets->new_secret[j], FD_QUIC_SECRET_SZ,
76 0 : secrets->secret[enc_level][j],
77 0 : FD_QUIC_CRYPTO_LABEL_KEY_UPDATE, FD_QUIC_CRYPTO_LABEL_KEY_UPDATE_LEN );
78 0 : }
79 :
80 0 : for( ulong j=0UL; j<2UL; j++ ) {
81 0 : fd_quic_hkdf_expand_label(
82 0 : new_keys[j].pkt_key, FD_AES_128_KEY_SZ,
83 0 : secrets->new_secret[j],
84 0 : FD_QUIC_CRYPTO_LABEL_QUIC_KEY,
85 0 : FD_QUIC_CRYPTO_LABEL_QUIC_KEY_LEN );
86 :
87 0 : fd_quic_hkdf_expand_label(
88 0 : new_keys[j].iv, FD_AES_GCM_IV_SZ,
89 0 : secrets->new_secret[j],
90 0 : FD_QUIC_CRYPTO_LABEL_QUIC_IV,
91 0 : FD_QUIC_CRYPTO_LABEL_QUIC_IV_LEN );
92 0 : }
93 0 : }
94 :
95 :
96 : void
97 : fd_quic_gen_keys(
98 : fd_quic_crypto_keys_t * keys,
99 0 : uchar const secret[ 32 ] ) {
100 :
101 : /* quic key */
102 :
103 : /* output length passed with "quic hp" and "quic key" must be the key size from
104 : the current cipher */
105 0 : fd_quic_hkdf_expand_label(
106 0 : keys->pkt_key, FD_AES_128_KEY_SZ,
107 0 : secret,
108 0 : FD_QUIC_CRYPTO_LABEL_QUIC_KEY,
109 0 : FD_QUIC_CRYPTO_LABEL_QUIC_KEY_LEN );
110 :
111 : /* quic hp */
112 :
113 : /* output length passed with "quic hp" and "quic key" must be the key size from
114 : the current cipher */
115 0 : fd_quic_hkdf_expand_label(
116 0 : keys->hp_key, FD_AES_128_KEY_SZ,
117 0 : secret,
118 0 : FD_QUIC_CRYPTO_LABEL_QUIC_HP,
119 0 : FD_QUIC_CRYPTO_LABEL_QUIC_HP_LEN );
120 :
121 : /* quic iv */
122 0 : fd_quic_hkdf_expand_label(
123 0 : keys->iv, FD_AES_GCM_IV_SZ,
124 0 : secret,
125 0 : FD_QUIC_CRYPTO_LABEL_QUIC_IV,
126 0 : FD_QUIC_CRYPTO_LABEL_QUIC_IV_LEN );
127 0 : }
128 :
129 : /* encrypt a packet
130 :
131 : uses the keys in keys to encrypt the packet "pkt" with header "hdr"
132 : (of length pkt_sz, and hdr_sz respectively) into out.
133 :
134 : out should have enough space to contain the full output with extra space
135 : for a full block which depends on the cipher
136 :
137 : *out_sz is used to determine the amount of buffer space left at *out
138 : if enough space is not available, the function fails and returns
139 : FD_QUIC_FAILED
140 : *out_sz is also set to the number of bytes written into *out at the end
141 :
142 : args
143 : out the destination for the encrypted output
144 : out_sz a pointer to the size of the buffer (on input) and the size of
145 : the written bytes (on output)
146 : hdr the input header bytes
147 : hdr_sz the size of the header in bytes
148 : pkt the input packet bytes
149 : pkt_sz the size of the packet in bytes (frames after packet number not including MAC tag)
150 : keys a pointer to the keys to use
151 : pkt_number needed to create the nonce used in encryption
152 : likely points to the packet number within "hdr"
153 : pkt_number_sz the size of the packet number in bytes
154 : */
155 :
156 : int
157 : fd_quic_crypto_encrypt(
158 : uchar * const out,
159 : ulong * const out_sz,
160 : uchar const * const hdr,
161 : ulong const hdr_sz,
162 : uchar const * const pkt,
163 : ulong const pkt_sz,
164 : fd_quic_crypto_keys_t const * const pkt_keys,
165 : fd_quic_crypto_keys_t const * const hp_keys,
166 0 : ulong const pkt_number ) {
167 :
168 :
169 : /* ensure we have enough space in the output buffer
170 : most space used by cipher:
171 : header bytes (just XORed)
172 : input bytes (encrypted)
173 : tag bytes */
174 :
175 : /* bound on the bytes needed for cipher output */
176 0 : ulong cipher_out_bound = hdr_sz + pkt_sz + FD_QUIC_CRYPTO_TAG_SZ;
177 :
178 0 : if( FD_UNLIKELY( *out_sz < cipher_out_bound ) ) {
179 0 : FD_DEBUG( FD_LOG_WARNING(( "fd_quic_crypto_encrypt: output buffer not big enough" )) );
180 0 : return FD_QUIC_FAILED;
181 0 : }
182 :
183 0 : if( FD_UNLIKELY( ( hdr_sz < 4 ) | ( hdr_sz > INT_MAX ) ) ) {
184 0 : FD_DEBUG( FD_LOG_WARNING(( "fd_quic_crypto_encrypt: packet header size out of bounds" )) );
185 0 : return FD_QUIC_FAILED;
186 0 : }
187 :
188 : /* bounds check */
189 0 : if( FD_UNLIKELY( pkt_sz > INT_MAX ) ) return FD_QUIC_FAILED;
190 :
191 : /* copy the header into the output */
192 0 : fd_memcpy( out, hdr, hdr_sz );
193 :
194 : /* first byte needed in a couple of places */
195 0 : uchar first = out[0];
196 0 : ulong pkt_number_sz = fd_quic_h0_pkt_num_len( first ) + 1u;
197 0 : uchar const * pkt_number_ptr = out + hdr_sz - pkt_number_sz;
198 :
199 0 : uchar nonce[FD_QUIC_NONCE_SZ] = {0};
200 0 : fd_quic_get_nonce( nonce, pkt_keys->iv, pkt_number );
201 :
202 : // Initial packets cipher uses AEAD_AES_128_GCM with keys derived from the Destination Connection ID field of the
203 : // first Initial packet sent by the client; see rfc9001 Section 5.2.
204 0 : fd_aes_gcm_t pkt_cipher[1];
205 0 : fd_aes_128_gcm_init( pkt_cipher, pkt_keys->pkt_key, nonce );
206 :
207 : /* cipher_text is start of encrypted packet bytes, which starts after the header */
208 0 : uchar * cipher_text = out + hdr_sz;
209 0 : uchar * tag = cipher_text + pkt_sz;
210 0 : uchar * pkt_end = tag + FD_QUIC_CRYPTO_TAG_SZ;
211 :
212 0 : fd_aes_gcm_encrypt( pkt_cipher, cipher_text, pkt, pkt_sz, hdr, hdr_sz, tag );
213 :
214 0 : *out_sz = (ulong)( pkt_end - out );
215 :
216 : /* Header protection */
217 :
218 : /* sample start is defined as 4 bytes after the start of the packet number
219 : so shorter packet numbers means sample starts later in the cipher text */
220 0 : uchar const * sample = pkt_number_ptr + 4;
221 :
222 0 : fd_aes_key_t ecb[1];
223 0 : fd_aes_set_encrypt_key( hp_keys->hp_key, 128, ecb );
224 0 : uchar hp_cipher[16];
225 0 : fd_aes_encrypt( sample, hp_cipher, ecb );
226 :
227 : /* hp_cipher is mask */
228 0 : uchar const * mask = hp_cipher;
229 :
230 0 : uchar long_hdr = first & 0x80u; /* long header? */
231 0 : out[0] ^= (uchar)( mask[0] & ( long_hdr ? 0x0fu : 0x1fu ) );
232 :
233 0 : ulong pkt_number_off = hdr_sz - pkt_number_sz;
234 :
235 0 : for( ulong j = 0; j < pkt_number_sz; ++j ) {
236 0 : out[pkt_number_off + j] ^= mask[1+j];
237 0 : }
238 :
239 0 : return FD_QUIC_SUCCESS;
240 0 : }
241 :
242 : int
243 : fd_quic_crypto_decrypt(
244 : uchar * buf,
245 : ulong buf_sz,
246 : ulong pkt_number_off,
247 : ulong pkt_number,
248 0 : fd_quic_crypto_keys_t const * keys ) {
249 :
250 0 : if( FD_UNLIKELY( ( pkt_number_off >= buf_sz ) |
251 0 : ( buf_sz < FD_QUIC_SHORTEST_PKT ) ) ) {
252 0 : FD_DEBUG( FD_LOG_WARNING( ( "fd_quic_crypto_decrypt: cipher text buffer too small" ) ) );
253 0 : return FD_QUIC_FAILED;
254 0 : }
255 :
256 : /* Derive header size */
257 0 : uint first = buf[0];
258 0 : ulong pkt_number_sz = fd_quic_h0_pkt_num_len( first ) + 1u;
259 0 : uchar * hdr = buf;
260 0 : ulong hdr_sz = pkt_number_off + pkt_number_sz;
261 :
262 : /* calculate nonce for decryption
263 : nonce is quic-iv XORed with *reconstructed* packet-number
264 : packet number is 1-4 bytes, so only XOR last pkt_number_sz bytes */
265 0 : uchar nonce[FD_QUIC_NONCE_SZ] = {0};
266 0 : fd_quic_get_nonce( nonce, keys->iv, pkt_number );
267 :
268 0 : if( FD_UNLIKELY( ( buf_sz < hdr_sz ) |
269 0 : ( buf_sz < hdr_sz+FD_QUIC_CRYPTO_TAG_SZ ) ) )
270 0 : return FD_QUIC_FAILED;
271 :
272 : /* Derive offsets
273 :
274 : +----------+ <-- buf
275 : | Header |
276 : +----------+ <-- out
277 : | Payload |
278 : +----------+ <-- gcm_tag
279 : | GCM Tag |
280 : +----------+ <-- buf_end */
281 :
282 0 : uchar * const out = buf + hdr_sz;
283 0 : uchar * const buf_end = buf + buf_sz;
284 0 : uchar * const gcm_tag = buf_end - FD_QUIC_CRYPTO_TAG_SZ;
285 0 : ulong const gcm_sz = (ulong)( gcm_tag - out );
286 :
287 0 : fd_aes_gcm_t pkt_cipher[1];
288 0 : fd_aes_128_gcm_init( pkt_cipher, keys->pkt_key, nonce );
289 :
290 0 : int decrypt_ok = fd_aes_gcm_decrypt( pkt_cipher,
291 0 : out /* ciphertext */, out /* plaintext */,
292 0 : gcm_sz, /* size of plaintext */
293 0 : hdr, hdr_sz, /* associated data */
294 0 : gcm_tag /* auth tag */ );
295 0 : if( FD_UNLIKELY( !decrypt_ok ) ) {
296 0 : FD_DEBUG( FD_LOG_WARNING(( "fd_aes_gcm_decrypt failed" )) );
297 0 : return FD_QUIC_FAILED;
298 0 : }
299 :
300 0 : return FD_QUIC_SUCCESS;
301 0 : }
302 :
303 :
304 : int
305 : fd_quic_crypto_decrypt_hdr(
306 : uchar * buf,
307 : ulong buf_sz,
308 : ulong pkt_number_off,
309 0 : fd_quic_crypto_keys_t const * keys ) {
310 :
311 : /* bounds checks */
312 0 : if( FD_UNLIKELY( ( buf_sz < FD_QUIC_CRYPTO_TAG_SZ ) |
313 0 : ( pkt_number_off >= buf_sz ) ) ) {
314 0 : FD_DEBUG( FD_LOG_WARNING(( "decrypt hdr: bounds checks failed" )) );
315 0 : return FD_QUIC_FAILED;
316 0 : }
317 :
318 0 : uint first = buf[0]; /* first byte */
319 0 : uint long_hdr = first & 0x80u; /* long header? (this bit is not encrypted) */
320 0 : ulong sample_off = pkt_number_off + 4;
321 :
322 0 : if( FD_UNLIKELY( sample_off + FD_QUIC_HP_SAMPLE_SZ > buf_sz ) ) {
323 0 : FD_DEBUG( FD_LOG_WARNING(( "decrypt hdr: not enough bytes for a sample" )) );
324 0 : return FD_QUIC_FAILED;
325 0 : }
326 :
327 0 : uchar * sample = buf + sample_off;
328 :
329 : /* TODO this is hardcoded to AES-128 */
330 0 : uchar hp_cipher[16];
331 0 : fd_aes_key_t ecb[1];
332 0 : fd_aes_set_encrypt_key( keys->hp_key, 128, ecb );
333 0 : fd_aes_encrypt( sample, hp_cipher, ecb );
334 :
335 : /* hp_cipher is mask */
336 0 : uchar const * mask = hp_cipher;
337 :
338 : /* undo first byte mask */
339 0 : first ^= (uint)mask[0] & ( long_hdr ? 0x0fu : 0x1fu );
340 0 : buf[0] = (uchar)first;
341 :
342 : /* now we can calculate the actual packet number size */
343 0 : ulong pkt_number_sz = fd_quic_h0_pkt_num_len( first ) + 1u;
344 0 : if( pkt_number_off+pkt_number_sz > buf_sz ) {
345 0 : FD_DEBUG( FD_LOG_WARNING(( "decrypt hdr: not enough bytes for packet number" )) );
346 0 : return FD_QUIC_FAILED;
347 0 : }
348 :
349 : /* undo packet number encryption */
350 0 : for( ulong j = 0u; j < pkt_number_sz; ++j ) {
351 0 : buf[ pkt_number_off + j ] ^= mask[ 1u+j ];
352 0 : }
353 :
354 0 : return FD_QUIC_SUCCESS;
355 0 : }
|