DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
quic_crypto.h
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file quic_crypto.h
6 * @brief QUIC packet protection: Initial secrets, AEAD payload protection, header protection,
7 * and the Retry integrity tag (RFC 9001).
8 *
9 * This ties the HKDF key schedule (quic_hkdf) and AEAD_AES_128_GCM (quic_aead) into the two QUIC
10 * packet-protection operations of RFC 9001 sec 5:
11 *
12 * - quic_derive_initial_secrets() runs the sec 5.2 Initial key derivation: a fixed salt and the
13 * client's Destination Connection ID produce the client and server {key, iv, hp} triples that
14 * protect Initial packets (the only keys available before the TLS handshake yields more).
15 * - quic_packet_protect() / quic_packet_unprotect() perform sec 5.3 AEAD payload protection and
16 * sec 5.4 header protection together, on a whole packet in a buffer. They take a {key, iv, hp}
17 * triple and a header form, so the same code protects Initial, Handshake, and 1-RTT packets -
18 * only the secrets differ. AES-128-GCM header protection samples a 16-byte AES-ECB block.
19 * - quic_retry_integrity_tag() computes the sec 5.8 Retry Integrity Tag (a fixed-key AEAD over the
20 * Retry Pseudo-Packet).
21 *
22 * Pure, zero heap, host-tested against RFC 9001 Appendix A (client Initial A.2, server Initial A.3,
23 * Retry A.4).
24 *
25 * @author Douglas Quigg (dstroy0)
26 * @date 2026
27 */
28
29#ifndef DETERMINISTICESPASYNCWEBSERVER_QUIC_CRYPTO_H
30#define DETERMINISTICESPASYNCWEBSERVER_QUIC_CRYPTO_H
31
32#include "ServerConfig.h"
33
34#if DETWS_ENABLE_HTTP3
35
36#include "network_drivers/presentation/http3/quic_hkdf.h" // QUIC_HKDF_HASH_LEN
37#include <stddef.h>
38#include <stdint.h>
39
40/** @brief The client/server packet-protection secrets for one QUIC encryption level. */
41struct QuicPacketKeys
42{
43 uint8_t key[16]; ///< AEAD_AES_128_GCM key.
44 uint8_t iv[12]; ///< AEAD nonce base (XOR'd with the padded packet number).
45 uint8_t hp[16]; ///< Header-protection key (AES-128-ECB mask).
46};
47
48/** @brief Both directions' Initial secrets derived from the client's Destination Connection ID. */
49struct QuicInitialSecrets
50{
51 QuicPacketKeys client; ///< Protects client-sent Initial packets (server opens with this).
52 QuicPacketKeys server; ///< Protects server-sent Initial packets (server seals with this).
53};
54
55/**
56 * @brief Derive the Initial packet-protection secrets (RFC 9001 sec 5.2).
57 *
58 * initial_secret = HKDF-Extract(initial_salt, dcid); each direction's traffic secret is an
59 * HKDF-Expand-Label ("client in" / "server in") of that, and key/iv/hp are expanded from the
60 * traffic secret. @p dcid is the Destination Connection ID from the client's first Initial packet.
61 */
62void quic_derive_initial_secrets(const uint8_t *dcid, size_t dcid_len, QuicInitialSecrets *out);
63
64/**
65 * @brief Expand one traffic secret into a {key, iv, hp} triple (RFC 9001 sec 5.1 labels).
66 *
67 * key = HKDF-Expand-Label(secret, "quic key", 16), iv = "quic iv" (12), hp = "quic hp" (16). The
68 * Initial derivation uses this internally; the Handshake and 1-RTT levels call it directly on the
69 * TLS-derived handshake / application traffic secrets so every level shares one code path.
70 */
71void quic_keys_from_secret(const uint8_t secret[QUIC_HKDF_HASH_LEN], QuicPacketKeys *out);
72
73/**
74 * @brief Protect one QUIC packet in place: AEAD-seal the payload, then apply header protection.
75 *
76 * On entry @p pkt holds the unprotected header (flags with reserved bits 0, the packet number in
77 * the clear) in bytes [0, pn_offset + pn_len), immediately followed by @p payload_len plaintext
78 * frame bytes. On return @p pkt holds the header (now protected) followed by the ciphertext and
79 * 16-byte tag. The header in [0, pn_offset + pn_len) is used as the AEAD associated data before
80 * protection is applied.
81 *
82 * @param pkt Buffer holding header || plaintext payload; rewritten to header || ciphertext.
83 * @param cap Capacity of @p pkt; must be >= pn_offset + pn_len + payload_len + 16.
84 * @param pn_offset Offset of the packet number within the header.
85 * @param pn_len Packet-number length in bytes (1..4).
86 * @param full_pn Full (untruncated) packet number, for the AEAD nonce.
87 * @param payload_len Plaintext payload length in bytes.
88 * @param keys The {key, iv, hp} triple for this encryption level.
89 * @param is_long True for a long header (Initial/Handshake), false for a 1-RTT short header.
90 * @return total protected packet length, or 0 on a capacity/parameter error.
91 */
92size_t quic_packet_protect(uint8_t *pkt, size_t cap, size_t pn_offset, uint8_t pn_len, uint64_t full_pn,
93 size_t payload_len, const QuicPacketKeys *keys, bool is_long);
94
95/**
96 * @brief Remove header protection and AEAD-open one QUIC packet in place (RFC 9001 sec 5.3/5.4).
97 *
98 * @p pkt holds the received packet on the wire. @p pn_offset is the offset of the (protected)
99 * packet number, found by parsing the header up to the Length field. @p length is the QUIC Length
100 * field value (packet-number bytes + protected payload + 16-byte tag). Header protection is removed
101 * first (recovering the true packet-number length and value), the packet number is reconstructed
102 * against @p largest_pn (RFC 9000 sec 17.1), and the payload is verified and decrypted.
103 *
104 * @param pkt Buffer holding the protected packet (mutated: header unprotected in place).
105 * @param pn_offset Offset of the protected packet number.
106 * @param length QUIC Length field (packet-number + payload + tag bytes).
107 * @param largest_pn Largest packet number already received at this level (0 if none yet).
108 * @param keys The {key, iv, hp} triple for this encryption level.
109 * @param is_long True for a long header, false for a 1-RTT short header.
110 * @param out Output plaintext frames (>= length - pn_len - 16 bytes); may alias @p pkt payload.
111 * @param out_pn Receives the reconstructed full packet number (may be NULL).
112 * @return plaintext length, or (size_t)-1 on parameter error or AEAD authentication failure.
113 */
114size_t quic_packet_unprotect(uint8_t *pkt, size_t pn_offset, size_t length, uint64_t largest_pn,
115 const QuicPacketKeys *keys, bool is_long, uint8_t *out, uint64_t *out_pn);
116
117/**
118 * @brief Compute the Retry Integrity Tag (RFC 9001 sec 5.8).
119 *
120 * AEAD_AES_128_GCM with the version-1 fixed key and nonce over the Retry Pseudo-Packet, whose AAD
121 * is the original Destination Connection ID (length-prefixed) followed by the Retry packet up to
122 * but excluding the tag. Plaintext is empty, so the whole 16-byte output is the tag.
123 *
124 * @param odcid Original Destination Connection ID (from the client's first Initial).
125 * @param odcid_len ODCID length in bytes.
126 * @param retry Retry packet bytes from the first byte up to (not including) the tag.
127 * @param retry_len Length of @p retry.
128 * @param tag Output 16-byte integrity tag.
129 */
130void quic_retry_integrity_tag(const uint8_t *odcid, size_t odcid_len, const uint8_t *retry, size_t retry_len,
131 uint8_t tag[16]);
132
133#endif // DETWS_ENABLE_HTTP3
134#endif // DETERMINISTICESPASYNCWEBSERVER_QUIC_CRYPTO_H
User-facing configuration for DeterministicESPAsyncWebServer.
HKDF-SHA256 (RFC 5869) and TLS 1.3 HKDF-Expand-Label (RFC 8446 sec 7.1).