LCOV - code coverage report
Current view: top level - waltz/quic/crypto - fd_quic_crypto_suites.c (source / functions) Hit Total Coverage
Test: cov.lcov Lines: 0 166 0.0 %
Date: 2026-10-28 16:16:06 Functions: 0 8 0.0 %

          Line data    Source code
       1             : #include "fd_quic_crypto_suites.h"
       2             : #include "../fd_quic.h"
       3             : 
       4             : #include "../../../ballet/aes/fd_aes_base.h"
       5             : #include "../../../ballet/aes/fd_aes_gcm.h"
       6             : #include "../../../ballet/hmac/fd_hmac.h"
       7             : #include "../templ/fd_quic_parse_util.h"
       8             : 
       9             : /* FD_QUIC_CRYPTO_V1_INITIAL_SALT is the salt to the initial secret
      10             :    HKDF in QUIC v1. */
      11             : 
      12             : static uchar const FD_QUIC_CRYPTO_V1_INITIAL_SALT[ 20UL ] = {
      13             :     0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3,
      14             :     0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad,
      15             :     0xcc, 0xbb, 0x7f, 0x0a };
      16             : 
      17             : static inline void
      18             : fd_quic_hkdf_extract( void *       output,
      19             :                       void const * salt,    ulong salt_sz,
      20           0 :                       void const * conn_id, ulong conn_id_sz ) {
      21           0 :   fd_hmac_sha256( conn_id, conn_id_sz, salt, salt_sz, output );
      22           0 : }
      23             : 
      24             : static inline void
      25             : fd_quic_hkdf_expand_label( uchar *       out,
      26             :                            ulong         out_sz,
      27             :                            uchar const   secret[ 32 ],
      28             :                            char const *  label,
      29           0 :                            ulong         label_sz ) {
      30           0 :   fd_tls_hkdf_expand_label( out, out_sz, secret, label, label_sz, NULL, 0UL );
      31           0 : }
      32             : 
      33             : void
      34             : fd_quic_gen_initial_secrets(
      35             :     fd_quic_crypto_secrets_t * secrets,
      36             :     uchar const *              conn_id,
      37             :     ulong                      conn_id_sz,
      38           0 :     int                        is_server ) {
      39             :   /* Initial Packets
      40             :      from rfc:
      41             :      initial_salt = 0x38762cf7f55934b34d179ae6a4c80cadccbb7f0a */
      42           0 :   uchar const * initial_salt    = FD_QUIC_CRYPTO_V1_INITIAL_SALT;
      43           0 :   ulong         initial_salt_sz = sizeof(FD_QUIC_CRYPTO_V1_INITIAL_SALT);
      44           0 :   fd_quic_hkdf_extract( secrets->initial_secret,
      45           0 :                         initial_salt,            initial_salt_sz,
      46           0 :                         conn_id,                 conn_id_sz );
      47             : 
      48           0 :   uchar * read_secret   = secrets->secret[0][0];
      49           0 :   uchar * write_secret  = secrets->secret[0][1];
      50           0 :   uchar * client_secret = is_server ? read_secret  : write_secret;
      51           0 :   uchar * server_secret = is_server ? write_secret : read_secret;
      52             : 
      53           0 :   fd_quic_hkdf_expand_label(
      54           0 :       client_secret, FD_QUIC_SECRET_SZ,
      55           0 :       secrets->initial_secret,
      56           0 :       FD_QUIC_CRYPTO_LABEL_CLIENT_IN,
      57           0 :       FD_QUIC_CRYPTO_LABEL_CLIENT_IN_LEN );
      58             : 
      59           0 :   fd_quic_hkdf_expand_label(
      60           0 :       server_secret, FD_QUIC_SECRET_SZ,
      61           0 :       secrets->initial_secret,
      62           0 :       FD_QUIC_CRYPTO_LABEL_SERVER_IN,
      63           0 :       FD_QUIC_CRYPTO_LABEL_SERVER_IN_LEN );
      64           0 : }
      65             : 
      66             : void
      67             : fd_quic_key_update_derive( fd_quic_crypto_secrets_t * secrets,
      68           0 :                            fd_quic_crypto_keys_t      new_keys[2] ) {
      69             :   /* Defined in RFC 9001 Section 6.1 as:
      70             :      secret_<n+1> = HKDF-Expand-Label(secret_<n>, "quic ku", "", Hash.length) */
      71           0 :   uint enc_level = fd_quic_enc_level_appdata_id;
      72             : 
      73           0 :   for( ulong j=0UL; j<2UL; j++ ) {
      74           0 :     fd_quic_hkdf_expand_label(
      75           0 :         secrets->new_secret[j], FD_QUIC_SECRET_SZ,
      76           0 :         secrets->secret[enc_level][j],
      77           0 :         FD_QUIC_CRYPTO_LABEL_KEY_UPDATE, FD_QUIC_CRYPTO_LABEL_KEY_UPDATE_LEN );
      78           0 :   }
      79             : 
      80           0 :   for( ulong j=0UL; j<2UL; j++ ) {
      81           0 :     fd_quic_hkdf_expand_label(
      82           0 :         new_keys[j].pkt_key, FD_AES_128_KEY_SZ,
      83           0 :         secrets->new_secret[j],
      84           0 :         FD_QUIC_CRYPTO_LABEL_QUIC_KEY,
      85           0 :         FD_QUIC_CRYPTO_LABEL_QUIC_KEY_LEN );
      86             : 
      87           0 :     fd_quic_hkdf_expand_label(
      88           0 :         new_keys[j].iv, FD_AES_GCM_IV_SZ,
      89           0 :         secrets->new_secret[j],
      90           0 :         FD_QUIC_CRYPTO_LABEL_QUIC_IV,
      91           0 :         FD_QUIC_CRYPTO_LABEL_QUIC_IV_LEN );
      92           0 :   }
      93           0 : }
      94             : 
      95             : 
      96             : void
      97             : fd_quic_gen_keys(
      98             :     fd_quic_crypto_keys_t * keys,
      99           0 :     uchar const             secret[ 32 ] ) {
     100             : 
     101             :   /* quic key */
     102             : 
     103             :   /* output length passed with "quic hp" and "quic key" must be the key size from
     104             :      the current cipher */
     105           0 :   fd_quic_hkdf_expand_label(
     106           0 :       keys->pkt_key, FD_AES_128_KEY_SZ,
     107           0 :       secret,
     108           0 :       FD_QUIC_CRYPTO_LABEL_QUIC_KEY,
     109           0 :       FD_QUIC_CRYPTO_LABEL_QUIC_KEY_LEN );
     110             : 
     111             :   /* quic hp */
     112             : 
     113             :   /* output length passed with "quic hp" and "quic key" must be the key size from
     114             :      the current cipher */
     115           0 :   fd_quic_hkdf_expand_label(
     116           0 :       keys->hp_key, FD_AES_128_KEY_SZ,
     117           0 :       secret,
     118           0 :       FD_QUIC_CRYPTO_LABEL_QUIC_HP,
     119           0 :       FD_QUIC_CRYPTO_LABEL_QUIC_HP_LEN );
     120             : 
     121             :   /* quic iv */
     122           0 :   fd_quic_hkdf_expand_label(
     123           0 :       keys->iv, FD_AES_GCM_IV_SZ,
     124           0 :       secret,
     125           0 :       FD_QUIC_CRYPTO_LABEL_QUIC_IV,
     126           0 :       FD_QUIC_CRYPTO_LABEL_QUIC_IV_LEN );
     127           0 : }
     128             : 
     129             : /* encrypt a packet
     130             : 
     131             :    uses the keys in keys to encrypt the packet "pkt" with header "hdr"
     132             :    (of length pkt_sz, and hdr_sz respectively) into out.
     133             : 
     134             :    out should have enough space to contain the full output with extra space
     135             :    for a full block which depends on the cipher
     136             : 
     137             :    *out_sz is used to determine the amount of buffer space left at *out
     138             :      if enough space is not available, the function fails and returns
     139             :      FD_QUIC_FAILED
     140             :    *out_sz is also set to the number of bytes written into *out at the end
     141             : 
     142             :    args
     143             :      out               the destination for the encrypted output
     144             :      out_sz            a pointer to the size of the buffer (on input) and the size of
     145             :                          the written bytes (on output)
     146             :      hdr               the input header bytes
     147             :      hdr_sz            the size of the header in bytes
     148             :      pkt               the input packet bytes
     149             :      pkt_sz            the size of the packet in bytes (frames after packet number not including MAC tag)
     150             :      keys              a pointer to the keys to use
     151             :      pkt_number        needed to create the nonce used in encryption
     152             :                          likely points to the packet number within "hdr"
     153             :      pkt_number_sz     the size of the packet number in bytes
     154             :      */
     155             : 
     156             : int
     157             : fd_quic_crypto_encrypt(
     158             :     uchar *                        const out,
     159             :     ulong *                        const out_sz,
     160             :     uchar const *                  const hdr,
     161             :     ulong                          const hdr_sz,
     162             :     uchar const *                  const pkt,
     163             :     ulong                          const pkt_sz,
     164             :     fd_quic_crypto_keys_t const *  const pkt_keys,
     165             :     fd_quic_crypto_keys_t const *  const hp_keys,
     166           0 :     ulong                          const pkt_number ) {
     167             : 
     168             : 
     169             :   /* ensure we have enough space in the output buffer
     170             :      most space used by cipher:
     171             :        header bytes (just XORed)
     172             :        input bytes (encrypted)
     173             :        tag bytes */
     174             : 
     175             :   /* bound on the bytes needed for cipher output */
     176           0 :   ulong cipher_out_bound = hdr_sz + pkt_sz + FD_QUIC_CRYPTO_TAG_SZ;
     177             : 
     178           0 :   if( FD_UNLIKELY( *out_sz < cipher_out_bound ) ) {
     179           0 :     FD_DEBUG( FD_LOG_WARNING(( "fd_quic_crypto_encrypt: output buffer not big enough" )) );
     180           0 :     return FD_QUIC_FAILED;
     181           0 :   }
     182             : 
     183           0 :   if( FD_UNLIKELY( ( hdr_sz < 4 ) | ( hdr_sz > INT_MAX ) ) ) {
     184           0 :     FD_DEBUG( FD_LOG_WARNING(( "fd_quic_crypto_encrypt: packet header size out of bounds" )) );
     185           0 :     return FD_QUIC_FAILED;
     186           0 :   }
     187             : 
     188             :   /* bounds check */
     189           0 :   if( FD_UNLIKELY( pkt_sz > INT_MAX ) ) return FD_QUIC_FAILED;
     190             : 
     191             :   /* copy the header into the output */
     192           0 :   fd_memcpy( out, hdr, hdr_sz );
     193             : 
     194             :   /* first byte needed in a couple of places */
     195           0 :   uchar first = out[0];
     196           0 :   ulong pkt_number_sz = fd_quic_h0_pkt_num_len( first ) + 1u;
     197           0 :   uchar const * pkt_number_ptr = out + hdr_sz - pkt_number_sz;
     198             : 
     199           0 :   uchar nonce[FD_QUIC_NONCE_SZ] = {0};
     200           0 :   fd_quic_get_nonce( nonce, pkt_keys->iv, pkt_number );
     201             : 
     202             :   // Initial packets cipher uses AEAD_AES_128_GCM with keys derived from the Destination Connection ID field of the
     203             :   // first Initial packet sent by the client; see rfc9001 Section 5.2.
     204           0 :   fd_aes_gcm_t pkt_cipher[1];
     205           0 :   fd_aes_128_gcm_init( pkt_cipher, pkt_keys->pkt_key, nonce );
     206             : 
     207             :   /* cipher_text is start of encrypted packet bytes, which starts after the header */
     208           0 :   uchar * cipher_text = out + hdr_sz;
     209           0 :   uchar * tag         = cipher_text + pkt_sz;
     210           0 :   uchar * pkt_end     = tag + FD_QUIC_CRYPTO_TAG_SZ;
     211             : 
     212           0 :   fd_aes_gcm_encrypt( pkt_cipher, cipher_text, pkt, pkt_sz, hdr, hdr_sz, tag );
     213             : 
     214           0 :   *out_sz = (ulong)( pkt_end - out );
     215             : 
     216             :   /* Header protection */
     217             : 
     218             :   /* sample start is defined as 4 bytes after the start of the packet number
     219             :      so shorter packet numbers means sample starts later in the cipher text */
     220           0 :   uchar const * sample = pkt_number_ptr + 4;
     221             : 
     222           0 :   fd_aes_key_t ecb[1];
     223           0 :   fd_aes_set_encrypt_key( hp_keys->hp_key, 128, ecb );
     224           0 :   uchar hp_cipher[16];
     225           0 :   fd_aes_encrypt( sample, hp_cipher, ecb );
     226             : 
     227             :   /* hp_cipher is mask */
     228           0 :   uchar const * mask = hp_cipher;
     229             : 
     230           0 :   uchar long_hdr = first & 0x80u; /* long header? */
     231           0 :   out[0] ^= (uchar)( mask[0] & ( long_hdr ? 0x0fu : 0x1fu ) );
     232             : 
     233           0 :   ulong pkt_number_off = hdr_sz - pkt_number_sz;
     234             : 
     235           0 :   for( ulong j = 0; j < pkt_number_sz; ++j ) {
     236           0 :     out[pkt_number_off + j] ^= mask[1+j];
     237           0 :   }
     238             : 
     239           0 :   return FD_QUIC_SUCCESS;
     240           0 : }
     241             : 
     242             : int
     243             : fd_quic_crypto_decrypt(
     244             :     uchar *                       buf,
     245             :     ulong                         buf_sz,
     246             :     ulong                         pkt_number_off,
     247             :     ulong                         pkt_number,
     248           0 :     fd_quic_crypto_keys_t const * keys ) {
     249             : 
     250           0 :   if( FD_UNLIKELY( ( pkt_number_off >= buf_sz      ) |
     251           0 :                    ( buf_sz < FD_QUIC_SHORTEST_PKT ) ) ) {
     252           0 :     FD_DEBUG( FD_LOG_WARNING( ( "fd_quic_crypto_decrypt: cipher text buffer too small" ) ) );
     253           0 :     return FD_QUIC_FAILED;
     254           0 :   }
     255             : 
     256             :   /* Derive header size */
     257           0 :   uint    first         = buf[0];
     258           0 :   ulong   pkt_number_sz = fd_quic_h0_pkt_num_len( first ) + 1u;
     259           0 :   uchar * hdr           = buf;
     260           0 :   ulong   hdr_sz        = pkt_number_off + pkt_number_sz;
     261             : 
     262             :   /* calculate nonce for decryption
     263             :      nonce is quic-iv XORed with *reconstructed* packet-number
     264             :      packet number is 1-4 bytes, so only XOR last pkt_number_sz bytes */
     265           0 :   uchar nonce[FD_QUIC_NONCE_SZ] = {0};
     266           0 :   fd_quic_get_nonce( nonce, keys->iv, pkt_number );
     267             : 
     268           0 :   if( FD_UNLIKELY( ( buf_sz < hdr_sz ) |
     269           0 :                    ( buf_sz < hdr_sz+FD_QUIC_CRYPTO_TAG_SZ ) ) )
     270           0 :     return FD_QUIC_FAILED;
     271             : 
     272             :   /* Derive offsets
     273             : 
     274             :      +----------+ <-- buf
     275             :      | Header   |
     276             :      +----------+ <-- out
     277             :      | Payload  |
     278             :      +----------+ <-- gcm_tag
     279             :      | GCM Tag  |
     280             :      +----------+ <-- buf_end */
     281             : 
     282           0 :   uchar * const out     = buf     + hdr_sz;
     283           0 :   uchar * const buf_end = buf     + buf_sz;
     284           0 :   uchar * const gcm_tag = buf_end - FD_QUIC_CRYPTO_TAG_SZ;
     285           0 :   ulong   const gcm_sz  = (ulong)( gcm_tag - out );
     286             : 
     287           0 :   fd_aes_gcm_t pkt_cipher[1];
     288           0 :   fd_aes_128_gcm_init( pkt_cipher, keys->pkt_key, nonce );
     289             : 
     290           0 :   int decrypt_ok = fd_aes_gcm_decrypt( pkt_cipher,
     291           0 :                                        out /* ciphertext */, out /* plaintext */,
     292           0 :                                        gcm_sz,      /* size of plaintext */
     293           0 :                                        hdr, hdr_sz, /* associated data */
     294           0 :                                        gcm_tag      /* auth tag */ );
     295           0 :   if( FD_UNLIKELY( !decrypt_ok ) ) {
     296           0 :    FD_DEBUG( FD_LOG_WARNING(( "fd_aes_gcm_decrypt failed" )) );
     297           0 :    return FD_QUIC_FAILED;
     298           0 :   }
     299             : 
     300           0 :   return FD_QUIC_SUCCESS;
     301           0 : }
     302             : 
     303             : 
     304             : int
     305             : fd_quic_crypto_decrypt_hdr(
     306             :     uchar *                        buf,
     307             :     ulong                          buf_sz,
     308             :     ulong                          pkt_number_off,
     309           0 :     fd_quic_crypto_keys_t const *  keys ) {
     310             : 
     311             :   /* bounds checks */
     312           0 :   if( FD_UNLIKELY( ( buf_sz < FD_QUIC_CRYPTO_TAG_SZ ) |
     313           0 :                    ( pkt_number_off >= buf_sz       ) ) ) {
     314           0 :     FD_DEBUG( FD_LOG_WARNING(( "decrypt hdr: bounds checks failed" )) );
     315           0 :     return FD_QUIC_FAILED;
     316           0 :   }
     317             : 
     318           0 :   uint          first      = buf[0]; /* first byte */
     319           0 :   uint          long_hdr   = first & 0x80u;  /* long header? (this bit is not encrypted) */
     320           0 :   ulong         sample_off = pkt_number_off + 4;
     321             : 
     322           0 :   if( FD_UNLIKELY( sample_off + FD_QUIC_HP_SAMPLE_SZ > buf_sz ) ) {
     323           0 :     FD_DEBUG( FD_LOG_WARNING(( "decrypt hdr: not enough bytes for a sample" )) );
     324           0 :     return FD_QUIC_FAILED;
     325           0 :   }
     326             : 
     327           0 :   uchar * sample = buf + sample_off;
     328             : 
     329             :   /* TODO this is hardcoded to AES-128 */
     330           0 :   uchar hp_cipher[16];
     331           0 :   fd_aes_key_t ecb[1];
     332           0 :   fd_aes_set_encrypt_key( keys->hp_key, 128, ecb );
     333           0 :   fd_aes_encrypt( sample, hp_cipher, ecb );
     334             : 
     335             :   /* hp_cipher is mask */
     336           0 :   uchar const * mask = hp_cipher;
     337             : 
     338             :   /* undo first byte mask */
     339           0 :   first  ^= (uint)mask[0] & ( long_hdr ? 0x0fu : 0x1fu );
     340           0 :   buf[0]  = (uchar)first;
     341             : 
     342             :   /* now we can calculate the actual packet number size */
     343           0 :   ulong pkt_number_sz = fd_quic_h0_pkt_num_len( first ) + 1u;
     344           0 :   if( pkt_number_off+pkt_number_sz > buf_sz ) {
     345           0 :     FD_DEBUG( FD_LOG_WARNING(( "decrypt hdr: not enough bytes for packet number" )) );
     346           0 :     return FD_QUIC_FAILED;
     347           0 :   }
     348             : 
     349             :   /* undo packet number encryption */
     350           0 :   for( ulong j = 0u; j < pkt_number_sz; ++j ) {
     351           0 :     buf[ pkt_number_off + j ] ^= mask[ 1u+j ];
     352           0 :   }
     353             : 
     354           0 :   return FD_QUIC_SUCCESS;
     355           0 : }

Generated by: LCOV version 1.14