From: Frederic Lecaille Date: Fri, 16 Oct 2026 09:41:41 +0000 (+0200) Subject: MINOR: quic: use the same HP encryption helpers for RX and TX X-Git-Tag: v3.5-dev10~167 X-Git-Url: https://git.kaiwu.me/?a=commitdiff_plain;h=9a00cdaa152e68b37f3ab50687643990ef002727;p=haproxy.git MINOR: quic: use the same HP encryption helpers for RX and TX Header protection is applied by generating a mask and XORing it with the header. Removing it requires the same mask and the same XOR, which restores the original header. With AES-128-CTR, AES-256-CTR and ChaCha20, encryption and decryption produce the same mask from the same key and packet sample. This is a property of these algorithms, not specific to OpenSSL. Use quic_tls_enc_hp_ctx_init() and quic_tls_hp_encrypt() for RX too, with the RX key, and remove the redundant decryption helpers. --- diff --git a/include/haproxy/quic_tls.h b/include/haproxy/quic_tls.h index e0abc2355..1332f8509 100644 --- a/include/haproxy/quic_tls.h +++ b/include/haproxy/quic_tls.h @@ -120,14 +120,9 @@ int quic_tls_sec_update(const EVP_MD *md, const struct quic_version *qv, void quic_aead_iv_build(unsigned char *iv, size_t ivlen, unsigned char *aead_iv, size_t aead_ivlen, uint64_t pn); -/* HP protection (AES) */ -int quic_tls_dec_hp_ctx_init(EVP_CIPHER_CTX **aes_ctx, - const EVP_CIPHER *aes, unsigned char *key); +/* Header protection: encryption generates the mask for both TX and RX. */ int quic_tls_enc_hp_ctx_init(EVP_CIPHER_CTX **aes_ctx, const EVP_CIPHER *aes, unsigned char *key); -int quic_tls_hp_decrypt(unsigned char *out, - const unsigned char *in, size_t inlen, - EVP_CIPHER_CTX *ctx, unsigned char *key); int quic_tls_hp_encrypt(unsigned char *out, const unsigned char *in, size_t inlen, EVP_CIPHER_CTX *ctx, unsigned char *key); diff --git a/src/quic_rx.c b/src/quic_rx.c index 5fcb213e3..e90c76e09 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -95,7 +95,7 @@ static int qc_do_rm_hp(struct quic_conn *qc, sample = pn + QUIC_PACKET_PN_MAXLEN; - if (!quic_tls_hp_decrypt(mask, sample, sizeof mask, tls_ctx->rx.hp_ctx, tls_ctx->rx.hp_key)) { + if (!quic_tls_hp_encrypt(mask, sample, sizeof mask, tls_ctx->rx.hp_ctx, tls_ctx->rx.hp_key)) { TRACE_ERROR("HP removing failed", QUIC_EV_CONN_RMHP, qc, pkt); goto leave; } diff --git a/src/quic_ssl.c b/src/quic_ssl.c index c329bffe9..09fff51c8 100644 --- a/src/quic_ssl.c +++ b/src/quic_ssl.c @@ -250,7 +250,7 @@ static int ha_quic_set_encryption_secrets(SSL *ssl, enum ssl_encryption_level_t goto leave; } - if (!quic_tls_dec_hp_ctx_init(&rx->hp_ctx, rx->hp, rx->hp_key)) { + if (!quic_tls_enc_hp_ctx_init(&rx->hp_ctx, rx->hp, rx->hp_key)) { TRACE_ERROR("could not initial RX TLS cipher context for HP", QUIC_EV_CONN_RWSEC, qc); goto leave; } diff --git a/src/quic_tls.c b/src/quic_tls.c index 0838ffa6b..9f1726c25 100644 --- a/src/quic_tls.c +++ b/src/quic_tls.c @@ -696,7 +696,9 @@ int quic_tls_rx_ctx_init(QUIC_AEAD_CTX **rx_ctx, return 0; } -/* Initialize <*hp_ctx> cipher context with as key for header protection encryption */ +/* Initialize <*hp_ctx> with for header protection mask generation, + * both when applying (TX) and removing (RX) header protection. + */ int quic_tls_enc_hp_ctx_init(EVP_CIPHER_CTX **hp_ctx, const EVP_CIPHER *hp, unsigned char *key) { @@ -725,9 +727,9 @@ int quic_tls_enc_hp_ctx_init(EVP_CIPHER_CTX **hp_ctx, return 0; } -/* Encrypt bytes from buffer into with as - * cipher context. This is the responsibility of the caller to check there - * is at least bytes of available space in buffer. +/* Generate the header protection mask in using as the sample. + * The caller must provide zero bytes in . + * The same operation is used for TX and RX, with their respective keys. * Return 1 if succeeded, 0 if not. */ int quic_tls_hp_encrypt(unsigned char *out, @@ -763,73 +765,6 @@ int quic_tls_hp_encrypt(unsigned char *out, return 1; } -/* Initialize <*hp_ctx> cipher context with as key for header protection decryption */ -int quic_tls_dec_hp_ctx_init(EVP_CIPHER_CTX **hp_ctx, - const EVP_CIPHER *hp, unsigned char *key) -{ - EVP_CIPHER_CTX *ctx; - -#ifdef QUIC_AEAD_API - - if (hp == EVP_CIPHER_CHACHA20) { - *hp_ctx = EVP_CIPHER_CTX_CHACHA20; - return 1; - } -#endif - - ctx = EVP_CIPHER_CTX_new(); - if (!ctx) - return 0; - - if (!EVP_DecryptInit_ex(ctx, hp, NULL, key, NULL)) - goto err; - - *hp_ctx = ctx; - return 1; - - err: - EVP_CIPHER_CTX_free(ctx); - return 0; -} - -/* Decrypt data into with as cipher context. - * This is the responsibility of the caller to check there is at least - * bytes into buffer. - * Return 1 if succeeded, 0 if not. - */ -int quic_tls_hp_decrypt(unsigned char *out, - const unsigned char *in, size_t inlen, - EVP_CIPHER_CTX *ctx, unsigned char *key) -{ - int ret = 0; - -#ifdef QUIC_AEAD_API - if (ctx == EVP_CIPHER_CTX_CHACHA20) { - uint32_t counter; - - /* According to RFC 9001, 5.4.4. ChaCha20-Based Header Protection: - * The first 4 bytes of the sampled ciphertext are the block counter. - * The remaining 12 bytes are used as the nonce. - */ -#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - counter = (uint32_t)in[0] + (uint32_t)(in[1] << 8) + (uint32_t)(in[2] << 16) + (uint32_t)(in[3] << 24); -#else - memcpy(&counter, in, sizeof(counter)); -#endif - CRYPTO_chacha_20(out, out, inlen, key, in + sizeof(counter), counter); - return 1; - } - -#endif - - if (!EVP_DecryptInit_ex(ctx, NULL, NULL, NULL, in) || - !EVP_DecryptUpdate(ctx, out, &ret, out, inlen) || - !EVP_DecryptFinal_ex(ctx, out, &ret)) - return 0; - - return 1; -} - /* Initialize the cipher context for TX part of QUIC TLS context. * Return 1 if succeeded, 0 if not. */