LCOV - code coverage report
Current view: top level - waltz/quic/tls - fd_quic_tls.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 224 0.0 %
Date: 2026-11-09 21:21:31 Functions: 0 15 0.0 %

          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 : }

Generated by: LCOV version 1.14