LCOV - code coverage report
Current view: top level - waltz/quic - fd_quic_retry.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 43 43 100.0 %
Date: 2026-09-17 04:28:31 Functions: 7 24 29.2 %

          Line data    Source code
       1             : #ifndef HEADER_fd_src_waltz_quic_fd_quic_retry_h
       2             : #define HEADER_fd_src_waltz_quic_fd_quic_retry_h
       3             : 
       4             : #include "fd_quic_conn_id.h"
       5             : #include "fd_quic_enum.h"
       6             : #include "fd_quic_proto_structs.h"
       7             : #include "crypto/fd_quic_crypto_suites.h"
       8             : #include "../../ballet/aes/fd_aes_gcm.h"
       9             : 
      10             : /* fd_quic_retry.h contains APIs for
      11             :    - the QUIC v1 Retry mechanism (RFC 9000)
      12             :    - the QUIC-TLS v1 Retry Integrity Tag (RFC 9001)
      13             :    - the fd_quic retry token scheme (loosely based on draft-ietf-quic-
      14             :      retry-offload-00 but incompatible) */
      15             : 
      16             : /* Retry Integrity Tag ************************************************/
      17             : 
      18             : /* The retry integrity tag is the 16-byte tag output of AES-128-GCM */
      19             : #define FD_QUIC_RETRY_INTEGRITY_TAG_SZ FD_QUIC_CRYPTO_TAG_SZ
      20     6002763 : #define FD_QUIC_RETRY_INTEGRITY_TAG_KEY ((uchar *)"\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e")
      21     6002763 : #define FD_QUIC_RETRY_INTEGRITY_TAG_NONCE ((uchar *)"\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb")
      22             : 
      23             : FD_PROTOTYPES_BEGIN
      24             : 
      25             : /* fd_quic_retry_integrity_tag_{sign,verify} implement the RFC 9001
      26             :    "Retry Integrity Tag" AEAD scheme.
      27             : 
      28             :    This is a standard and mandatory step in the QUIC retry process, both
      29             :    on the server (sign) and client (verify) side.  Confusingly, all
      30             :    inputs to these functions are either public constants (e.g. the
      31             :    hardcoded encryption key) or sent in plain text over the wire.  Thus,
      32             :    the "retry_integrity_tag" is more like a hash function than a MAC and
      33             :    the retry_pseudo_pkt is just obfuscated, but not securely encrypted.
      34             : 
      35             :    Failure to generate a correct integrity tag as part of the retry
      36             :    handshake is considered a protocol error that typically results in
      37             :    connection termination.
      38             : 
      39             :    fd_quic_retry_integrity_tag_sign creates a MAC over the byte range at
      40             :    retry_pseudo_pkt and writes it into retry_integrity_tag.  It is
      41             :    infallible.
      42             : 
      43             :    fd_quic_retry_integrity_tag_decrypt checks whether a Retry Integrity
      44             :    Tag matches the byte range at retry_pseudo_pkt.  It returns
      45             :    FD_QUIC_SUCCESS if the integrity tag is valid, and FD_QUIC_FAILURE
      46             :    otherwise. */
      47             : 
      48             : static inline void
      49             : fd_quic_retry_integrity_tag_sign(
      50             :     fd_aes_gcm_t * aes_gcm,
      51             :     uchar const *  retry_pseudo_pkt,
      52             :     ulong          retry_pseudo_pkt_len,
      53             :     uchar          retry_integrity_tag[static FD_QUIC_RETRY_INTEGRITY_TAG_SZ]
      54     3000024 : ) {
      55     3000024 :   fd_aes_128_gcm_init( aes_gcm, FD_QUIC_RETRY_INTEGRITY_TAG_KEY, FD_QUIC_RETRY_INTEGRITY_TAG_NONCE );
      56     3000024 :   fd_aes_gcm_encrypt( aes_gcm, NULL, NULL, 0UL, retry_pseudo_pkt, retry_pseudo_pkt_len, retry_integrity_tag );
      57     3000024 : }
      58             : 
      59             : static inline int
      60             : fd_quic_retry_integrity_tag_verify(
      61             :     fd_aes_gcm_t * aes_gcm,
      62             :     uchar const *  retry_pseudo_pkt,
      63             :     ulong          retry_pseudo_pkt_len,
      64             :     uchar const    retry_integrity_tag[static FD_QUIC_RETRY_INTEGRITY_TAG_SZ]
      65     3002739 : ) {
      66     3002739 :   fd_aes_128_gcm_init( aes_gcm, FD_QUIC_RETRY_INTEGRITY_TAG_KEY, FD_QUIC_RETRY_INTEGRITY_TAG_NONCE );
      67     3002739 :   int ok = fd_aes_gcm_decrypt( aes_gcm, NULL, NULL, 0UL, retry_pseudo_pkt, retry_pseudo_pkt_len, retry_integrity_tag );
      68     3002739 :   return ok ? FD_QUIC_SUCCESS : FD_QUIC_FAILED;
      69     3002739 : }
      70             : 
      71             : FD_PROTOTYPES_END
      72             : 
      73             : /* fd_quic retry token (non-standard) **********************************
      74             : 
      75             :    The QUIC Retry mechanism as specified in RFC 9000 does not
      76             :    authenticate retry packets.  To safely and statelessly handle retries
      77             :    in fd_quic, we need to authenticate the token itself.  A construction
      78             :    similar to a HMAC scheme is used, but using the OTM in AES-GCM.
      79             :    Although AES-GCM is not the ideal algorithm for the job, it was
      80             :    chosen because it's common throughout QUIC v1, and also quite fast.
      81             : 
      82             :    Security Note: This scheme relies on a 128-bit auth key and 96-bit
      83             :    unique nonces.  The encryption key is sourced from CSPRNG on startup
      84             :    and stays secret.  Nonces only have to be unique, not unguessable,
      85             :    but if the same 96-bit nonce is ever generated twice the retry token
      86             :    authentication mechanism breaks down entirely (AES-GCM IV reuse).
      87             :    Callers therefore source them from fd_quic_rng_ulong. */
      88             : 
      89             : /* fd_quic_retry_data_t encodes data within the QUIC Retry token.
      90             :    It contains claims about the client. */
      91             : 
      92             : struct __attribute__((packed)) fd_quic_retry_data {
      93             :   /* 0x00 */ ushort magic;
      94     3000021 : # define FD_QUIC_RETRY_TOKEN_MAGIC 0xdaa5
      95             :   /* 0x02 */ uchar  token_id[12];  /* pseudorandom, guessable */
      96             :   /* 0x0e */ uchar  ip6_addr[16];  /* Source IPv6 or IPv4-mapped IPv6 address, net order */
      97             :   /* 0x1e */ ushort udp_port;      /* Source UDP port, host order */
      98             :   /* 0x20 */ ulong  expire_comp;   /* unix_nanos>>22 */
      99             :   /* 0x28 */ ulong  rscid;         /* Retry Source Connection ID */
     100             :   /* 0x30 */ uchar  odcid[20];     /* Original Destination Connection ID */
     101             :   /* 0x44 */ uchar  odcid_sz;      /* in [1,20] */
     102             :   /* 0x45 */
     103             : };
     104             : 
     105             : typedef struct fd_quic_retry_data fd_quic_retry_data_t;
     106             : 
     107             : /* fd_quic_retry_token_t encodes the QUIC Retry token itself. */
     108             : 
     109             : struct fd_quic_retry_token {
     110             :   union {
     111             :     fd_quic_retry_data_t data;
     112             :     uchar                data_opaque[ sizeof(fd_quic_retry_data_t) ];
     113             :   };
     114             :   uchar mac_tag[ FD_AES_GCM_TAG_SZ ];
     115             : };
     116             : 
     117             : typedef struct fd_quic_retry_token fd_quic_retry_token_t;
     118             : 
     119             : FD_PROTOTYPES_BEGIN
     120             : 
     121             : /* fd_quic_retry_data_new initializes fd_quic_retry_data_t with a
     122             :    randomly generated 96-bit nonce. */
     123             : 
     124             : static inline fd_quic_retry_data_t *
     125             : fd_quic_retry_data_new( fd_quic_retry_data_t * data,
     126             :                         ulong                  nonce0,    /* rand */
     127     3000021 :                         ulong                  nonce1 ) { /* rand */
     128     3000021 :   memset( data, 0, sizeof(fd_quic_retry_data_t) );
     129     3000021 :   data->magic = FD_QUIC_RETRY_TOKEN_MAGIC;
     130     3000021 :   FD_STORE( ulong, data->token_id + 0, nonce0         );
     131     3000021 :   FD_STORE( uint,  data->token_id + 8, (uint)nonce1   );
     132     3000021 :   return data;
     133     3000021 : }
     134             : 
     135             : /* fd_quic_retry_data_set_ip4 sets the IP address of the token payload
     136             :    to an IPv4-mapped IPv6 address. ip4_addr is in big endian order. */
     137             : 
     138             : static inline fd_quic_retry_data_t *
     139             : fd_quic_retry_data_set_ip4( fd_quic_retry_data_t * data,
     140     3000021 :                             uint                   ip4_addr ) {
     141     3000021 :   memset( data->ip6_addr,      0x00, 10 );
     142     3000021 :   memset( data->ip6_addr + 10, 0xFF,  2 );
     143     3000021 :   FD_STORE( uint, data->ip6_addr + 12, ip4_addr );
     144     3000021 :   return data;
     145     3000021 : }
     146             : 
     147             : /* fd_quic_retry_token_sign creates mac_tag using the AEAD instance in
     148             :    aes_gcm and the associated data in token->data.
     149             : 
     150             :    WARNING: The same token->data->token_id value may not be reused
     151             :             across two sign function calls. */
     152             : 
     153             : static inline void
     154             : fd_quic_retry_token_sign( fd_quic_retry_token_t * token,
     155             :                           fd_aes_gcm_t *          aes_gcm,
     156             :                           uchar const *           aes_key,
     157     3000021 :                           uchar const *           aes_iv ) {
     158     3000021 :   uchar iv[12];
     159    39000273 :   for( ulong j=0; j<12; j++ ) iv[j] = (uchar)( aes_iv[j] ^ token->data.token_id[j] );
     160     3000021 :   fd_aes_128_gcm_init( aes_gcm, aes_key, iv );
     161             : 
     162     3000021 :   void const * aad    = token->data_opaque;
     163     3000021 :   ulong        aad_sz = sizeof(fd_quic_retry_data_t);
     164     3000021 :   fd_aes_gcm_encrypt( aes_gcm, NULL, NULL, 0UL, aad, aad_sz, token->mac_tag );
     165     3000021 : }
     166             : 
     167             : /* fd_quic_retry_token_verify checks if token->mac_tag is valid given
     168             :    AEAD params and associated data in token->data.  Does not validate
     169             :    the content of token->data.
     170             :    Returns FD_QUIC_SUCCESS if valid, otherwise FD_QUIC_FAILED. */
     171             : 
     172             : static inline int
     173             : fd_quic_retry_token_verify( fd_quic_retry_token_t const * token,
     174             :                             fd_aes_gcm_t *                aes_gcm,
     175             :                             uchar const *                 aes_key,
     176     3002067 :                             uchar const *                 aes_iv ) {
     177     3002067 :   uchar iv[12];
     178    39026871 :   for( ulong j=0; j<12; j++ ) iv[j] = (uchar)( aes_iv[j] ^ token->data.token_id[j] );
     179     3002067 :   fd_aes_128_gcm_init( aes_gcm, aes_key, iv );
     180             : 
     181     3002067 :   void const * aad    = token->data_opaque;
     182     3002067 :   ulong        aad_sz = sizeof(fd_quic_retry_data_t);
     183     3002067 :   int ok = fd_aes_gcm_decrypt( aes_gcm, NULL, NULL, 0UL, aad, aad_sz, token->mac_tag );
     184     3002067 :   return ok ? FD_QUIC_SUCCESS : FD_QUIC_FAILED;
     185     3002067 : }
     186             : 
     187             : FD_PROTOTYPES_END
     188             : 
     189             : /* Retry Packets ******************************************************/
     190             : 
     191             : FD_PROTOTYPES_BEGIN
     192             : 
     193             : /* FD_QUIC_RETRY_LOCAL_SZ is the encoded size of Retry packets generated
     194             :    by fd_quic.  (Other QUIC implementations may produce differently
     195             :    sized retry packets) */
     196             : 
     197     6000042 : #define FD_QUIC_RETRY_LOCAL_SZ (148UL)
     198             : 
     199             : /* fd_quic_retry_{create,verify} do end-to-end issuance and verification
     200             :    of fd_quic retry tokens.  Used by the server-side.
     201             : 
     202             :    orig_dst_conn_id is the DCID chosen by the client in the Initial that
     203             :    triggered a Retry.  retry_src_conn_id is the SCID chosen by the server
     204             :    in the Retry packet.  nonce0 and nonce1 are the two halves of the
     205             :    token's single 96-bit AES-GCM nonce. */
     206             : 
     207             : ulong
     208             : fd_quic_retry_create(
     209             :     uchar                     retry[FD_QUIC_RETRY_LOCAL_SZ], /* out */
     210             :     fd_quic_pkt_t const *     pkt,
     211             :     ulong                     nonce0, /* rand */
     212             :     ulong                     nonce1, /* rand */
     213             :     uchar const               retry_secret[ FD_QUIC_RETRY_SECRET_SZ ],
     214             :     uchar const               retry_iv[ FD_QUIC_RETRY_IV_SZ ],
     215             :     fd_quic_conn_id_t const * orig_dst_conn_id,
     216             :     fd_quic_conn_id_t const * src_conn_id,
     217             :     ulong                     retry_src_conn_id,
     218             :     long                      expire_at
     219             : );
     220             : 
     221             : int
     222             : fd_quic_retry_server_verify(
     223             :     fd_quic_pkt_t const *     pkt,
     224             :     fd_quic_initial_t const * initial,
     225             :     fd_quic_conn_id_t *       orig_dst_conn_id, /* out */
     226             :     ulong *                   retry_src_conn_id, /* out */
     227             :     uchar const               retry_secret[ FD_QUIC_RETRY_SECRET_SZ ],
     228             :     uchar const               retry_iv[ FD_QUIC_RETRY_IV_SZ ],
     229             :     long                      now,
     230             :     long                      ttl
     231             : );
     232             : 
     233             : int
     234             : fd_quic_retry_client_verify(
     235             :     uchar const * const       retry_ptr,
     236             :     ulong         const       retry_sz,
     237             :     fd_quic_conn_id_t const * orig_dst_conn_id,
     238             :     fd_quic_conn_id_t *       src_conn_id, /* out */
     239             :     uchar const **            token,
     240             :     ulong *                   token_sz
     241             : );
     242             : 
     243             : FD_PROTOTYPES_END
     244             : 
     245             : #endif /* HEADER_fd_src_waltz_quic_fd_quic_retry_h */

Generated by: LCOV version 1.14