Line data Source code
1 : #include "fd_quic_common.h"
2 : #include "fd_quic_retry_private.h"
3 : #include "crypto/fd_quic_crypto_suites.h"
4 : #include "fd_quic_conn_id.h"
5 : #include "fd_quic_enum.h"
6 : #include "fd_quic_private.h"
7 : #include "../../ballet/aes/fd_aes_gcm.h"
8 : #include <assert.h>
9 :
10 : FD_STATIC_ASSERT( FD_QUIC_RETRY_LOCAL_SZ==
11 : FD_QUIC_MAX_FOOTPRINT(retry_hdr) +
12 : sizeof(fd_quic_retry_token_t) +
13 : FD_QUIC_CRYPTO_TAG_SZ,
14 : layout );
15 :
16 : ulong
17 : fd_quic_retry_pseudo(
18 : uchar out[ FD_QUIC_RETRY_MAX_PSEUDO_SZ ],
19 : void const * retry_pkt,
20 : ulong retry_pkt_sz,
21 6002862 : fd_quic_conn_id_t const * orig_dst_conn_id ) {
22 :
23 6002862 : if( FD_UNLIKELY( retry_pkt_sz <= FD_QUIC_CRYPTO_TAG_SZ ||
24 6002862 : retry_pkt_sz > FD_QUIC_RETRY_MAX_SZ ) ) {
25 0 : return FD_QUIC_PARSE_FAIL;
26 0 : }
27 :
28 : /* Retry Pseudo-Packet {
29 : ODCID Length (8),
30 : Original Destination Connection ID (0..160),
31 : Header Form (1) = 1,
32 : Fixed Bit (1) = 1,
33 : Long Packet Type (2) = 3,
34 : Unused (4),
35 : Version (32),
36 : DCID Len (8),
37 : Destination Connection ID (0..160),
38 : SCID Len (8),
39 : Source Connection ID (0..160),
40 : Retry Token (..),
41 : } */
42 :
43 6002862 : uchar * cur_ptr = out;
44 :
45 6002862 : cur_ptr[0] = (uchar)orig_dst_conn_id->sz;
46 6002862 : cur_ptr += 1;
47 :
48 6002862 : memcpy( cur_ptr, orig_dst_conn_id->conn_id, FD_QUIC_MAX_CONN_ID_SZ ); /* oversz is safe */
49 6002862 : cur_ptr += orig_dst_conn_id->sz;
50 :
51 6002862 : ulong stripped_retry_sz = retry_pkt_sz - FD_QUIC_CRYPTO_TAG_SZ; /* >0 */
52 6002862 : fd_memcpy( cur_ptr, retry_pkt, stripped_retry_sz );
53 6002862 : cur_ptr += stripped_retry_sz;
54 :
55 6002862 : return (ulong)cur_ptr - (ulong)out;
56 6002862 : }
57 :
58 : ulong
59 : fd_quic_retry_create(
60 : uchar retry[FD_QUIC_RETRY_LOCAL_SZ], /* out */
61 : fd_quic_pkt_t const * pkt,
62 : fd_rng_t * rng,
63 : uchar const retry_secret[ FD_QUIC_RETRY_SECRET_SZ ],
64 : uchar const retry_iv[ FD_QUIC_RETRY_IV_SZ ],
65 : fd_quic_conn_id_t const * orig_dst_conn_id,
66 : fd_quic_conn_id_t const * new_conn_id,
67 : ulong wallclock /* ns since unix epoch */
68 3000096 : ) {
69 :
70 3000096 : uchar * out_ptr = retry;
71 3000096 : ulong out_free = FD_QUIC_RETRY_LOCAL_SZ;
72 :
73 : /* Craft a new Retry packet */
74 :
75 3000096 : fd_quic_retry_hdr_t retry_hdr[1] = {{
76 3000096 : .h0 = 0xf0,
77 3000096 : .version = 1,
78 3000096 : .dst_conn_id_len = pkt->long_hdr->src_conn_id_len,
79 : // .dst_conn_id (initialized below)
80 3000096 : .src_conn_id_len = new_conn_id->sz,
81 : // .src_conn_id (initialized below)
82 3000096 : }};
83 3000096 : memcpy( retry_hdr->dst_conn_id, pkt->long_hdr->src_conn_id, FD_QUIC_MAX_CONN_ID_SZ );
84 3000096 : memcpy( retry_hdr->src_conn_id, &new_conn_id->conn_id, FD_QUIC_MAX_CONN_ID_SZ );
85 3000096 : ulong rc = fd_quic_encode_retry_hdr( retry, FD_QUIC_RETRY_LOCAL_SZ, retry_hdr );
86 3000096 : assert( rc!=FD_QUIC_PARSE_FAIL );
87 3000096 : if( FD_UNLIKELY( rc==FD_QUIC_PARSE_FAIL ) ) FD_LOG_CRIT(( "fd_quic_encode_retry_hdr failed" ));
88 3000096 : out_ptr += rc;
89 3000096 : out_free -= rc;
90 :
91 : /* Craft a new retry token */
92 :
93 3000096 : fd_quic_retry_token_t * retry_token = fd_type_pun( out_ptr );
94 3000096 : assert( out_free >= sizeof(fd_quic_retry_token_t) );
95 :
96 3000096 : uint src_ip4_addr = FD_LOAD( uint, pkt->ip4->saddr_c ); /* net order */
97 3000096 : ushort src_udp_port = (ushort)fd_ushort_bswap( (ushort)pkt->udp->net_sport );
98 3000096 : ulong expire_at = wallclock + FD_QUIC_RETRY_TOKEN_LIFETIME * (ulong)1e9;
99 :
100 3000096 : fd_quic_retry_data_new( &retry_token->data, rng );
101 3000096 : fd_quic_retry_data_set_ip4( &retry_token->data, src_ip4_addr );
102 3000096 : retry_token->data.udp_port = (ushort)src_udp_port;
103 3000096 : retry_token->data.expire_comp = expire_at >> FD_QUIC_RETRY_EXPIRE_SHIFT;
104 :
105 3000096 : retry_token->data.odcid_sz = orig_dst_conn_id->sz;
106 3000096 : retry_token->data.rscid_sz = new_conn_id->sz;
107 3000096 : memcpy( retry_token->data.odcid, orig_dst_conn_id->conn_id, FD_QUIC_MAX_CONN_ID_SZ ); /* oversz copy ok */
108 3000096 : memcpy( retry_token->data.rscid, new_conn_id->conn_id, FD_QUIC_MAX_CONN_ID_SZ ); /* practically always FD_QUIC_CONN_ID_SZ */
109 :
110 : /* Create the inner integrity tag (non-standard) */
111 :
112 3000096 : fd_aes_gcm_t aes_gcm[1];
113 3000096 : fd_quic_retry_token_sign( retry_token, aes_gcm, retry_secret, retry_iv );
114 3000096 : memset( aes_gcm, 0, sizeof(fd_aes_gcm_t) );
115 :
116 3000096 : out_ptr += sizeof(fd_quic_retry_token_t);
117 3000096 : out_free -= sizeof(fd_quic_retry_token_t);
118 :
119 : # if FD_QUIC_DISABLE_CRYPTO
120 :
121 : memset( out_ptr, 0, FD_QUIC_CRYPTO_TAG_SZ );
122 : out_ptr += FD_QUIC_CRYPTO_TAG_SZ;
123 : out_free -= FD_QUIC_CRYPTO_TAG_SZ;
124 :
125 : # else
126 :
127 : /* Create the outer integrity tag (standard) */
128 :
129 3000096 : ulong retry_unsigned_sz = (ulong)out_ptr - (ulong)retry;
130 :
131 3000096 : uchar retry_pseudo_buf[ FD_QUIC_RETRY_MAX_PSEUDO_SZ ];
132 3000096 : ulong retry_pseudo_sz = fd_quic_retry_pseudo( retry_pseudo_buf, retry, retry_unsigned_sz + FD_QUIC_CRYPTO_TAG_SZ, orig_dst_conn_id );
133 3000096 : if( FD_UNLIKELY( retry_pseudo_sz==FD_QUIC_PARSE_FAIL ) ) FD_LOG_ERR(( "fd_quic_retry_pseudo_hdr failed" ));
134 3000096 : fd_quic_retry_integrity_tag_sign( aes_gcm, retry_pseudo_buf, retry_pseudo_sz, out_ptr );
135 3000096 : out_ptr += FD_QUIC_CRYPTO_TAG_SZ;
136 3000096 : out_free -= FD_QUIC_CRYPTO_TAG_SZ;
137 :
138 3000096 : # endif /* FD_QUIC_DISABLE_CRYPTO */
139 :
140 3000096 : assert( (ulong)out_ptr - (ulong)retry <= FD_QUIC_RETRY_LOCAL_SZ );
141 3000096 : ulong retry_sz = (ulong)out_ptr - (ulong)retry;
142 3000096 : return retry_sz;
143 3000096 : }
144 :
145 : int
146 : fd_quic_retry_server_verify(
147 : fd_quic_pkt_t const * pkt,
148 : fd_quic_initial_t const * initial,
149 : fd_quic_conn_id_t * orig_dst_conn_id, /* out */
150 : fd_quic_conn_id_t * retry_src_conn_id, /* out */
151 : uchar const retry_secret[ FD_QUIC_RETRY_SECRET_SZ ],
152 : uchar const retry_iv[ FD_QUIC_RETRY_IV_SZ ],
153 : ulong now
154 3002412 : ) {
155 :
156 : /* We told the client to retry with a DCID chosen by us, and we
157 : always use conn IDs of the same size */
158 3002412 : if( FD_UNLIKELY( initial->dst_conn_id_len != FD_QUIC_CONN_ID_SZ ) ) {
159 18 : FD_DEBUG( FD_LOG_DEBUG(( "Retry with weird dst conn ID sz, rejecting" )); )
160 18 : return FD_QUIC_FAILED;
161 18 : }
162 :
163 : /* fd_quic always uses retry tokens of the same size */
164 3002394 : if( FD_UNLIKELY( initial->token_len != sizeof(fd_quic_retry_token_t) ) ) {
165 18 : FD_DEBUG( FD_LOG_DEBUG(( "Retry with weird token sz, rejecting" )); )
166 18 : return FD_QUIC_FAILED;
167 18 : }
168 :
169 3002376 : fd_quic_retry_token_t const * retry_token = fd_type_pun_const( initial->token );
170 3002376 : if( FD_UNLIKELY( ( retry_token->data.odcid_sz > FD_QUIC_MAX_CONN_ID_SZ ) |
171 3002376 : ( retry_token->data.rscid_sz != FD_QUIC_CONN_ID_SZ ) ) ) {
172 0 : FD_DEBUG( FD_LOG_DEBUG(( "Retry token with invalid ODCID or RSCID, rejecting" )); )
173 0 : return FD_QUIC_FAILED;
174 0 : }
175 :
176 3002376 : fd_aes_gcm_t aes_gcm[1];
177 3002376 : int vfy_res = fd_quic_retry_token_verify( retry_token, aes_gcm, retry_secret, retry_iv );
178 3002376 : memset( aes_gcm, 0, sizeof(fd_aes_gcm_t) );
179 :
180 3002376 : uint pkt_ip4 = FD_LOAD( uint, pkt->ip4->saddr_c );
181 3002376 : uint retry_ip4 = FD_LOAD( uint, retry_token->data.ip6_addr + 12 );
182 3002376 : int is_ip4 = 0==memcmp( retry_token->data.ip6_addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff", 12 );
183 3002376 : uint pkt_port = fd_ushort_bswap( (ushort)pkt->udp->net_sport );
184 3002376 : uint retry_port = retry_token->data.udp_port;
185 3002376 : ulong expire_at = retry_token->data.expire_comp << FD_QUIC_RETRY_EXPIRE_SHIFT;
186 3002376 : ulong expire_before = now + FD_QUIC_RETRY_TOKEN_LIFETIME * (ulong)1e9;
187 :
188 3002376 : int is_match =
189 3002376 : vfy_res == FD_QUIC_SUCCESS &&
190 3002376 : is_ip4 &&
191 3002376 : pkt_ip4 == retry_ip4 &&
192 3002376 : pkt_port == retry_port &&
193 3002376 : now < expire_at &&
194 3002376 : expire_at < expire_before; /* token was issued in the future */
195 :
196 3002376 : FD_DEBUG(
197 3002376 : if( vfy_res!=FD_QUIC_SUCCESS ) FD_LOG_DEBUG(( "Invalid Retry Token" ));
198 3002376 : else if( now >= expire_at ) FD_LOG_DEBUG(( "Expired Retry Token" ));
199 3002376 : else if( expire_at >= expire_before ) FD_LOG_WARNING(( "Retry Token issued in the future" ));
200 3002376 : else if( !is_match ) FD_LOG_DEBUG(( "Foreign Retry Token" ));
201 3002376 : )
202 :
203 3002376 : orig_dst_conn_id->sz = (uchar)retry_token->data.odcid_sz;
204 3002376 : retry_src_conn_id->sz = (uchar)retry_token->data.rscid_sz;
205 3002376 : memcpy( orig_dst_conn_id->conn_id, retry_token->data.odcid, FD_QUIC_MAX_CONN_ID_SZ ); /* oversz copy ok */
206 3002376 : memcpy( retry_src_conn_id->conn_id, retry_token->data.rscid, FD_QUIC_MAX_CONN_ID_SZ ); /* oversz copy ok */
207 :
208 3002376 : return is_match ? FD_QUIC_SUCCESS : FD_QUIC_FAILED;
209 3002376 : }
210 :
211 : int
212 : fd_quic_retry_client_verify( uchar const * const retry_ptr,
213 : ulong const retry_sz,
214 : fd_quic_conn_id_t const * orig_dst_conn_id,
215 : fd_quic_conn_id_t * src_conn_id, /* out */
216 : uchar const ** token,
217 3002856 : ulong * token_sz ) {
218 :
219 3002856 : uchar const * cur_ptr = retry_ptr;
220 3002856 : ulong cur_sz = retry_sz;
221 :
222 : /* Consume retry header */
223 :
224 3002856 : fd_quic_retry_hdr_t retry_hdr[1] = {{0}};
225 3002856 : ulong decode_rc = fd_quic_decode_retry_hdr( retry_hdr, cur_ptr, cur_sz );
226 3002856 : if( FD_UNLIKELY( decode_rc == FD_QUIC_PARSE_FAIL ) ) {
227 87 : FD_DEBUG( FD_LOG_DEBUG(( "fd_quic_decode_retry failed" )); )
228 87 : return FD_QUIC_FAILED;
229 87 : }
230 3002769 : cur_ptr += decode_rc;
231 3002769 : cur_sz -= decode_rc;
232 :
233 3002769 : if( FD_UNLIKELY( retry_hdr->src_conn_id_len == 0 ) ) {
234 : /* something is horribly broken or some attack - ignore packet */
235 0 : FD_DEBUG( FD_LOG_DEBUG(( "Missing source conn ID" )); )
236 0 : return FD_QUIC_FAILED;
237 0 : }
238 :
239 : /* Consume retry token
240 : > A client MUST discard a Retry packet with a zero-length Retry Token field. */
241 :
242 3002769 : if( FD_UNLIKELY( cur_sz <= FD_QUIC_CRYPTO_TAG_SZ ) ) {
243 6 : FD_DEBUG( FD_LOG_DEBUG(( "Retry packet is too small" )); )
244 6 : return FD_QUIC_FAILED;
245 6 : }
246 3002763 : uchar const * retry_token = cur_ptr;
247 3002763 : ulong retry_token_sz = cur_sz - FD_QUIC_CRYPTO_TAG_SZ;
248 3002763 : if( FD_UNLIKELY( retry_token_sz > FD_QUIC_RETRY_MAX_TOKEN_SZ ) ) {
249 0 : FD_DEBUG( FD_LOG_DEBUG(( "Retry token is too long (%lu bytes)", retry_token_sz )); )
250 0 : return FD_QUIC_FAILED;
251 0 : }
252 :
253 3002763 : cur_ptr += retry_token_sz;
254 3002763 : cur_sz -= retry_token_sz;
255 :
256 : /* Consume retry integrity tag */
257 :
258 3002763 : uchar const * retry_tag = cur_ptr;
259 3002763 : assert( cur_sz==FD_QUIC_CRYPTO_TAG_SZ );
260 3002763 : cur_ptr += FD_QUIC_CRYPTO_TAG_SZ;
261 3002763 : cur_sz -= FD_QUIC_CRYPTO_TAG_SZ;
262 :
263 : /* Construct Retry Pseudo Header required to validate Retry Integrity
264 : Tag. TODO This could be made more efficient using streaming
265 : AES-GCM. */
266 :
267 3002763 : uchar retry_pseudo_buf[ FD_QUIC_RETRY_MAX_PSEUDO_SZ ];
268 3002763 : ulong retry_pseudo_sz = fd_quic_retry_pseudo( retry_pseudo_buf, retry_ptr, retry_sz, orig_dst_conn_id );
269 3002763 : if( FD_UNLIKELY( retry_pseudo_sz==FD_QUIC_PARSE_FAIL ) ) FD_LOG_ERR(( "fd_quic_retry_pseudo_hdr failed" ));
270 :
271 : # if FD_QUIC_DISABLE_CRYPTO
272 :
273 : (void)retry_tag; /* skip verification */
274 :
275 : # else
276 :
277 : /* Validate the retry integrity tag
278 :
279 : Retry packets (see Section 17.2.5 of [QUIC-TRANSPORT]) carry a Retry Integrity Tag that
280 : provides two properties: it allows the discarding of packets that have accidentally been
281 : corrupted by the network, and only an entity that observes an Initial packet can send a valid
282 : Retry packet.*/
283 3002763 : fd_aes_gcm_t aes_gcm[1];
284 3002763 : int rc = fd_quic_retry_integrity_tag_verify( aes_gcm, retry_pseudo_buf, retry_pseudo_sz, retry_tag );
285 3002763 : if( FD_UNLIKELY( rc == FD_QUIC_FAILED ) ) {
286 : /* Clients MUST discard Retry packets that have a Retry Integrity Tag that
287 : cannot be validated */
288 2757 : FD_DEBUG( FD_LOG_DEBUG(( "Invalid retry integrity tag" )); )
289 2757 : return FD_QUIC_FAILED;
290 2757 : }
291 :
292 3000006 : # endif
293 :
294 : /* Set out params */
295 :
296 3000006 : src_conn_id[0].sz = retry_hdr->src_conn_id_len;
297 3000006 : memcpy( src_conn_id[0].conn_id, retry_hdr->src_conn_id, FD_QUIC_MAX_CONN_ID_SZ ); /* oversz copy ok */
298 :
299 3000006 : *token = retry_token;
300 3000006 : *token_sz = retry_token_sz;
301 :
302 3000006 : return FD_QUIC_SUCCESS;
303 3002763 : }
|