Line data Source code
1 : #include "fd_quic_tls.h"
2 : #include "../../tls/fd_tls_proto.h"
3 : #include "../../../ballet/ed25519/fd_x25519.h"
4 : #include "../../../ballet/x509/fd_x509_mock.h"
5 :
6 : #include <errno.h>
7 : #include <stdlib.h>
8 : #include <string.h>
9 :
10 : /* fd_tls callbacks provided by fd_quic *******************************/
11 :
12 : /* fd_quic_tls_sendmsg is called by fd_tls when fd_quic should send a
13 : CRYPTO frame to the peer. Currently, we can assume that the
14 : encryption_level will never decrease (INITIAL => HANDSHAKE => APP) */
15 :
16 : int
17 : fd_quic_tls_sendmsg( void const * handshake,
18 : void const * record,
19 : ulong record_sz,
20 : uint encryption_level,
21 : int flush );
22 :
23 : /* fd_quic_tls_secrets is called by fd_tls when new encryption keys
24 : become available. Currently, this is called at most two times per
25 : connection: For the handshake secrets, and for the initial app-level
26 : secrets. */
27 :
28 : void
29 : fd_quic_tls_secrets( void const * handshake,
30 : void const * recv_secret,
31 : void const * send_secret,
32 : uint encryption_level );
33 :
34 : /* fd_quic_tls_tp_self is called by fd_tls to retrieve fd_quic's QUIC
35 : transport parameters. */
36 :
37 : ulong
38 : fd_quic_tls_tp_self( void * handshake,
39 : uchar * quic_tp,
40 : ulong quic_tp_bufsz );
41 :
42 : /* fd_quic_tls_tp_peer is called by fd_tls to inform fd_quic of the
43 : peer's QUIC transport parameters. */
44 :
45 : void
46 : fd_quic_tls_tp_peer( void * handshake,
47 : uchar const * quic_tp,
48 : ulong quic_tp_sz );
49 :
50 : /* fd_quic_tls lifecycle API ******************************************/
51 :
52 : static void
53 : fd_quic_tls_init( fd_tls_t * tls,
54 : fd_tls_sign_t signer,
55 : uchar const cert_public_key[ static 32 ],
56 : uchar const * alpn,
57 : ulong alpn_sz,
58 : fd_chacha_rng_t * rng );
59 :
60 : fd_quic_tls_t *
61 : fd_quic_tls_new( fd_quic_tls_t * self,
62 0 : fd_quic_tls_cfg_t * cfg ) {
63 :
64 0 : if( FD_UNLIKELY( !self ) ) {
65 0 : FD_LOG_WARNING(( "NULL mem" ));
66 0 : return NULL;
67 0 : }
68 0 : if( FD_UNLIKELY( !cfg ) ) {
69 0 : FD_LOG_WARNING(( "NULL cfg" ));
70 0 : return NULL;
71 0 : }
72 0 : if( FD_UNLIKELY( (!cfg->secret_cb ) |
73 0 : (!cfg->handshake_complete_cb) |
74 0 : (!cfg->peer_params_cb ) ) ) {
75 0 : FD_LOG_WARNING(( "Missing callbacks" ));
76 0 : return NULL;
77 0 : }
78 0 : if( FD_UNLIKELY( !cfg->rng ) ) {
79 0 : FD_LOG_WARNING(( "NULL rng" ));
80 0 : return NULL;
81 0 : }
82 :
83 0 : self->secret_cb = cfg->secret_cb;
84 0 : self->handshake_complete_cb = cfg->handshake_complete_cb;
85 0 : self->peer_params_cb = cfg->peer_params_cb;
86 :
87 : /* Initialize fd_tls */
88 0 : fd_quic_tls_init( &self->tls, cfg->signer, cfg->cert_public_key, cfg->alpn, cfg->alpn_sz, cfg->rng );
89 0 : self->tls.req_client_cert = !!cfg->req_client_cert;
90 :
91 0 : return self;
92 0 : }
93 :
94 : /* fd_quic_tls_init is called as part of fd_quic_tls_new. It sets up
95 : the embedded fd_tls instance. */
96 :
97 : static void
98 : fd_quic_tls_init( fd_tls_t * tls,
99 : fd_tls_sign_t signer,
100 : uchar const cert_public_key[ static 32 ],
101 : uchar const * alpn,
102 : ulong alpn_sz,
103 0 : fd_chacha_rng_t * rng ) {
104 0 : tls = fd_tls_new( tls );
105 0 : *tls = (fd_tls_t) {
106 0 : .quic = 1,
107 0 : .rng = rng,
108 0 : .sign = signer,
109 0 : .secrets_fn = fd_quic_tls_secrets,
110 0 : .sendmsg_fn = fd_quic_tls_sendmsg,
111 :
112 0 : .quic_tp_self_fn = fd_quic_tls_tp_self,
113 0 : .quic_tp_peer_fn = fd_quic_tls_tp_peer,
114 0 : };
115 :
116 : /* Generate X25519 key */
117 0 : if( FD_UNLIKELY( !fd_rng_secure( tls->kex_private_key, 32UL ) ) )
118 0 : FD_LOG_ERR(( "fd_rng_secure failed: %s", fd_io_strerror( errno ) ));
119 0 : fd_x25519_public( tls->kex_public_key, tls->kex_private_key );
120 :
121 : /* Set up Ed25519 key */
122 0 : fd_memcpy( tls->cert_public_key, cert_public_key, 32UL );
123 :
124 : /* Generate X.509 cert */
125 0 : fd_x509_mock_cert( tls->cert_x509, tls->cert_public_key );
126 0 : tls->cert_x509_sz = FD_X509_MOCK_CERT_SZ;
127 :
128 : /* Set ALPN protocol ID
129 : (Technically, don't need to copy the length prefix but we'll do
130 : so anyways.) */
131 0 : if( FD_UNLIKELY( alpn_sz ) ) {
132 0 : if( FD_UNLIKELY( alpn_sz>sizeof(tls->alpn) ) ) FD_LOG_ERR(( "alpn_sz %lu too large", alpn_sz ));
133 0 : memcpy( tls->alpn, alpn, alpn_sz );
134 0 : tls->alpn_sz = alpn_sz;
135 0 : } else { /* default this for backwards compatibility */
136 0 : tls->alpn[ 0 ] = 0x0a;
137 0 : memcpy( tls->alpn+1, "solana-tpu", 11UL );
138 0 : tls->alpn_sz = 11UL;
139 0 : }
140 0 : }
141 :
142 : void *
143 0 : fd_quic_tls_delete( fd_quic_tls_t * self ) {
144 0 : if( FD_UNLIKELY( !self ) ) {
145 0 : FD_LOG_WARNING(( "NULL self" ));
146 0 : return NULL;
147 0 : }
148 0 : return self;
149 0 : }
150 :
151 : fd_quic_tls_hs_t *
152 : fd_quic_tls_hs_new( fd_quic_tls_hs_t * self,
153 : fd_quic_tls_t * quic_tls,
154 : void * context,
155 : int is_server,
156 : fd_quic_transport_params_t const * self_transport_params,
157 0 : long now ) {
158 : // clear the handshake bits
159 0 : fd_memset( self, 0, sizeof(fd_quic_tls_hs_t) );
160 :
161 : // set properties on self
162 0 : self->quic_tls = quic_tls;
163 0 : self->is_server = is_server;
164 0 : self->context = context;
165 :
166 : /* initialize handshake data */
167 :
168 : /* init free list */
169 0 : self->hs_data_free_idx = 0u; /* head points at first */
170 0 : for( ushort j = 0u; j < FD_QUIC_TLS_HS_DATA_CNT; ++j ) {
171 0 : if( j < FD_QUIC_TLS_HS_DATA_CNT-1u ) {
172 0 : self->hs_data[j].next_idx = (ushort)(j+1u); /* each point to next */
173 0 : } else {
174 0 : self->hs_data[j].next_idx = FD_QUIC_TLS_HS_DATA_UNUSED ;
175 0 : }
176 0 : }
177 :
178 : /* no data pending */
179 0 : for( unsigned j = 0; j < 4; ++j ) {
180 0 : self->hs_data_pend_idx[j] = FD_QUIC_TLS_HS_DATA_UNUSED;
181 0 : self->hs_data_pend_end_idx[j] = FD_QUIC_TLS_HS_DATA_UNUSED;
182 0 : }
183 :
184 : /* clear hs_data_buf */
185 0 : self->hs_data_buf_ptr = 0;
186 :
187 : /* all handshake offsets start at zero */
188 0 : fd_memset( self->hs_data_offset, 0, sizeof( self->hs_data_offset ) );
189 :
190 : /* Set QUIC transport params */
191 0 : self->self_transport_params = *self_transport_params;
192 :
193 0 : if( is_server ) {
194 0 : fd_tls_estate_srv_new( &self->hs.srv );
195 0 : } else {
196 0 : fd_tls_estate_cli_new( &self->hs.cli );
197 0 : long res = fd_tls_client_handshake( &quic_tls->tls, &self->hs.cli, NULL, 0UL, 0 );
198 0 : if( FD_UNLIKELY( res<0L ) ) {
199 0 : self->alert = (uint)-res;
200 0 : }
201 0 : }
202 :
203 0 : self->birthtime = now;
204 :
205 0 : return self;
206 0 : }
207 :
208 : void
209 0 : fd_quic_tls_hs_delete( fd_quic_tls_hs_t * self ) {
210 0 : if( !self ) return;
211 :
212 0 : if( self->is_server )
213 0 : fd_tls_estate_srv_delete( &self->hs.srv );
214 0 : else
215 0 : fd_tls_estate_cli_delete( &self->hs.cli );
216 0 : }
217 :
218 : /* fd_quic_tls_check_post_hs is called once the TLS handshake reached
219 : the CONNECTED state. Finished is the last handshake message either
220 : peer sends before the key change to the 1-RTT encryption level, so
221 : any handshake data following it at the same encryption level is
222 : rejected with an unexpected_message alert (RFC 8446 Section 5.1).
223 : Post-handshake messages (e.g. NewSessionTicket) can only arrive at
224 : the 1-RTT level (RFC 9001 Section 4.1.3), where they are ignored. */
225 :
226 : static int
227 0 : fd_quic_tls_check_post_hs( fd_quic_tls_hs_t * self ) {
228 0 : if( FD_UNLIKELY( self->rx_enc_level<FD_TLS_LEVEL_APPLICATION &&
229 0 : self->rx_off<self->rx_sz ) ) {
230 0 : if( !self->hs.base.reason ) self->hs.base.reason = FD_TLS_REASON_HS_KEY_CHANGE;
231 0 : self->hs.base.state = FD_TLS_HS_FAIL;
232 0 : self->alert = FD_TLS_ALERT_UNEXPECTED_MESSAGE;
233 0 : return FD_QUIC_FAILED;
234 0 : }
235 0 : return FD_QUIC_SUCCESS;
236 0 : }
237 :
238 : int
239 0 : fd_quic_tls_process( fd_quic_tls_hs_t * self ) {
240 :
241 0 : if( FD_UNLIKELY( self->hs.base.state==FD_TLS_HS_FAIL ) ) return FD_QUIC_FAILED;
242 0 : if( self->hs.base.state==FD_TLS_HS_CONNECTED ) return fd_quic_tls_check_post_hs( self );
243 :
244 : /* Process all fully received messages */
245 :
246 0 : uint enc_level = self->rx_enc_level;
247 0 : while( self->hs.base.state!=FD_TLS_HS_CONNECTED ) {
248 0 : uchar const * buf = self->rx_hs_buf;
249 0 : ulong off = self->rx_off;
250 0 : ulong avail = self->rx_sz - off;
251 0 : if( avail<4 ) break;
252 :
253 : /* Peek the message size from fd_tls_msg_hdr_t
254 : ?? AA BB CC => 0xCCBBAA?? => 0x??AABBCC => 0x00AABBCC */
255 0 : uint msg_sz = fd_uint_bswap( FD_LOAD( uint, buf+off ) ) & 0xFFFFFFU;
256 0 : if( avail<msg_sz+4 ) break;
257 :
258 0 : long res = fd_tls_handshake( &self->quic_tls->tls, &self->hs, buf+off, avail, enc_level );
259 :
260 0 : if( FD_UNLIKELY( res<0L ) ) {
261 0 : int alert = (int)-res;
262 0 : self->alert = (uint)alert;
263 0 : return FD_QUIC_FAILED;
264 0 : }
265 0 : if( FD_UNLIKELY( res==0UL ) ) {
266 0 : FD_LOG_WARNING(( "preventing deadlock" ));
267 0 : return FD_QUIC_FAILED;
268 0 : }
269 :
270 0 : self->rx_off = (ushort)( off+(ulong)res );
271 0 : }
272 :
273 0 : switch( self->hs.base.state ) {
274 0 : case FD_TLS_HS_CONNECTED:
275 : /* handshake completed */
276 0 : if( FD_UNLIKELY( fd_quic_tls_check_post_hs( self )!=FD_QUIC_SUCCESS ) ) return FD_QUIC_FAILED;
277 0 : self->quic_tls->handshake_complete_cb( self, self->context );
278 0 : return FD_QUIC_SUCCESS;
279 0 : case FD_TLS_HS_FAIL:
280 : /* handshake permanently failed */
281 0 : return FD_QUIC_FAILED;
282 0 : default:
283 : /* handshake not yet complete */
284 0 : return FD_QUIC_SUCCESS;
285 0 : }
286 0 : }
287 :
288 : /* internal callbacks */
289 :
290 : int
291 : fd_quic_tls_sendmsg( void const * handshake,
292 : void const * data,
293 : ulong data_sz,
294 : uint enc_level,
295 0 : int flush FD_PARAM_UNUSED ) {
296 :
297 0 : uint buf_sz = FD_QUIC_TLS_HS_DATA_SZ;
298 0 : if( data_sz > buf_sz ) {
299 0 : return 0;
300 0 : }
301 :
302 : /* Safe because the fd_tls_estate_{srv,cli}_t object is the first
303 : element of fd_quic_tls_hs_t */
304 0 : fd_quic_tls_hs_t * hs = (fd_quic_tls_hs_t *)handshake;
305 :
306 : /* add handshake data to handshake for retrieval by user */
307 :
308 : /* find free handshake data */
309 0 : ushort hs_data_idx = hs->hs_data_free_idx;
310 0 : if( hs_data_idx == FD_QUIC_TLS_HS_DATA_UNUSED ) {
311 : /* no free structures left. fail */
312 0 : return 0;
313 0 : }
314 :
315 : /* allocate enough space from hs data buffer */
316 0 : uint ptr = hs->hs_data_buf_ptr;
317 0 : uint alloc_data_sz = fd_uint_align_up( (uint)data_sz, FD_QUIC_TLS_HS_DATA_ALIGN );
318 0 : if( ptr + alloc_data_sz > FD_QUIC_TLS_HS_DATA_SZ ) {
319 : /* not enough space */
320 0 : return 0;
321 0 : }
322 :
323 : /* success */
324 :
325 0 : fd_quic_tls_hs_data_t * hs_data = &hs->hs_data[hs_data_idx];
326 0 : uchar * buf = &hs->hs_data_buf[ptr];
327 :
328 : /* update free list */
329 0 : hs->hs_data_free_idx = hs_data->next_idx;
330 :
331 : /* write back new buf ptr */
332 0 : hs->hs_data_buf_ptr = ptr + alloc_data_sz;
333 :
334 : /* copy data into buffer, and update metadata in hs_data */
335 0 : fd_memcpy( buf, data, data_sz );
336 0 : hs_data->enc_level = enc_level;
337 0 : hs_data->data = buf;
338 0 : hs_data->data_sz = (uint)data_sz;
339 0 : hs_data->offset = hs->hs_data_offset[enc_level];
340 :
341 : /* offset adjusted ready for more data */
342 0 : hs->hs_data_offset[enc_level] += (uint)data_sz;
343 :
344 : /* add to end of pending list */
345 0 : hs_data->next_idx = FD_QUIC_TLS_HS_DATA_UNUSED;
346 0 : ulong pend_end_idx = hs->hs_data_pend_end_idx[enc_level];
347 0 : if( pend_end_idx == FD_QUIC_TLS_HS_DATA_UNUSED ) {
348 : /* pending list is empty */
349 0 : hs->hs_data_pend_end_idx[enc_level] = hs->hs_data_pend_idx[enc_level] = hs_data_idx;
350 0 : } else {
351 : /* last element must point to next */
352 0 : hs->hs_data[pend_end_idx].next_idx = hs_data_idx;
353 0 : hs->hs_data_pend_end_idx[enc_level] = hs_data_idx;
354 0 : }
355 :
356 0 : return 1;
357 0 : }
358 :
359 : void
360 : fd_quic_tls_secrets( void const * handshake,
361 : void const * recv_secret,
362 : void const * send_secret,
363 0 : uint enc_level ) {
364 :
365 0 : fd_quic_tls_hs_t * hs = (fd_quic_tls_hs_t *)handshake;
366 :
367 0 : fd_quic_tls_secret_t secret = { .enc_level = enc_level };
368 0 : memcpy( secret.read_secret, recv_secret, 32UL );
369 0 : memcpy( secret.write_secret, send_secret, 32UL );
370 :
371 0 : hs->quic_tls->secret_cb( hs, hs->context, &secret );
372 0 : fd_memzero_explicit( &secret, sizeof(secret) );
373 0 : }
374 :
375 : fd_quic_tls_hs_data_t *
376 : fd_quic_tls_get_hs_data( fd_quic_tls_hs_t * self,
377 0 : uint enc_level ) {
378 0 : if( !self ) return NULL;
379 :
380 0 : uint idx = self->hs_data_pend_idx[enc_level];
381 0 : if( idx == FD_QUIC_TLS_HS_DATA_UNUSED ) return NULL;
382 :
383 0 : return &self->hs_data[idx];
384 0 : }
385 :
386 : fd_quic_tls_hs_data_t *
387 0 : fd_quic_tls_get_next_hs_data( fd_quic_tls_hs_t * self, fd_quic_tls_hs_data_t * hs ) {
388 0 : ushort idx = hs->next_idx;
389 0 : if( idx == (ushort)(~0u) ) return NULL;
390 0 : return self->hs_data + idx;
391 0 : }
392 :
393 : void
394 0 : fd_quic_tls_pop_hs_data( fd_quic_tls_hs_t * self, uint enc_level ) {
395 0 : ushort idx = self->hs_data_pend_idx[enc_level];
396 0 : if( idx == FD_QUIC_TLS_HS_DATA_UNUSED ) return;
397 :
398 0 : fd_quic_tls_hs_data_t * hs_data = &self->hs_data[idx];
399 :
400 : /* pop from pending list */
401 0 : self->hs_data_pend_idx[enc_level] = hs_data->next_idx;
402 :
403 : /* if idx is the last, update last */
404 0 : if( hs_data->next_idx == FD_QUIC_TLS_HS_DATA_UNUSED ) {
405 0 : self->hs_data_pend_end_idx[enc_level] = FD_QUIC_TLS_HS_DATA_UNUSED;
406 0 : }
407 0 : }
408 :
409 : void
410 0 : fd_quic_tls_clear_hs_data( fd_quic_tls_hs_t * self, uint enc_level ) {
411 0 : self->hs_data_pend_idx[enc_level] = FD_QUIC_TLS_HS_DATA_UNUSED;
412 0 : self->hs_data_pend_end_idx[enc_level] = FD_QUIC_TLS_HS_DATA_UNUSED;
413 0 : }
414 :
415 : ulong
416 : fd_quic_tls_tp_self( void * const handshake,
417 : uchar * const quic_tp,
418 0 : ulong const quic_tp_bufsz ) {
419 0 : fd_quic_tls_hs_t * hs = (fd_quic_tls_hs_t *)handshake;
420 :
421 0 : ulong encoded_sz = fd_quic_encode_transport_params( quic_tp, quic_tp_bufsz, &hs->self_transport_params );
422 0 : if( FD_UNLIKELY( encoded_sz==FD_QUIC_ENCODE_FAIL ) ) {
423 0 : FD_LOG_WARNING(( "fd_quic_encode_transport_params failed" ));
424 0 : return 0UL;
425 0 : }
426 :
427 0 : return encoded_sz;
428 0 : }
429 :
430 : void
431 : fd_quic_tls_tp_peer( void * handshake,
432 : uchar const * quic_tp,
433 0 : ulong quic_tp_sz ) {
434 : /* Callback issued by fd_tls. Bubble up callback to fd_quic_tls. */
435 :
436 0 : fd_quic_tls_hs_t * hs = (fd_quic_tls_hs_t *)handshake;
437 0 : fd_quic_tls_t * quic_tls = hs->quic_tls;
438 :
439 0 : quic_tls->peer_params_cb( hs->context, quic_tp, quic_tp_sz );
440 0 : }
|