LCOV - code coverage report
Current view: top level - waltz/quic - fd_quic_retry.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 141 152 92.8 %
Date: 2024-11-13 11:58:15 Functions: 4 4 100.0 %

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

Generated by: LCOV version 1.14