]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: quic: Add reusable cipher contexts for header protection
authorFrédéric Lécaille <flecaille@haproxy.com>
Fri, 19 Aug 2022 16:18:13 +0000 (18:18 +0200)
committerFrédéric Lécaille <flecaille@haproxy.com>
Fri, 19 Aug 2022 16:31:59 +0000 (18:31 +0200)
Implement quic_tls_rx_hp_ctx_init() and quic_tls_tx_hp_ctx_init() to initiliaze
such header protection cipher contexts for each RX and TX parts and for each
packet number spaces, only one time by connection.
Make qc_new_isecs() call these two functions to initialize the cipher contexts
of the Initial secrets. Same thing for ha_quic_set_encryption_secrets() to
initialize the cipher contexts of the subsequent derived secrets (ORTT, 1RTT,
Handshake).
Modify qc_do_rm_hp() and quic_apply_header_protection() to reuse these
cipher contexts.
Note that there is no need to modify the key update for the header protection.
The header protection secrets are never updated.

include/haproxy/quic_tls-t.h
include/haproxy/quic_tls.h
src/quic_tls.c
src/xprt_quic.c

index dd2223542937a3bf3f2c46f6ef8d1e6aeb651c0b..2440a67dc1ca3487a110884161be6497f2b4c7b1 100644 (file)
@@ -138,6 +138,7 @@ struct quic_tls_secrets {
        EVP_CIPHER_CTX *ctx;
        const EVP_CIPHER *aead;
        const EVP_MD *md;
+       EVP_CIPHER_CTX *hp_ctx;
        const EVP_CIPHER *hp;
        unsigned char *secret;
        size_t secretlen;
index f4c1f0d16348c28a5d366a80a8fc1891831b3470..03ba400578f8074ede753f9fbb5c398cd107f9e1 100644 (file)
@@ -116,6 +116,18 @@ int quic_tls_sec_update(const EVP_MD *md, const struct quic_version *qv,
 int 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_aes_ctx_init(EVP_CIPHER_CTX **aes_ctx,
+                              const EVP_CIPHER *aes, unsigned char *key);
+int quic_tls_enc_aes_ctx_init(EVP_CIPHER_CTX **aes_ctx,
+                              const EVP_CIPHER *aes, unsigned char *key);
+int quic_tls_aes_decrypt(unsigned char *out,
+                         const unsigned char *in, size_t inlen,
+                         EVP_CIPHER_CTX *ctx);
+int quic_tls_aes_encrypt(unsigned char *out,
+                         const unsigned char *in, size_t inlen,
+                         EVP_CIPHER_CTX *ctx);
+
 static inline const EVP_CIPHER *tls_aead(const SSL_CIPHER *cipher)
 {
        switch (SSL_CIPHER_get_id(cipher)) {
@@ -381,10 +393,16 @@ static inline void quic_tls_ctx_secs_free(struct quic_tls_ctx *ctx)
                ctx->tx.keylen = 0;
        }
 
+       /* RX HP protection */
+       EVP_CIPHER_CTX_free(ctx->rx.hp_ctx);
+       /* RX AEAD decryption */
        EVP_CIPHER_CTX_free(ctx->rx.ctx);
        pool_free(pool_head_quic_tls_iv,  ctx->rx.iv);
        pool_free(pool_head_quic_tls_key, ctx->rx.key);
 
+       /* TX HP protection */
+       EVP_CIPHER_CTX_free(ctx->tx.hp_ctx);
+       /* TX AEAD encryption */
        EVP_CIPHER_CTX_free(ctx->tx.ctx);
        pool_free(pool_head_quic_tls_iv,  ctx->tx.iv);
        pool_free(pool_head_quic_tls_key, ctx->tx.key);
index 992fa8b8652372c4287f27b99abb752a43294f26..3114aa623cdd10b593173aebfd41d576e9918436 100644 (file)
@@ -341,6 +341,86 @@ int quic_tls_rx_ctx_init(EVP_CIPHER_CTX **rx_ctx,
        return 0;
 }
 
+/* Initialize <*aes_ctx> AES cipher context with <key> as key for encryption */
+int quic_tls_enc_aes_ctx_init(EVP_CIPHER_CTX **aes_ctx,
+                              const EVP_CIPHER *aes, unsigned char *key)
+{
+       EVP_CIPHER_CTX *ctx;
+
+       ctx = EVP_CIPHER_CTX_new();
+       if (!ctx)
+               return 0;
+
+       if (!EVP_EncryptInit_ex(ctx, aes, NULL, key, NULL))
+               goto err;
+
+       *aes_ctx = ctx;
+       return 1;
+
+ err:
+       EVP_CIPHER_CTX_free(ctx);
+       return 0;
+}
+
+/* Encrypt <inlen> bytes from <in> buffer into <out> with <ctx> as AES
+ * cipher context. This is the responsability of the caller to check there
+ * is at least <inlen> bytes of available space in <out> buffer.
+ * Return 1 if succeeded, 0 if not.
+ */
+int quic_tls_aes_encrypt(unsigned char *out,
+                         const unsigned char *in, size_t inlen,
+                         EVP_CIPHER_CTX *ctx)
+{
+       int ret = 0;
+
+       if (!EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, in) ||
+           !EVP_EncryptUpdate(ctx, out, &ret, out, inlen) ||
+           !EVP_EncryptFinal_ex(ctx, out, &ret))
+               return 0;
+
+       return 1;
+}
+
+/* Initialize <*aes_ctx> AES cipher context with <key> as key for decryption */
+int quic_tls_dec_aes_ctx_init(EVP_CIPHER_CTX **aes_ctx,
+                              const EVP_CIPHER *aes, unsigned char *key)
+{
+       EVP_CIPHER_CTX *ctx;
+
+       ctx = EVP_CIPHER_CTX_new();
+       if (!ctx)
+               return 0;
+
+       if (!EVP_DecryptInit_ex(ctx, aes, NULL, key, NULL))
+               goto err;
+
+       *aes_ctx = ctx;
+       return 1;
+
+ err:
+       EVP_CIPHER_CTX_free(ctx);
+       return 0;
+}
+
+/* Decrypt <in> data into <out> with <ctx> as AES cipher context.
+ * This is the responsability of the caller to check there is at least
+ * <outlen> bytes into <in> buffer.
+ * Return 1 if succeeded, 0 if not.
+ */
+int quic_tls_aes_decrypt(unsigned char *out,
+                         const unsigned char *in, size_t inlen,
+                         EVP_CIPHER_CTX *ctx)
+{
+       int ret = 0;
+
+       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 <tls_ctx> QUIC TLS context.
  * Return 1 if succeeded, 0 if not.
  */
index 9c42e524b201770b29ccbb6c6df95797780693de..e1f731da74c4f868205afb057b42663e9cc459c8 100644 (file)
@@ -920,6 +920,11 @@ int ha_quic_set_encryption_secrets(SSL *ssl, enum ssl_encryption_level_t level,
                goto leave;
        }
 
+       if (!quic_tls_dec_aes_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;
+       }
+
        /* Enqueue this connection asap if we could derive O-RTT secrets as
         * listener. Note that a listener derives only RX secrets for this
         * level.
@@ -944,6 +949,11 @@ int ha_quic_set_encryption_secrets(SSL *ssl, enum ssl_encryption_level_t level,
                goto leave;
        }
 
+       if (!quic_tls_enc_aes_ctx_init(&tx->hp_ctx, tx->hp, tx->hp_key)) {
+               TRACE_ERROR("could not initial TX TLS cipher context for HP", QUIC_EV_CONN_RWSEC, qc);
+               goto leave;
+       }
+
        if (level == ssl_encryption_application) {
                struct quic_tls_kp *prv_rx = &qc->ku.prv_rx;
                struct quic_tls_kp *nxt_rx = &qc->ku.nxt_rx;
@@ -1281,13 +1291,12 @@ static int qc_do_rm_hp(struct quic_conn *qc,
                        struct quic_rx_packet *pkt, struct quic_tls_ctx *tls_ctx,
                        int64_t largest_pn, unsigned char *pn, unsigned char *byte0)
 {
-       int ret, outlen, i, pnlen;
+       int ret, i, pnlen;
        uint64_t packet_number;
        uint32_t truncated_pn = 0;
        unsigned char mask[5] = {0};
        unsigned char *sample;
        EVP_CIPHER_CTX *cctx = NULL;
-       unsigned char *hp_key;
 
        TRACE_ENTER(QUIC_EV_CONN_RMHP, qc);
 
@@ -1307,11 +1316,8 @@ static int qc_do_rm_hp(struct quic_conn *qc,
 
        sample = pn + QUIC_PACKET_PN_MAXLEN;
 
-       hp_key = tls_ctx->rx.hp_key;
-       if (!EVP_DecryptInit_ex(cctx, tls_ctx->rx.hp, NULL, hp_key, sample) ||
-           !EVP_DecryptUpdate(cctx, mask, &outlen, mask, sizeof mask) ||
-           !EVP_DecryptFinal_ex(cctx, mask, &outlen)) {
-               TRACE_ERROR("decryption failed", QUIC_EV_CONN_RMHP, qc, pkt);
+       if (!quic_tls_aes_decrypt(mask, sample, sizeof mask, tls_ctx->rx.hp_ctx)) {
+               TRACE_ERROR("HP removing failed", QUIC_EV_CONN_RMHP, qc, pkt);
                goto leave;
        }
 
@@ -4670,7 +4676,7 @@ static struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
        char *buf_area = NULL;
        struct listener *l = NULL;
        struct quic_cc_algo *cc_algo = NULL;
-
+       struct quic_tls_ctx *ictx;
        TRACE_ENTER(QUIC_EV_CONN_INIT);
        qc = pool_zalloc(pool_head_quic_conn);
        if (!qc) {
@@ -4792,10 +4798,14 @@ static struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
            !quic_conn_init_idle_timer_task(qc))
                goto err;
 
-       if (!qc_new_isecs(qc, &qc->els[QUIC_TLS_ENC_LEVEL_INITIAL].tls_ctx,
-                         qc->original_version, dcid->data, dcid->len, 1))
+       ictx = &qc->els[QUIC_TLS_ENC_LEVEL_INITIAL].tls_ctx;
+       if (!qc_new_isecs(qc, ictx,qc->original_version, dcid->data, dcid->len, 1))
                goto err;
 
+       if (!quic_tls_dec_aes_ctx_init(&ictx->rx.hp_ctx, ictx->rx.hp, ictx->rx.hp_key) ||
+           !quic_tls_enc_aes_ctx_init(&ictx->tx.hp_ctx, ictx->tx.hp, ictx->tx.hp_key))
+           goto err;
+
        TRACE_LEAVE(QUIC_EV_CONN_INIT, qc);
 
        return qc;
@@ -6261,29 +6271,22 @@ static int quic_build_packet_short_header(unsigned char **buf, const unsigned ch
  * with <aead> as AEAD cipher and <key> as secret key.
  * Returns 1 if succeeded or 0 if failed.
  */
-static int quic_apply_header_protection(struct quic_conn *qc,
-                                        unsigned char *buf, unsigned char *pn, size_t pnlen,
-                                        const EVP_CIPHER *aead, const unsigned char *key)
+static int quic_apply_header_protection(struct quic_conn *qc, unsigned char *buf,
+                                        unsigned char *pn, size_t pnlen,
+                                        struct quic_tls_ctx *tls_ctx)
+
 {
-       int i, outlen, ret = 0;
-       EVP_CIPHER_CTX *ctx;
+       int i, ret = 0;
        /* We need an IV of at least 5 bytes: one byte for bytes #0
         * and at most 4 bytes for the packet number
         */
        unsigned char mask[5] = {0};
+       EVP_CIPHER_CTX *aes_ctx = tls_ctx->tx.hp_ctx;
 
        TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc);
 
-       ctx = EVP_CIPHER_CTX_new();
-       if (!ctx) {
-               TRACE_ERROR("cipher context allocation failed", QUIC_EV_CONN_TXPKT, qc);
-               goto out;
-       }
-
-       if (!EVP_EncryptInit_ex(ctx, aead, NULL, key, pn + QUIC_PACKET_PN_MAXLEN) ||
-           !EVP_EncryptUpdate(ctx, mask, &outlen, mask, sizeof mask) ||
-           !EVP_EncryptFinal_ex(ctx, mask, &outlen)) {
-               TRACE_ERROR("cipher context allocation failed", QUIC_EV_CONN_TXPKT, qc);
+       if (!quic_tls_aes_encrypt(mask, pn + QUIC_PACKET_PN_MAXLEN, sizeof mask, aes_ctx)) {
+               TRACE_ERROR("could not apply header protection", QUIC_EV_CONN_TXPKT, qc);
                goto out;
        }
 
@@ -6291,8 +6294,6 @@ static int quic_apply_header_protection(struct quic_conn *qc,
        for (i = 0; i < pnlen; i++)
                pn[i] ^= mask[i + 1];
 
-       EVP_CIPHER_CTX_free(ctx);
-
        ret = 1;
  out:
        TRACE_LEAVE(QUIC_EV_CONN_TXPKT, qc);
@@ -6971,8 +6972,7 @@ static struct quic_tx_packet *qc_build_pkt(unsigned char **pos,
 
        end += QUIC_TLS_TAG_LEN;
        pkt->len += QUIC_TLS_TAG_LEN;
-       if (!quic_apply_header_protection(qc, beg, buf_pn, pn_len,
-                                         tls_ctx->tx.hp, tls_ctx->tx.hp_key)) {
+       if (!quic_apply_header_protection(qc, beg, buf_pn, pn_len, tls_ctx)) {
                // trace already emitted by function above
                *err = -2;
                goto err;