LCOV - code coverage report
Current view: top level - waltz/quic - fd_quic_retry.h (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 44 44 100.0 %
Date: 2025-01-08 12:08:44 Functions: 7 18 38.9 %

          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     6002838 : #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     6002838 : #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     3000111 : ) {
      55     3000111 :   fd_aes_128_gcm_init( aes_gcm, FD_QUIC_RETRY_INTEGRITY_TAG_KEY, FD_QUIC_RETRY_INTEGRITY_TAG_NONCE );
      56     3000111 :   fd_aes_gcm_encrypt( aes_gcm, NULL, NULL, 0UL, retry_pseudo_pkt, retry_pseudo_pkt_len, retry_integrity_tag );
      57     3000111 : }
      58             : 
      59             : FD_FN_PURE 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     3002727 : ) {
      66     3002727 :   fd_aes_128_gcm_init( aes_gcm, FD_QUIC_RETRY_INTEGRITY_TAG_KEY, FD_QUIC_RETRY_INTEGRITY_TAG_NONCE );
      67     3002727 :   int ok = fd_aes_gcm_decrypt( aes_gcm, NULL, NULL, 0UL, retry_pseudo_pkt, retry_pseudo_pkt_len, retry_integrity_tag );
      68     3002727 :   return ok ? FD_QUIC_SUCCESS : FD_QUIC_FAILED;
      69     3002727 : }
      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 are generated using fd_rng_t (fine if an
      85             :    attacker can guess these nonces).  However, if fd_rng_t generates the
      86             :    same 96-bit nonce twice, the retry token authentication mechanism
      87             :    breaks down entirely (AES-GCM IV reuse). */
      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     3000108 : # 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 random
     122             :    nonce.  Uses fd_rng_t because only random (unique) bytes are required
     123             :    but it is not required that they are unguessable. */
     124             : 
     125             : static inline fd_quic_retry_data_t *
     126             : fd_quic_retry_data_new( fd_quic_retry_data_t * data,
     127     3000108 :                         fd_rng_t *             rng ) {
     128     3000108 :   memset( data, 0, sizeof(fd_quic_retry_data_t) );
     129     3000108 :   data->magic = FD_QUIC_RETRY_TOKEN_MAGIC;
     130     3000108 :   FD_STORE( uint, data->token_id + 0, fd_rng_uint( rng ) );
     131     3000108 :   FD_STORE( uint, data->token_id + 4, fd_rng_uint( rng ) );
     132     3000108 :   FD_STORE( uint, data->token_id + 8, fd_rng_uint( rng ) );
     133     3000108 :   return data;
     134     3000108 : }
     135             : 
     136             : /* fd_quic_retry_data_set_ip4 sets the IP address of the token payload
     137             :    to an IPv4-mapped IPv6 address. ip4_addr is in big endian order. */
     138             : 
     139             : static inline fd_quic_retry_data_t *
     140             : fd_quic_retry_data_set_ip4( fd_quic_retry_data_t * data,
     141     3000108 :                             uint                   ip4_addr ) {
     142     3000108 :   memset( data->ip6_addr,      0x00, 10 );
     143     3000108 :   memset( data->ip6_addr + 10, 0xFF,  2 );
     144     3000108 :   FD_STORE( uint, data->ip6_addr + 12, ip4_addr );
     145     3000108 :   return data;
     146     3000108 : }
     147             : 
     148             : /* fd_quic_retry_token_sign creates mac_tag using the AEAD instance in
     149             :    aes_gcm and the associated data in token->data.
     150             : 
     151             :    WARNING: The same token->data->token_id value may not be reused
     152             :             across two sign function calls. */
     153             : 
     154             : static inline void
     155             : fd_quic_retry_token_sign( fd_quic_retry_token_t * token,
     156             :                           fd_aes_gcm_t *          aes_gcm,
     157             :                           uchar const *           aes_key,
     158     3000108 :                           uchar const *           aes_iv ) {
     159     3000108 :   uchar iv[12];
     160    39001404 :   for( ulong j=0; j<12; j++ ) iv[j] = (uchar)( aes_iv[j] ^ token->data.token_id[j] );
     161     3000108 :   fd_aes_128_gcm_init( aes_gcm, aes_key, iv );
     162             : 
     163     3000108 :   void const * aad    = token->data_opaque;
     164     3000108 :   ulong        aad_sz = sizeof(fd_quic_retry_data_t);
     165     3000108 :   fd_aes_gcm_encrypt( aes_gcm, NULL, NULL, 0UL, aad, aad_sz, token->mac_tag );
     166     3000108 : }
     167             : 
     168             : /* fd_quic_retry_token_verify checks if token->mac_tag is valid given
     169             :    AEAD params and associated data in token->data.  Does not validate
     170             :    the content of token->data.
     171             :    Returns FD_QUIC_SUCCESS if valid, otherwise FD_QUIC_FAILED. */
     172             : 
     173             : static inline int
     174             : fd_quic_retry_token_verify( fd_quic_retry_token_t const * token,
     175             :                             fd_aes_gcm_t *                aes_gcm,
     176             :                             uchar const *                 aes_key,
     177     3002064 :                             uchar const *                 aes_iv ) {
     178     3002064 :   uchar iv[12];
     179    39026832 :   for( ulong j=0; j<12; j++ ) iv[j] = (uchar)( aes_iv[j] ^ token->data.token_id[j] );
     180     3002064 :   fd_aes_128_gcm_init( aes_gcm, aes_key, iv );
     181             : 
     182     3002064 :   void const * aad    = token->data_opaque;
     183     3002064 :   ulong        aad_sz = sizeof(fd_quic_retry_data_t);
     184     3002064 :   int ok = fd_aes_gcm_decrypt( aes_gcm, NULL, NULL, 0UL, aad, aad_sz, token->mac_tag );
     185     3002064 :   return ok ? FD_QUIC_SUCCESS : FD_QUIC_FAILED;
     186     3002064 : }
     187             : 
     188             : FD_PROTOTYPES_END
     189             : 
     190             : /* Retry Packets ******************************************************/
     191             : 
     192             : FD_PROTOTYPES_BEGIN
     193             : 
     194             : /* FD_QUIC_RETRY_LOCAL_SZ is the encoded size of Retry packets generated
     195             :    by fd_quic.  (Other QUIC implementations may produce differently
     196             :    sized retry packets) */
     197             : 
     198     6000216 : #define FD_QUIC_RETRY_LOCAL_SZ (148UL)
     199             : 
     200             : /* fd_quic_retry_{create,verify} do end-to-end issuance and verification
     201             :    of fd_quic retry tokens.  Used by the server-side.
     202             : 
     203             :    orig_dst_conn_id is the DCID chosen by the client in the Initial that
     204             :    triggered a Retry.  retry_src_conn_id is the SCID chosen by the server
     205             :    in the Retry packet. */
     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             :     fd_rng_t *                rng,
     212             :     uchar const               retry_secret[ FD_QUIC_RETRY_SECRET_SZ ],
     213             :     uchar const               retry_iv[ FD_QUIC_RETRY_IV_SZ ],
     214             :     fd_quic_conn_id_t const * orig_dst_conn_id,
     215             :     fd_quic_conn_id_t const * src_conn_id,
     216             :     ulong                     retry_src_conn_id,
     217             :     ulong                     expire_at
     218             : );
     219             : 
     220             : int
     221             : fd_quic_retry_server_verify(
     222             :     fd_quic_pkt_t const *     pkt,
     223             :     fd_quic_initial_t const * initial,
     224             :     fd_quic_conn_id_t *       orig_dst_conn_id, /* out */
     225             :     ulong *                   retry_src_conn_id, /* out */
     226             :     uchar const               retry_secret[ FD_QUIC_RETRY_SECRET_SZ ],
     227             :     uchar const               retry_iv[ FD_QUIC_RETRY_IV_SZ ],
     228             :     ulong                     now,
     229             :     ulong                     ttl
     230             : );
     231             : 
     232             : int
     233             : fd_quic_retry_client_verify(
     234             :     uchar const * const       retry_ptr,
     235             :     ulong         const       retry_sz,
     236             :     fd_quic_conn_id_t const * orig_dst_conn_id,
     237             :     fd_quic_conn_id_t *       src_conn_id, /* out */
     238             :     uchar const **            token,
     239             :     ulong *                   token_sz
     240             : );
     241             : 
     242             : FD_PROTOTYPES_END
     243             : 
     244             : #endif /* HEADER_fd_src_waltz_quic_fd_quic_retry_h */

Generated by: LCOV version 1.14