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: 2024-11-13 11:58:15 Functions: 7 198 3.5 %

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

Generated by: LCOV version 1.14