Line data Source code
1 : #ifndef HEADER_fd_src_waltz_quic_fd_quic_retry_h
2 : #define HEADER_fd_src_waltz_quic_fd_quic_retry_h
3 :
4 : #include "fd_quic.h"
5 : #include "fd_quic_conn_id.h"
6 : #include "fd_quic_enum.h"
7 : #include "fd_quic_proto_structs.h"
8 : #include "crypto/fd_quic_crypto_suites.h"
9 : #include "../../ballet/aes/fd_aes_gcm.h"
10 :
11 : /* fd_quic_retry.h contains APIs for
12 : - the QUIC v1 Retry mechanism (RFC 9000)
13 : - the QUIC-TLS v1 Retry Integrity Tag (RFC 9001)
14 : - the fd_quic retry token scheme (loosely based on draft-ietf-quic-
15 : retry-offload-00 but incompatible) */
16 :
17 : /* Retry Integrity Tag ************************************************/
18 :
19 : /* The retry integrity tag is the 16-byte tag output of AES-128-GCM */
20 : #define FD_QUIC_RETRY_INTEGRITY_TAG_SZ FD_QUIC_CRYPTO_TAG_SZ
21 6002862 : #define FD_QUIC_RETRY_INTEGRITY_TAG_KEY ((uchar *)"\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e")
22 6002862 : #define FD_QUIC_RETRY_INTEGRITY_TAG_NONCE ((uchar *)"\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb")
23 :
24 : FD_PROTOTYPES_BEGIN
25 :
26 : /* fd_quic_retry_integrity_tag_{sign,verify} implement the RFC 9001
27 : "Retry Integrity Tag" AEAD scheme.
28 :
29 : This is a standard and mandatory step in the QUIC retry proces, both
30 : on the server (sign) and client (verify) side. Confusingly, all
31 : inputs to these functions are either public constants (e.g. the
32 : hardcoded encryption key) or sent in plain text over the wire. Thus,
33 : the "retry_integrity_tag" is more like a hash function than a MAC and
34 : the retry_pseudo_pkt is just obfuscated, but not securely encrypted.
35 :
36 : Failure to generate a correct integrity tag as part of the retry
37 : handshake is considered a protocol error that typically results in
38 : connection termination.
39 :
40 : fd_quic_retry_integrity_tag_sign creates a MAC over the byte range at
41 : retry_pseudo_pkt and writes it into retry_integrity_tag. It is
42 : infallible.
43 :
44 : fd_quic_retry_integrity_tag_decrypt checks whether a Retry Integrity
45 : Tag matches the byte range at retry_pseudo_pkt. It returns
46 : FD_QUIC_SUCCESS if the integrity tag is vaild, and FD_QUIC_FAILURE
47 : otherwise. */
48 :
49 : static inline void
50 : fd_quic_retry_integrity_tag_sign(
51 : fd_aes_gcm_t * aes_gcm,
52 : uchar const * retry_pseudo_pkt,
53 : ulong retry_pseudo_pkt_len,
54 : uchar retry_integrity_tag[static FD_QUIC_RETRY_INTEGRITY_TAG_SZ]
55 3000099 : ) {
56 3000099 : fd_aes_128_gcm_init( aes_gcm, FD_QUIC_RETRY_INTEGRITY_TAG_KEY, FD_QUIC_RETRY_INTEGRITY_TAG_NONCE );
57 3000099 : fd_aes_gcm_encrypt( aes_gcm, NULL, NULL, 0UL, retry_pseudo_pkt, retry_pseudo_pkt_len, retry_integrity_tag );
58 3000099 : }
59 :
60 : FD_FN_PURE static inline int
61 : fd_quic_retry_integrity_tag_verify(
62 : fd_aes_gcm_t * aes_gcm,
63 : uchar const * retry_pseudo_pkt,
64 : ulong retry_pseudo_pkt_len,
65 : uchar const retry_integrity_tag[static FD_QUIC_RETRY_INTEGRITY_TAG_SZ]
66 3002763 : ) {
67 3002763 : fd_aes_128_gcm_init( aes_gcm, FD_QUIC_RETRY_INTEGRITY_TAG_KEY, FD_QUIC_RETRY_INTEGRITY_TAG_NONCE );
68 3002763 : int ok = fd_aes_gcm_decrypt( aes_gcm, NULL, NULL, 0UL, retry_pseudo_pkt, retry_pseudo_pkt_len, retry_integrity_tag );
69 3002763 : return ok ? FD_QUIC_SUCCESS : FD_QUIC_FAILED;
70 3002763 : }
71 :
72 : FD_PROTOTYPES_END
73 :
74 : /* fd_quic retry token (non-standard) **********************************
75 :
76 : The QUIC Retry mechanism as specified in RFC 9000 does not
77 : authenticate retry packets. To safely and statelessly handle retries
78 : in fd_quic, we need to authenticate the token itself. A construction
79 : similar to a HMAC scheme is used, but using the OTM in AES-GCM.
80 : Although AES-GCM is not the ideal algorithm for the job, it was
81 : chosen because it's common throughout QUIC v1, and also quite fast.
82 :
83 : Security Note: This scheme relies on a 128-bit auth key and 96-bit
84 : unique nonces. The encryption key is sourced from CSPRNG on startup
85 : and stays secret. Nonces are generated using fd_rng_t (fine if an
86 : attacker can guess these nonces). However, if fd_rng_t generates the
87 : same 96-bit nonce twice, the retry token authentication mechanism
88 : breaks down entirely (AES-GCM IV reuse). */
89 :
90 : /* fd_quic_retry_data_t encodes data within the QUIC Retry token.
91 : It contains claims about the client. */
92 :
93 : struct __attribute__((packed)) fd_quic_retry_data {
94 : /* 0x00 */ ushort magic;
95 3000096 : # define FD_QUIC_RETRY_TOKEN_MAGIC 0xdaa5
96 : /* 0x02 */ uchar odcid_sz; /* in [1,20] */
97 : /* 0x03 */ uchar rscid_sz; /* in [1,20] */
98 : /* 0x04 */ uchar token_id[12]; /* pseudorandom, guessable */
99 : /* 0x10 */ uchar ip6_addr[16]; /* IPv6 or IPv4-mapped IPv6 address, net order */
100 : /* 0x20 */ ulong expire_comp; /* unix_nanos>>22 */
101 : /* 0x28 */ ushort udp_port; /* host order */
102 : /* 0x2a */ uchar odcid[20]; /* Original Destination Connection ID */
103 : /* 0x3e */ uchar rscid[20]; /* Retry Source Connection ID */
104 : /* 0x52 */
105 : };
106 :
107 : typedef struct fd_quic_retry_data fd_quic_retry_data_t;
108 :
109 : /* fd_quic_retry_token_t encodes the QUIC Retry token itself. */
110 :
111 : struct fd_quic_retry_token {
112 : union {
113 : fd_quic_retry_data_t data;
114 : uchar data_opaque[ sizeof(fd_quic_retry_data_t) ];
115 : };
116 : uchar mac_tag[ FD_AES_GCM_TAG_SZ ];
117 : };
118 :
119 : typedef struct fd_quic_retry_token fd_quic_retry_token_t;
120 :
121 : FD_PROTOTYPES_BEGIN
122 :
123 : /* fd_quic_retry_data_new initializes fd_quic_retry_data_t with a random
124 : nonce. Uses fd_rng_t because only random (unique) bytes are required
125 : but it is not required that they are unguessable. */
126 :
127 : static inline fd_quic_retry_data_t *
128 : fd_quic_retry_data_new( fd_quic_retry_data_t * data,
129 3000096 : fd_rng_t * rng ) {
130 3000096 : memset( data, 0, sizeof(fd_quic_retry_data_t) );
131 3000096 : data->magic = FD_QUIC_RETRY_TOKEN_MAGIC;
132 3000096 : FD_STORE( uint, data->token_id + 0, fd_rng_uint( rng ) );
133 3000096 : FD_STORE( uint, data->token_id + 4, fd_rng_uint( rng ) );
134 3000096 : FD_STORE( uint, data->token_id + 8, fd_rng_uint( rng ) );
135 3000096 : return data;
136 3000096 : }
137 :
138 : /* fd_quic_retry_data_set_ip4 sets the IP address of the token payload
139 : to an IPv4-mapped IPv6 address. ip4_addr is in big endian order. */
140 :
141 : static inline fd_quic_retry_data_t *
142 : fd_quic_retry_data_set_ip4( fd_quic_retry_data_t * data,
143 3000096 : uint ip4_addr ) {
144 3000096 : memset( data->ip6_addr, 0x00, 10 );
145 3000096 : memset( data->ip6_addr + 10, 0xFF, 2 );
146 3000096 : FD_STORE( uint, data->ip6_addr + 12, ip4_addr );
147 3000096 : return data;
148 3000096 : }
149 :
150 : /* fd_quic_retry_token_sign creates mac_tag using the AEAD instance in
151 : aes_gcm and the associated data in token->data.
152 :
153 : WARNING: The same token->data->token_id value may not be reused
154 : across two sign function calls. */
155 :
156 : static inline void
157 : fd_quic_retry_token_sign( fd_quic_retry_token_t * token,
158 : fd_aes_gcm_t * aes_gcm,
159 : uchar const * aes_key,
160 3000096 : uchar const * aes_iv ) {
161 3000096 : uchar iv[12];
162 39001248 : for( ulong j=0; j<12; j++ ) iv[j] = (uchar)( aes_iv[j] ^ token->data.token_id[j] );
163 3000096 : fd_aes_128_gcm_init( aes_gcm, aes_key, iv );
164 :
165 3000096 : void const * aad = token->data_opaque;
166 3000096 : ulong aad_sz = sizeof(fd_quic_retry_data_t);
167 3000096 : fd_aes_gcm_encrypt( aes_gcm, NULL, NULL, 0UL, aad, aad_sz, token->mac_tag );
168 3000096 : }
169 :
170 : /* fd_quic_retry_token_verify checks if token->mac_tag is valid given
171 : AEAD params and associated data in token->data. Does not validate
172 : the content of token->data.
173 : Returns FD_QUIC_SUCCESS if valid, otherwise FD_QUIC_FAILED. */
174 :
175 : static inline int
176 : fd_quic_retry_token_verify( fd_quic_retry_token_t const * token,
177 : fd_aes_gcm_t * aes_gcm,
178 : uchar const * aes_key,
179 3002376 : uchar const * aes_iv ) {
180 3002376 : uchar iv[12];
181 39030888 : for( ulong j=0; j<12; j++ ) iv[j] = (uchar)( aes_iv[j] ^ token->data.token_id[j] );
182 3002376 : fd_aes_128_gcm_init( aes_gcm, aes_key, iv );
183 :
184 3002376 : void const * aad = token->data_opaque;
185 3002376 : ulong aad_sz = sizeof(fd_quic_retry_data_t);
186 3002376 : int ok = fd_aes_gcm_decrypt( aes_gcm, NULL, NULL, 0UL, aad, aad_sz, token->mac_tag );
187 3002376 : return ok ? FD_QUIC_SUCCESS : FD_QUIC_FAILED;
188 3002376 : }
189 :
190 : FD_PROTOTYPES_END
191 :
192 : /* Retry Packets ******************************************************/
193 :
194 : FD_PROTOTYPES_BEGIN
195 :
196 : /* FD_QUIC_RETRY_LOCAL_SZ is the encoded size of Retry packets generated
197 : by fd_quic. (Other QUIC implementations may produce differently
198 : sized retry packets) */
199 :
200 6000192 : #define FD_QUIC_RETRY_LOCAL_SZ (161UL)
201 :
202 : /* fd_quic_retry_{create,verify} do end-to-end issuance and verification
203 : of fd_quic retry tokens. Used by the server-side. */
204 :
205 : ulong
206 : fd_quic_retry_create(
207 : uchar retry[FD_QUIC_RETRY_LOCAL_SZ], /* out */
208 : fd_quic_pkt_t const * pkt,
209 : fd_rng_t * rng,
210 : uchar const retry_secret[ FD_QUIC_RETRY_SECRET_SZ ],
211 : uchar const retry_iv[ FD_QUIC_RETRY_IV_SZ ],
212 : fd_quic_conn_id_t const * orig_dst_conn_id,
213 : fd_quic_conn_id_t const * new_conn_id,
214 : ulong wallclock /* ns since unix epoch */
215 : );
216 :
217 : int
218 : fd_quic_retry_server_verify(
219 : fd_quic_pkt_t const * pkt,
220 : fd_quic_initial_t const * initial,
221 : fd_quic_conn_id_t * orig_dst_conn_id, /* out */
222 : fd_quic_conn_id_t * retry_src_conn_id, /* out */
223 : uchar const retry_secret[ FD_QUIC_RETRY_SECRET_SZ ],
224 : uchar const retry_iv[ FD_QUIC_RETRY_IV_SZ ],
225 : ulong now /* ns since unix epoch */
226 : );
227 :
228 : int
229 : fd_quic_retry_client_verify(
230 : uchar const * const retry_ptr,
231 : ulong const retry_sz,
232 : fd_quic_conn_id_t const * orig_dst_conn_id,
233 : fd_quic_conn_id_t * src_conn_id, /* out */
234 : uchar const ** token,
235 : ulong * token_sz
236 : );
237 :
238 : FD_PROTOTYPES_END
239 :
240 : #endif /* HEADER_fd_src_waltz_quic_fd_quic_retry_h */
|