Line data Source code
1 : #include "fd_tlsrec.h"
2 : #include "../tls/fd_tls.h"
3 : #include "../tls/fd_tls_proto.h"
4 : #include "../../ballet/aes/fd_aes_gcm.h"
5 :
6 : FD_FN_PURE char const *
7 0 : fd_tlsrec_strerror( int err ) {
8 0 : switch( err ) {
9 0 : case FD_TLSREC_SUCCESS: return "success";
10 0 : case FD_TLSREC_ERR_OOM: return "out of memory";
11 0 : case FD_TLSREC_ERR_PROTO: return "protocol error";
12 0 : case FD_TLSREC_ERR_STATE: return "unexpected state";
13 0 : case FD_TLSREC_ERR_CRYPTO: return "crypto error";
14 0 : default: return "unknown";
15 0 : }
16 0 : }
17 :
18 : /* RFC 8446 §5.3: per-record nonce = write_iv XOR (0-padded seq) */
19 :
20 : static void
21 0 : fd_tlsrec_nonce( uchar iv[ static 12 ], uchar const base[ static 12 ], ulong seq ) {
22 0 : memcpy( iv, base, 12 );
23 0 : for( uint i=0; i<8; i++ ) iv[11-i] ^= (uchar)(seq>>(8*i));
24 0 : }
25 :
26 : /* RFC 8446 §5.2: AEAD decrypt/encrypt with 5-byte record header as AAD */
27 :
28 : static int
29 : fd_tlsrec_decrypt( uchar * p, uchar const * c, ulong sz,
30 : fd_tlsrec_hdr_t const * hdr, ulong seq,
31 0 : uchar const tag[16], fd_tlsrec_keys_t * k ) {
32 0 : uchar iv[12]; fd_tlsrec_nonce( iv, k->read_iv, seq );
33 0 : fd_aes_gcm_set_iv( &k->read_gcm, iv );
34 0 : return fd_aes_gcm_decrypt( &k->read_gcm, c, p, sz, (uchar const *)hdr, sizeof(*hdr), tag );
35 0 : }
36 :
37 : static void
38 : fd_tlsrec_encrypt( uchar * c, uchar const * p, ulong sz,
39 : fd_tlsrec_hdr_t const * hdr, ulong seq,
40 0 : uchar tag[16], fd_tlsrec_keys_t * k ) {
41 0 : uchar iv[12]; fd_tlsrec_nonce( iv, k->write_iv, seq );
42 0 : fd_aes_gcm_set_iv( &k->write_gcm, iv );
43 0 : fd_aes_gcm_encrypt( &k->write_gcm, c, p, sz, (uchar const *)hdr, sizeof(*hdr), tag );
44 0 : }
45 :
46 : /* Transmit path ********************************************************/
47 :
48 : static int
49 : fd_tlsrec_tx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_tx,
50 : uchar const * payload, ulong payload_sz,
51 0 : uint content_type, uint enc_level ) {
52 :
53 0 : if( FD_UNLIKELY( conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
54 :
55 : /* Size checks precede the first write so that on ERR_OOM tcp_tx is
56 : untouched and the caller can retry later. */
57 :
58 0 : if( enc_level==FD_TLS_LEVEL_INITIAL ) {
59 0 : if( FD_UNLIKELY( fd_tlsrec_slice_sz(tcp_tx) < sizeof(fd_tlsrec_hdr_t)+payload_sz ) )
60 0 : return FD_TLSREC_ERR_OOM;
61 0 : fd_tlsrec_hdr_t * hdr = fd_type_pun( fd_tlsrec_slice_pop( tcp_tx, sizeof(fd_tlsrec_hdr_t) ) );
62 0 : *hdr = (fd_tlsrec_hdr_t){
63 0 : .content_type = (uchar)content_type,
64 0 : .legacy_record_version = fd_ushort_bswap(0x0303),
65 0 : .length = fd_ushort_bswap((ushort)payload_sz),
66 0 : };
67 0 : fd_memcpy( fd_tlsrec_slice_pop(tcp_tx, payload_sz), payload, payload_sz );
68 0 : return FD_TLSREC_SUCCESS;
69 0 : }
70 :
71 : /* Encrypted record: TLSInnerPlaintext = payload || content_type */
72 0 : ulong inner_sz = payload_sz + 1 + FD_AES_GCM_TAG_SZ;
73 0 : ulong outer_sz = sizeof(fd_tlsrec_hdr_t) + inner_sz;
74 0 : if( FD_UNLIKELY( outer_sz > FD_TLSREC_CAP || outer_sz > fd_tlsrec_slice_sz(tcp_tx) ) )
75 0 : return FD_TLSREC_ERR_OOM;
76 :
77 0 : fd_tlsrec_hdr_t * hdr = fd_type_pun( fd_tlsrec_slice_pop( tcp_tx, sizeof(fd_tlsrec_hdr_t) ) );
78 0 : *hdr = (fd_tlsrec_hdr_t){
79 0 : .content_type = FD_TLS_REC_APPLICATION_DATA,
80 0 : .legacy_record_version = fd_ushort_bswap(0x0303),
81 0 : .length = fd_ushort_bswap((ushort)inner_sz),
82 0 : };
83 :
84 0 : fd_tlsrec_keys_t * keys = &conn->keys[ enc_level==FD_TLS_LEVEL_APPLICATION ];
85 0 : uchar * c = fd_tlsrec_slice_pop( tcp_tx, payload_sz+1 );
86 0 : uchar * tag = fd_tlsrec_slice_pop( tcp_tx, FD_AES_GCM_TAG_SZ );
87 0 : fd_memcpy( c, payload, payload_sz );
88 0 : c[ payload_sz ] = (uchar)content_type;
89 0 : fd_tlsrec_encrypt( c, c, payload_sz+1, hdr, conn->write_seq, tag, keys );
90 0 : conn->write_seq++;
91 0 : return FD_TLSREC_SUCCESS;
92 0 : }
93 :
94 : /* RFC 8446 §7.3 */
95 :
96 : static void
97 : fd_tlsrec_derive_traffic_key( fd_aes_gcm_t * gcm,
98 : uchar key[ static 16 ],
99 : uchar iv[ static 12 ],
100 0 : uchar const secret[ static 32 ] ) {
101 0 : fd_tls_hkdf_expand_label( key, 16UL, secret, "key", 3UL, NULL, 0UL );
102 0 : fd_tls_hkdf_expand_label( iv, 12UL, secret, "iv", 2UL, NULL, 0UL );
103 0 : fd_aes_gcm_init( gcm, key, 16UL, iv );
104 0 : }
105 :
106 : static void
107 : fd_tlsrec_update_traffic_secret( fd_aes_gcm_t * gcm,
108 : uchar secret[ static 32 ],
109 : uchar key[ static 16 ],
110 0 : uchar iv[ static 12 ] ) {
111 0 : uchar next_secret[ 32 ];
112 0 : fd_tls_hkdf_expand_label( next_secret, 32UL, secret, "traffic upd", 11UL, NULL, 0UL );
113 0 : fd_memcpy( secret, next_secret, 32UL );
114 0 : fd_memzero_explicit( next_secret, sizeof(next_secret) );
115 0 : fd_tlsrec_derive_traffic_key( gcm, key, iv, secret );
116 0 : }
117 :
118 : static int
119 : fd_tlsrec_send_key_update( fd_tlsrec_conn_t * conn,
120 : fd_tlsrec_slice_t * tcp_tx,
121 0 : uchar request_peer_update ) {
122 0 : struct __attribute__((packed)) {
123 0 : fd_tls_msg_hdr_t hdr;
124 0 : uchar request_update;
125 0 : } msg = {
126 0 : .hdr = {
127 0 : .type = FD_TLS_MSG_KEY_UPDATE,
128 0 : .sz = fd_uint_to_tls_u24( 1U ),
129 0 : },
130 0 : .request_update = request_peer_update,
131 0 : };
132 0 : fd_tls_msg_hdr_bswap( &msg.hdr );
133 :
134 0 : int rc = fd_tlsrec_tx( conn, tcp_tx, (uchar const *)&msg, sizeof(msg),
135 0 : FD_TLS_REC_HANDSHAKE, FD_TLS_LEVEL_APPLICATION );
136 0 : if( FD_UNLIKELY( rc ) ) return rc;
137 :
138 0 : fd_tlsrec_keys_t * keys = &conn->keys[1];
139 0 : fd_tlsrec_update_traffic_secret( &keys->write_gcm, keys->write_secret, keys->write_key,
140 0 : keys->write_iv );
141 0 : conn->write_seq = 0UL;
142 0 : return FD_TLSREC_SUCCESS;
143 0 : }
144 :
145 : /* fd_tlsrec_answer_key_update sends the one KeyUpdate that answers every
146 : update the peer requested since the last one (RFC 8446 Section 4.6.3
147 : lets a silent receiver collapse them). Not having room in tcp_tx is
148 : not an error: the reply stays pending. */
149 :
150 : static int
151 0 : fd_tlsrec_answer_key_update( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_tx ) {
152 0 : if( conn->tx_closed || !conn->key_update_pending ) return FD_TLSREC_SUCCESS;
153 0 : int rc = fd_tlsrec_send_key_update( conn, tcp_tx, 0U );
154 0 : if( rc==FD_TLSREC_ERR_OOM ) return FD_TLSREC_SUCCESS;
155 0 : if( FD_UNLIKELY( rc ) ) return rc;
156 0 : conn->key_update_pending = 0;
157 0 : return FD_TLSREC_SUCCESS;
158 0 : }
159 :
160 : /* Handshake message reassembly *****************************************/
161 :
162 : static inline ulong
163 0 : fd_tlsrec_peek_msg_sz( uchar const * buf, ulong buf_sz ) {
164 0 : if( buf_sz < sizeof(fd_tls_msg_hdr_t) ) return 0;
165 0 : fd_tls_msg_hdr_t hdr;
166 0 : fd_memcpy( &hdr, buf, sizeof(hdr) );
167 0 : fd_tls_msg_hdr_bswap( &hdr );
168 0 : ulong payload = fd_tls_u24_to_uint( hdr.sz );
169 0 : ulong msg_sz = sizeof(fd_tls_msg_hdr_t) + payload;
170 0 : return ( msg_sz <= FD_TLSREC_HS_MSG_CAP ) ? msg_sz : 0;
171 0 : }
172 :
173 : /* Thread-local buffer coalescing handshake messages into one record */
174 : static FD_TL struct {
175 : uchar buf[ FD_TLSREC_CAP ];
176 : uint sz;
177 : uint enc_level;
178 : fd_tlsrec_slice_t tcp_tx;
179 : } hs_tbuf;
180 :
181 : static void
182 0 : hs_tbuf_init( fd_tlsrec_slice_t const * tx ) {
183 0 : hs_tbuf.sz = 0U;
184 0 : hs_tbuf.tcp_tx = *tx;
185 0 : }
186 :
187 : static int
188 0 : hs_tbuf_push( uchar const * msg, ulong msg_sz, uint enc_level ) {
189 0 : if( hs_tbuf.sz && hs_tbuf.enc_level != enc_level ) return FD_TLSREC_ERR_PROTO;
190 0 : if( hs_tbuf.sz + msg_sz > FD_TLSREC_CAP ) return FD_TLSREC_ERR_OOM;
191 0 : fd_memcpy( hs_tbuf.buf + hs_tbuf.sz, msg, msg_sz );
192 0 : hs_tbuf.sz += (uint)msg_sz;
193 0 : hs_tbuf.enc_level = enc_level;
194 0 : return FD_TLSREC_SUCCESS;
195 0 : }
196 :
197 : /* Emits coalesced handshake messages as records of at most 2^14 bytes
198 : of plaintext (RFC 8446 Section 5.1). */
199 :
200 : static int
201 0 : hs_tbuf_flush( fd_tlsrec_conn_t * conn ) {
202 0 : ulong off = 0UL;
203 0 : while( off < hs_tbuf.sz ) {
204 0 : ulong sz = fd_ulong_min( hs_tbuf.sz - off, FD_TLSREC_PLAINTEXT_MAX );
205 0 : int rc = fd_tlsrec_tx( conn, &hs_tbuf.tcp_tx, hs_tbuf.buf + off, sz,
206 0 : FD_TLS_REC_HANDSHAKE, hs_tbuf.enc_level );
207 0 : if( FD_UNLIKELY( rc ) ) { hs_tbuf.sz = 0; return rc; }
208 0 : off += sz;
209 0 : }
210 : /* The server moves off plaintext when its encrypted flight goes out.
211 : Never lower the write epoch or advance it on an empty flush, such
212 : as when discarding CCS before ServerHello. */
213 0 : if( hs_tbuf.sz && hs_tbuf.enc_level==FD_TLS_LEVEL_HANDSHAKE && conn->tx_level<FD_TLS_LEVEL_HANDSHAKE )
214 0 : conn->tx_level = FD_TLS_LEVEL_HANDSHAKE;
215 0 : hs_tbuf.sz = 0;
216 0 : return FD_TLSREC_SUCCESS;
217 0 : }
218 :
219 : /* fd_tlsrec_send_alert writes an alert record (RFC 8446 Section 6) at
220 : the current write level into tcp_tx and closes the write side. */
221 :
222 : static int
223 0 : fd_tlsrec_send_alert( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_tx, uchar level, uchar desc ) {
224 0 : uchar const alert[2] = { level, desc };
225 0 : int rc = fd_tlsrec_tx( conn, tcp_tx, alert, sizeof(alert), FD_TLS_REC_ALERT, conn->tx_level );
226 0 : if( FD_UNLIKELY( rc ) ) return rc;
227 0 : conn->tx_closed = 1;
228 0 : conn->key_update_pending = 0;
229 0 : return FD_TLSREC_SUCCESS;
230 0 : }
231 :
232 : /* fd_tlsrec_fail marks conn failed and queues the fatal alert for the
233 : peer behind whatever fd_tlsrec_conn_rx already produced in this call.
234 : Pending handshake output is dropped: the peer only needs the alert.
235 : Called from the receive path only, where hs_tbuf is initialized. */
236 :
237 : static int
238 0 : fd_tlsrec_fail( fd_tlsrec_conn_t * conn, uint alert, ushort reason ) {
239 0 : conn->hs.base.state = FD_TLS_HS_FAIL;
240 0 : conn->hs.base.reason = reason;
241 0 : FD_LOG_WARNING(( "TLS connection failed (alert %u-%s; reason %u-%s)",
242 0 : alert, fd_tls_alert_cstr( alert ),
243 0 : reason, fd_tls_reason_cstr( reason ) ));
244 0 : hs_tbuf.sz = 0U;
245 0 : if( !conn->tx_closed ) fd_tlsrec_send_alert( conn, &hs_tbuf.tcp_tx, 2U, (uchar)alert );
246 0 : return FD_TLSREC_ERR_PROTO;
247 0 : }
248 :
249 : /* fd_tlsrec_alert_rx handles an alert record payload (plaintext or
250 : decrypted). RFC 8446 Section 5.1: alerts may not be fragmented or
251 : coalesced, so the payload is exactly two bytes. close_notify marks
252 : the receive side closed (RFC 8446 Section 6.1). user_canceled is
253 : ignored; every other alert is fatal regardless of the level byte. */
254 :
255 : static int
256 0 : fd_tlsrec_alert_rx( fd_tlsrec_conn_t * conn, uchar const * pt, ulong p_sz ) {
257 0 : if( FD_UNLIKELY( p_sz!=2UL ) )
258 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_ALERT_PARSE );
259 0 : uint level = pt[0];
260 0 : uint desc = pt[1];
261 0 : if( desc==FD_TLS_ALERT_CLOSE_NOTIFY ) {
262 0 : conn->rx_closed = 1;
263 0 : return FD_TLSREC_SUCCESS;
264 0 : }
265 0 : if( desc==FD_TLS_ALERT_USER_CANCELED ) return FD_TLSREC_SUCCESS;
266 0 : FD_LOG_WARNING(( "TLS peer sent alert (level %u; alert %u-%s)",
267 0 : level, desc, fd_tls_alert_cstr( desc ) ));
268 0 : conn->hs.base.state = FD_TLS_HS_FAIL;
269 0 : conn->hs.base.reason = FD_TLS_REASON_PEER_ALERT;
270 0 : return FD_TLSREC_ERR_PROTO;
271 0 : }
272 :
273 : static int
274 0 : fd_tlsrec_post_hs_rx( fd_tlsrec_conn_t * conn, uchar const * msg, ulong msg_sz ) {
275 0 : switch( msg[0] ) {
276 :
277 0 : case FD_TLS_MSG_KEY_UPDATE: {
278 0 : if( FD_UNLIKELY( msg_sz!=sizeof(fd_tls_msg_hdr_t)+1UL ) )
279 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_KEY_UPDATE_PARSE );
280 0 : if( FD_UNLIKELY( msg[4]>1U ) )
281 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_ILLEGAL_PARAMETER, FD_TLS_REASON_KEY_UPDATE_PARSE );
282 :
283 0 : fd_tlsrec_keys_t * keys = &conn->keys[1];
284 0 : fd_tlsrec_update_traffic_secret( &keys->read_gcm, keys->read_secret, keys->read_key,
285 0 : keys->read_iv );
286 0 : conn->read_seq = 0UL;
287 :
288 0 : if( msg[4] && !conn->tx_closed ) conn->key_update_pending = 1;
289 0 : return FD_TLSREC_SUCCESS;
290 0 : }
291 :
292 0 : case FD_TLS_MSG_NEW_SESSION_TICKET:
293 0 : if( !conn->hs.base.server ) return FD_TLSREC_SUCCESS;
294 0 : __attribute__((fallthrough));
295 :
296 0 : default:
297 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_POST_HS_MSG );
298 0 : }
299 0 : }
300 :
301 : /* Handshake message delivery *******************************************/
302 :
303 : /* fd_tlsrec_read_keys_plaintext is 1 while the peer's records arrive
304 : unencrypted (before it has our ServerHello, or before we have its). */
305 :
306 : static inline int
307 0 : fd_tlsrec_read_keys_plaintext( fd_tlsrec_conn_t const * conn ) {
308 0 : uint state = conn->hs.base.state;
309 0 : return state==FD_TLS_HS_START || state==FD_TLS_HS_WAIT_SH;
310 0 : }
311 :
312 : /* fd_tlsrec_hs_rx consumes handshake bytes from rx, at most one message
313 : completion per call. *key_change is set to 1 if that message changed
314 : the read keys (ServerHello, Finished, KeyUpdate): RFC 8446 Section
315 : 5.1 requires such a message to end its record. */
316 :
317 : static int
318 0 : fd_tlsrec_hs_rx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * rx, uint enc_level, int * key_change ) {
319 0 : fd_tlsrec_hs_rbuf_t * rbuf = &conn->hs_rbuf;
320 0 : *key_change = 0;
321 :
322 : /* Reassemble message header */
323 0 : if( rbuf->sz < sizeof(fd_tls_msg_hdr_t) ) {
324 0 : ulong want = sizeof(fd_tls_msg_hdr_t);
325 0 : ulong have = fd_ulong_min( want, rbuf->sz + fd_tlsrec_slice_sz(rx) );
326 0 : ulong n = have - rbuf->sz;
327 0 : fd_memcpy( rbuf->buf + rbuf->sz, rx->data, n );
328 0 : rbuf->sz = have; rx->data += n;
329 0 : if( have < want ) return FD_TLSREC_SUCCESS;
330 0 : }
331 :
332 : /* Reassemble message body */
333 0 : ulong msg_sz = fd_tlsrec_peek_msg_sz( rbuf->buf, sizeof(fd_tls_msg_hdr_t) );
334 0 : if( FD_UNLIKELY( !msg_sz ) )
335 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_HS_MSG_SIZE );
336 :
337 0 : ulong have = fd_ulong_min( msg_sz, rbuf->sz + fd_tlsrec_slice_sz(rx) );
338 0 : ulong n = have - rbuf->sz;
339 0 : fd_memcpy( rbuf->buf + rbuf->sz, rx->data, n );
340 0 : rbuf->sz = have; rx->data += n;
341 0 : if( have < msg_sz ) return FD_TLSREC_SUCCESS;
342 :
343 0 : rbuf->sz = 0;
344 :
345 0 : if( conn->hs.base.state == FD_TLS_HS_CONNECTED ) {
346 0 : *key_change = rbuf->buf[0]==FD_TLS_MSG_KEY_UPDATE;
347 0 : return fd_tlsrec_post_hs_rx( conn, rbuf->buf, msg_sz );
348 0 : }
349 :
350 : /* Dispatch to fd_tls */
351 0 : int plaintext_0 = fd_tlsrec_read_keys_plaintext( conn );
352 0 : long rc = fd_tls_handshake( &conn->tls, &conn->hs, rbuf->buf, msg_sz, enc_level );
353 0 : if( FD_UNLIKELY( rc<0 ) ) return fd_tlsrec_fail( conn, (uint)(-rc), conn->hs.base.reason );
354 0 : if( FD_UNLIKELY( (ulong)rc != msg_sz ) )
355 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_DECODE_ERROR, FD_TLS_REASON_HS_MSG_SIZE );
356 0 : if( conn->hs.base.state == FD_TLS_HS_CONNECTED ) {
357 0 : conn->read_seq = 0;
358 0 : if( !conn->hs.base.server ) {
359 0 : conn->write_seq = 0;
360 0 : conn->tx_level = FD_TLS_LEVEL_APPLICATION;
361 0 : }
362 0 : *key_change = 1;
363 0 : }
364 0 : if( plaintext_0 != fd_tlsrec_read_keys_plaintext( conn ) ) *key_change = 1;
365 0 : return FD_TLSREC_SUCCESS;
366 0 : }
367 :
368 : /* fd_tlsrec_hs_rx_record delivers the handshake payload of one record. */
369 :
370 : static int
371 0 : fd_tlsrec_hs_rx_record( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * payload, uint enc_level ) {
372 0 : if( FD_UNLIKELY( fd_tlsrec_slice_is_empty(payload) ) )
373 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_MSG_SIZE );
374 0 : while( !fd_tlsrec_slice_is_empty(payload) ) {
375 0 : int key_change;
376 0 : int rc = fd_tlsrec_hs_rx( conn, payload, enc_level, &key_change );
377 0 : if( FD_UNLIKELY( rc ) ) return rc;
378 : /* RFC 8446 Section 5.1: a message that changes keys ends its record.
379 : Anything after it in this record was authenticated under the old
380 : keys yet would be handled under the new ones. This also bounds
381 : KeyUpdate processing to one per record. */
382 0 : if( FD_UNLIKELY( key_change && !fd_tlsrec_slice_is_empty(payload) ) )
383 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_KEY_CHANGE );
384 0 : }
385 0 : return FD_TLSREC_SUCCESS;
386 0 : }
387 :
388 : /* TLS callbacks ********************************************************/
389 :
390 : FD_FN_CONST static fd_tlsrec_conn_t *
391 0 : cb_ctx( void const * hs ) {
392 0 : return (fd_tlsrec_conn_t *)((ulong)hs - offsetof(fd_tlsrec_conn_t, hs));
393 0 : }
394 :
395 : static void
396 0 : cb_secrets( void const * hs, void const * rx_secret, void const * tx_secret, uint level ) {
397 0 : fd_tlsrec_conn_t * conn = cb_ctx( hs );
398 :
399 0 : fd_tlsrec_keys_t * out = &conn->keys[ level==FD_TLS_LEVEL_APPLICATION ];
400 0 : fd_memcpy( out->read_secret, rx_secret, 32UL );
401 0 : fd_memcpy( out->write_secret, tx_secret, 32UL );
402 :
403 0 : fd_tlsrec_derive_traffic_key( &out->read_gcm, out->read_key, out->read_iv, rx_secret );
404 0 : fd_tlsrec_derive_traffic_key( &out->write_gcm, out->write_key, out->write_iv, tx_secret );
405 :
406 : /* The server has sent Finished before deriving application secrets,
407 : so its write epoch changes now, independently of the read epoch.
408 : The client still has its own Finished to send with handshake keys. */
409 0 : if( level==FD_TLS_LEVEL_HANDSHAKE && !conn->hs.base.server ) conn->tx_level = FD_TLS_LEVEL_HANDSHAKE;
410 0 : if( level==FD_TLS_LEVEL_APPLICATION && conn->hs.base.server ) {
411 0 : conn->tx_level = FD_TLS_LEVEL_APPLICATION;
412 0 : conn->write_seq = 0UL;
413 0 : }
414 :
415 0 : if( conn->secrets_fn ) conn->secrets_fn( hs, rx_secret, tx_secret, level );
416 0 : }
417 :
418 : static int
419 0 : cb_sendmsg( void const * hs, void const * msg, ulong msg_sz, uint enc_level, int flush ) {
420 0 : fd_tlsrec_conn_t * conn = cb_ctx( hs );
421 0 : int rc = hs_tbuf_push( msg, msg_sz, enc_level );
422 0 : if( FD_UNLIKELY( rc ) ) return 0;
423 0 : if( flush ) { rc = hs_tbuf_flush( conn ); if( FD_UNLIKELY(rc) ) return 0; }
424 0 : return 1;
425 0 : }
426 :
427 : /* Record layer (receive path) ******************************************/
428 :
429 : static inline ulong
430 0 : fd_tlsrec_peek_rec_sz( uchar const * buf, ulong buf_sz ) {
431 0 : if( buf_sz < sizeof(fd_tlsrec_hdr_t) ) return 0;
432 0 : fd_tlsrec_hdr_t hdr;
433 0 : fd_memcpy( &hdr, buf, sizeof(hdr) );
434 0 : fd_tlsrec_hdr_bswap( &hdr );
435 0 : ulong payload = hdr.length;
436 0 : if( payload > FD_TLSREC_PAYLOAD_MAX ) return 0;
437 0 : return sizeof(fd_tlsrec_hdr_t) + payload;
438 0 : }
439 :
440 : static int
441 0 : fd_tlsrec_rx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_rx, fd_tlsrec_slice_t * app_rx ) {
442 0 : fd_tlsrec_buf_t * rb = &conn->rec_buf;
443 :
444 : /* Skip rec_buf if tcp_rx already holds a whole record */
445 0 : uchar * rec;
446 0 : ulong rec_sz;
447 0 : if( !rb->sz &&
448 0 : (rec_sz = fd_tlsrec_peek_rec_sz( tcp_rx->data, fd_tlsrec_slice_sz(tcp_rx) )) &&
449 0 : rec_sz <= fd_tlsrec_slice_sz(tcp_rx) ) {
450 0 : rec = fd_tlsrec_slice_pop( tcp_rx, rec_sz );
451 0 : } else {
452 : /* Reassemble record header (5 bytes) */
453 0 : if( rb->sz < sizeof(fd_tlsrec_hdr_t) ) {
454 0 : ulong want = sizeof(fd_tlsrec_hdr_t);
455 0 : ulong have = fd_ulong_min( want, rb->sz + fd_tlsrec_slice_sz(tcp_rx) );
456 0 : ulong n = have - rb->sz;
457 0 : fd_memcpy( rb->buf + rb->sz, tcp_rx->data, n );
458 0 : rb->sz = have; tcp_rx->data += n;
459 0 : if( have < want ) return FD_TLSREC_SUCCESS;
460 0 : }
461 :
462 : /* Reassemble full record */
463 0 : rec_sz = fd_tlsrec_peek_rec_sz( rb->buf, sizeof(fd_tlsrec_hdr_t) );
464 0 : if( FD_UNLIKELY( !rec_sz ) )
465 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_RECORD_OVERFLOW, FD_TLS_REASON_REC_OVERFLOW );
466 :
467 0 : ulong have = fd_ulong_min( rec_sz, rb->sz + fd_tlsrec_slice_sz(tcp_rx) );
468 0 : ulong n = have - rb->sz;
469 0 : fd_memcpy( rb->buf + rb->sz, tcp_rx->data, n );
470 0 : rb->sz = have; tcp_rx->data += n;
471 0 : if( have < rec_sz ) return FD_TLSREC_SUCCESS;
472 :
473 0 : rb->sz = 0; /* consume record */
474 0 : rec = rb->buf;
475 0 : }
476 :
477 0 : fd_tlsrec_hdr_t * hdr = fd_type_pun( rec );
478 0 : fd_tls_estate_base_t * hs = &conn->hs.base;
479 :
480 : /* RFC 8446 Sections 5.1-5.2: ignore legacy_record_version on receive,
481 : but preserve its wire bytes for AEAD additional data. */
482 0 : int plaintext = fd_tlsrec_read_keys_plaintext( conn );
483 0 : int peer_plaintext = hs->server ? hs->state!=FD_TLS_HS_CONNECTED : plaintext;
484 :
485 : /* RFC 8446 Section 5: discard compatibility CCS throughout the
486 : window after the first ClientHello and before the peer Finished,
487 : even between fragments of a handshake message. */
488 0 : if( FD_UNLIKELY( hdr->content_type == FD_TLS_REC_CHANGE_CIPHER_SPEC ) ) {
489 0 : int allowed = hs->server
490 0 : ? ( hs->state!=FD_TLS_HS_CONNECTED && ( hs->state!=FD_TLS_HS_START || conn->hs.srv.hello_retry ) )
491 0 : : ( hs->state!=FD_TLS_HS_CONNECTED && hs->state!=FD_TLS_HS_START );
492 0 : if( FD_UNLIKELY( !allowed ||
493 0 : rec_sz != sizeof(fd_tlsrec_hdr_t)+1UL ||
494 0 : rec[ sizeof(fd_tlsrec_hdr_t) ] != 0x01 ) )
495 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_CCS );
496 0 : return FD_TLSREC_SUCCESS;
497 0 : }
498 :
499 0 : if( FD_UNLIKELY( hdr->content_type == FD_TLS_REC_ALERT && peer_plaintext ) ) {
500 0 : if( FD_UNLIKELY( conn->hs_rbuf.sz ) )
501 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_INTERLEAVED );
502 0 : return fd_tlsrec_alert_rx( conn, rec + sizeof(fd_tlsrec_hdr_t), rec_sz - sizeof(fd_tlsrec_hdr_t) );
503 0 : }
504 :
505 : /* Unencrypted handshake records (pre-ServerHello) */
506 0 : if( FD_UNLIKELY( plaintext ) ) {
507 : /* RFC 8446 Section 5.1: TLSPlaintext.length may not exceed 2^14 */
508 0 : if( FD_UNLIKELY( rec_sz > sizeof(fd_tlsrec_hdr_t)+FD_TLSREC_PLAINTEXT_MAX ) )
509 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_RECORD_OVERFLOW, FD_TLS_REASON_REC_OVERFLOW );
510 0 : fd_tlsrec_slice_t payload[1];
511 0 : fd_tlsrec_slice_init( payload, rec + sizeof(fd_tlsrec_hdr_t), rec_sz - sizeof(fd_tlsrec_hdr_t) );
512 0 : if( FD_UNLIKELY( hdr->content_type != FD_TLS_REC_HANDSHAKE ) )
513 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
514 0 : return fd_tlsrec_hs_rx_record( conn, payload, FD_TLS_LEVEL_INITIAL );
515 0 : }
516 :
517 : /* RFC 8446 Section 5.1: handshake messages MUST NOT be interleaved
518 : with other record types. hs_rbuf holds the head of a handshake
519 : message until the next handshake record completes it. */
520 0 : if( FD_UNLIKELY( conn->hs_rbuf.sz && hdr->content_type != FD_TLS_REC_APPLICATION_DATA ) )
521 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_INTERLEAVED );
522 :
523 : /* Encrypted record (RFC 8446 Section 5.2: TLSCiphertext has outer
524 : type application_data). The header is also AEAD additional data,
525 : so a wrong outer type would fail authentication anyway; checking
526 : first gives the right alert. */
527 0 : if( FD_UNLIKELY( hdr->content_type != FD_TLS_REC_APPLICATION_DATA ) )
528 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
529 0 : if( FD_UNLIKELY( rec_sz < sizeof(fd_tlsrec_hdr_t) + FD_AES_GCM_TAG_SZ ) )
530 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_BAD_RECORD_MAC, FD_TLS_REASON_REC_MAC );
531 :
532 0 : uint enc_level = ( hs->state == FD_TLS_HS_CONNECTED )
533 0 : ? FD_TLS_LEVEL_APPLICATION : FD_TLS_LEVEL_HANDSHAKE;
534 0 : fd_tlsrec_keys_t * keys = &conn->keys[ enc_level==FD_TLS_LEVEL_APPLICATION ];
535 :
536 0 : uchar const * tag = rec + rec_sz - FD_AES_GCM_TAG_SZ;
537 0 : uchar * c = rec + sizeof(fd_tlsrec_hdr_t);
538 0 : ulong c_sz = (ulong)(tag - c);
539 :
540 : /* Decrypt into app_rx if it fits (content type is only known after
541 : decrypting; non-app records don't advance the cursor). Otherwise
542 : decrypt in place in rec_buf. */
543 0 : uchar * pt = app_rx->data;
544 0 : if( FD_UNLIKELY( fd_tlsrec_slice_sz(app_rx) < c_sz ) ) {
545 0 : if( rec != rb->buf ) {
546 0 : fd_memcpy( rb->buf, rec, rec_sz );
547 0 : rec = rb->buf;
548 0 : hdr = fd_type_pun( rec );
549 0 : tag = rec + rec_sz - FD_AES_GCM_TAG_SZ;
550 0 : c = rec + sizeof(fd_tlsrec_hdr_t);
551 0 : }
552 0 : pt = c;
553 0 : }
554 0 : if( FD_UNLIKELY( !fd_tlsrec_decrypt( pt, c, c_sz, hdr, conn->read_seq, tag, keys ) ) ) {
555 0 : fd_tlsrec_fail( conn, FD_TLS_ALERT_BAD_RECORD_MAC, FD_TLS_REASON_REC_MAC );
556 0 : return FD_TLSREC_ERR_CRYPTO;
557 0 : }
558 0 : conn->read_seq++;
559 :
560 : /* RFC 8446 Section 5.4: the entire TLSInnerPlaintext, including
561 : content type and padding, must fit in 2^14+1 bytes. */
562 0 : if( FD_UNLIKELY( c_sz > FD_TLSREC_PLAINTEXT_MAX+1UL ) )
563 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_RECORD_OVERFLOW, FD_TLS_REASON_REC_OVERFLOW );
564 :
565 : /* Strip padding and content type (RFC 8446 §5.4) */
566 0 : ulong p_sz = c_sz;
567 0 : while( p_sz > 0 && pt[p_sz-1] == 0 ) p_sz--;
568 0 : if( FD_UNLIKELY( !p_sz ) )
569 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_PADDING );
570 0 : uint ct = pt[--p_sz];
571 0 : if( FD_UNLIKELY( conn->hs_rbuf.sz && ct != FD_TLS_REC_HANDSHAKE ) )
572 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_HS_INTERLEAVED );
573 :
574 : /* Dispatch by inner content type */
575 0 : switch( ct ) {
576 :
577 0 : case FD_TLS_REC_HANDSHAKE:
578 0 : { fd_tlsrec_slice_t payload[1];
579 0 : fd_tlsrec_slice_init( payload, pt, p_sz );
580 0 : int rc = fd_tlsrec_hs_rx_record( conn, payload, enc_level );
581 0 : if( FD_UNLIKELY( rc ) ) return rc;
582 0 : }
583 0 : break;
584 :
585 0 : case FD_TLS_REC_APPLICATION_DATA:
586 0 : if( FD_UNLIKELY( hs->state != FD_TLS_HS_CONNECTED ) )
587 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
588 0 : if( pt != app_rx->data ) {
589 0 : if( FD_UNLIKELY( p_sz > fd_tlsrec_slice_sz(app_rx) ) ) return FD_TLSREC_ERR_OOM;
590 0 : fd_memcpy( app_rx->data, pt, p_sz );
591 0 : }
592 0 : app_rx->data += p_sz;
593 0 : break;
594 :
595 0 : case FD_TLS_REC_ALERT:
596 0 : return fd_tlsrec_alert_rx( conn, pt, p_sz );
597 :
598 0 : default:
599 0 : return fd_tlsrec_fail( conn, FD_TLS_ALERT_UNEXPECTED_MESSAGE, FD_TLS_REASON_REC_TYPE );
600 0 : }
601 :
602 0 : return FD_TLSREC_SUCCESS;
603 0 : }
604 :
605 : /* Public API ***********************************************************/
606 :
607 : fd_tlsrec_conn_t *
608 0 : fd_tlsrec_conn_init( fd_tlsrec_conn_t * conn, fd_tls_t const * tls, int is_server ) {
609 0 : fd_memzero_explicit( conn, sizeof(*conn) );
610 0 : fd_memcpy( &conn->tls, tls, sizeof(*tls) );
611 0 : conn->secrets_fn = conn->tls.secrets_fn;
612 0 : conn->tls.quic = 0;
613 0 : conn->tls.secrets_fn = cb_secrets;
614 0 : conn->tls.sendmsg_fn = cb_sendmsg;
615 0 : if( is_server ) fd_tls_estate_srv_new( &conn->hs.srv );
616 0 : else fd_tls_estate_cli_new( &conn->hs.cli );
617 0 : return conn;
618 0 : }
619 :
620 : void
621 0 : fd_tlsrec_conn_fini( fd_tlsrec_conn_t * conn ) {
622 0 : fd_memzero_explicit( conn, sizeof(*conn) );
623 0 : }
624 :
625 : int
626 : fd_tlsrec_conn_rx( fd_tlsrec_conn_t * conn, fd_tlsrec_slice_t * tcp_rx,
627 : uchar * tcp_tx, ulong * tcp_tx_sz,
628 0 : uchar * app_rx, ulong * app_rx_sz ) {
629 0 : ulong _z = 0; if( !tcp_tx_sz ) tcp_tx_sz = &_z; if( !app_rx_sz ) app_rx_sz = &_z;
630 :
631 0 : fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
632 0 : fd_tlsrec_slice_t rx[1]; fd_tlsrec_slice_init( rx, app_rx, *app_rx_sz );
633 0 : *tcp_tx_sz = *app_rx_sz = 0;
634 :
635 0 : if( FD_UNLIKELY( fd_tlsrec_conn_is_failed( conn ) ) ) return FD_TLSREC_ERR_STATE;
636 :
637 0 : hs_tbuf_init( tx );
638 :
639 : /* Client: send ClientHello on first call */
640 0 : if( FD_UNLIKELY( conn->hs.base.state==FD_TLS_HS_START && !conn->hs.base.server ) ) {
641 0 : long rc = fd_tls_handshake( &conn->tls, &conn->hs, NULL, 0, FD_TLS_LEVEL_INITIAL );
642 0 : if( FD_UNLIKELY( rc<0 ) ) return FD_TLSREC_ERR_PROTO;
643 0 : }
644 :
645 : /* Process incoming TLS records. RFC 8446 Section 6.1: anything
646 : received after close_notify is ignored. */
647 0 : int rc = FD_TLSREC_SUCCESS;
648 0 : if( tcp_rx ) {
649 0 : while( !fd_tlsrec_slice_is_empty(tcp_rx) && !conn->rx_closed ) {
650 0 : rc = fd_tlsrec_rx( conn, tcp_rx, rx );
651 0 : if( FD_UNLIKELY(rc) ) break;
652 0 : }
653 0 : if( conn->rx_closed ) tcp_rx->data = tcp_rx->data_end;
654 0 : }
655 :
656 0 : if( !rc ) rc = hs_tbuf_flush( conn );
657 0 : if( !rc ) rc = fd_tlsrec_answer_key_update( conn, &hs_tbuf.tcp_tx );
658 :
659 : /* Output (including a fatal alert) is reported even on error so the
660 : caller can send it before tearing down; plaintext is not. */
661 0 : *tx = hs_tbuf.tcp_tx;
662 0 : *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
663 0 : *app_rx_sz = rc ? 0UL : (ulong)(rx->data - app_rx);
664 0 : return rc;
665 0 : }
666 :
667 : int
668 : fd_tlsrec_conn_tx( fd_tlsrec_conn_t * conn, uchar * tcp_tx, ulong * tcp_tx_sz,
669 0 : fd_tlsrec_slice_t * app_tx ) {
670 0 : fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
671 0 : *tcp_tx_sz = 0;
672 :
673 0 : if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready(conn) || conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
674 0 : if( !fd_tlsrec_slice_sz(app_tx) ) return FD_TLSREC_SUCCESS;
675 :
676 0 : ulong overhead = sizeof(fd_tlsrec_hdr_t) + 1 + FD_AES_GCM_TAG_SZ;
677 0 : if( FD_UNLIKELY( fd_tlsrec_slice_sz(tx) < overhead + 128 ) ) return FD_TLSREC_ERR_OOM;
678 :
679 : /* RFC 8446 Section 4.6.3: a requested KeyUpdate goes out before the
680 : next application data record */
681 0 : if( FD_UNLIKELY( conn->key_update_pending ) ) {
682 0 : int rc = fd_tlsrec_send_key_update( conn, tx, 0U );
683 0 : if( FD_UNLIKELY( rc ) ) return rc;
684 0 : conn->key_update_pending = 0;
685 0 : } else if( FD_UNLIKELY( conn->write_seq >= FD_TLSREC_KEY_UPDATE_SEQ ) ) {
686 0 : int rc = fd_tlsrec_send_key_update( conn, tx, 0U );
687 0 : if( FD_UNLIKELY( rc ) ) return rc;
688 0 : }
689 :
690 0 : ulong sz = fd_ulong_min( fd_tlsrec_slice_sz(tx) - overhead, FD_TLSREC_PLAINTEXT_MAX );
691 0 : sz = fd_ulong_min( sz, fd_tlsrec_slice_sz(app_tx) );
692 :
693 0 : int rc = fd_tlsrec_tx( conn, tx, fd_tlsrec_slice_pop(app_tx, sz), sz,
694 0 : FD_TLS_REC_APPLICATION_DATA, FD_TLS_LEVEL_APPLICATION );
695 0 : *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
696 0 : return rc;
697 0 : }
698 :
699 : int
700 : fd_tlsrec_conn_key_update( fd_tlsrec_conn_t * conn, uchar * tcp_tx, ulong * tcp_tx_sz,
701 0 : int request_peer_update ) {
702 0 : fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
703 0 : *tcp_tx_sz = 0UL;
704 :
705 0 : if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready(conn) || conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
706 0 : if( FD_UNLIKELY( request_peer_update<0 || request_peer_update>1 ) ) return FD_TLSREC_ERR_PROTO;
707 :
708 0 : int rc = fd_tlsrec_send_key_update( conn, tx, (uchar)request_peer_update );
709 0 : *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
710 0 : return rc;
711 0 : }
712 :
713 : int
714 0 : fd_tlsrec_conn_close( fd_tlsrec_conn_t * conn, uchar * tcp_tx, ulong * tcp_tx_sz ) {
715 0 : fd_tlsrec_slice_t tx[1]; fd_tlsrec_slice_init( tx, tcp_tx, *tcp_tx_sz );
716 0 : *tcp_tx_sz = 0UL;
717 :
718 0 : if( FD_UNLIKELY( !fd_tlsrec_conn_is_ready(conn) || conn->tx_closed ) ) return FD_TLSREC_ERR_STATE;
719 :
720 0 : int rc = fd_tlsrec_send_alert( conn, tx, 1U, FD_TLS_ALERT_CLOSE_NOTIFY );
721 0 : *tcp_tx_sz = (ulong)(tx->data - tcp_tx);
722 0 : return rc;
723 0 : }
724 :
725 0 : FD_FN_PURE int fd_tlsrec_conn_is_server( fd_tlsrec_conn_t const * c ) { return c->hs.base.server; }
726 0 : FD_FN_PURE int fd_tlsrec_conn_is_ready ( fd_tlsrec_conn_t const * c ) { return c->hs.base.state == FD_TLS_HS_CONNECTED; }
727 0 : FD_FN_PURE int fd_tlsrec_conn_is_failed( fd_tlsrec_conn_t const * c ) { return c->hs.base.state == FD_TLS_HS_FAIL; }
|