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