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