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