ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
quic_crypto.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4#ifndef PROTOCORE_QUIC_CRYPTO_H
5#define PROTOCORE_QUIC_CRYPTO_H
6
7#include "crypto/kdf/hkdf/hkdf.h" // the complete type a public struct below holds by value
8#include "protocore_config.h" // the entry point: protocore_types.h for the widths
9
11
12/**
13 * @file quic_crypto.h
14 * @brief QUIC packet protection: Initial secrets, AEAD payload protection, header protection,
15and the Retry integrity tag (RFC 9001).
16 *
17 * This ties the HKDF key schedule (protocore_hkdf) and AEAD_AES_128_GCM (aes128gcm) into the two QUIC
18 * packet-protection operations of RFC 9001 sec 5:
19 *
20 * - QuicCrypto.derive_initial_secrets runs the sec 5.2 Initial key derivation: a fixed salt and the
21 * client's Destination Connection ID produce the client and server {key, iv, hp} triples that
22 * protect Initial packets (the only keys available before the TLS handshake yields more).
23 * - QuicCrypto.packet_protect / QuicCrypto.packet_unprotect perform sec 5.3 AEAD payload protection and
24 * sec 5.4 header protection together, on a whole packet in a buffer. They take a {key, iv, hp}
25 * triple and a header form, so the same code protects Initial, Handshake, and 1-RTT packets -
26 * only the secrets differ. AES-128-GCM header protection samples a 16-byte AES-ECB block.
27 * - QuicCrypto.retry_integrity_tag computes the sec 5.8 Retry Integrity Tag (a fixed-key AEAD over the
28 * Retry Pseudo-Packet).
29 *
30 * Pure, zero heap, host-tested against RFC 9001 Appendix A (client Initial A.2, server Initial A.3,
31 * Retry A.4).
32 *
33 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
34 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
35 * a caller drives every namespace the same way.
36 *
37 * @author Douglas Quigg (dstroy0)
38 * @date 2026
39 */
40
41// PROTOCORE_QUIC_CRYPTO_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
42// it into its arena. Its size and its offset are each a static_assert, so a feature
43// combination that does not fit fails to compile rather than overrunning at run time.
44
45/** @brief The client/server packet-protection secrets for one QUIC encryption level. */
46typedef struct
47{
48 uint8_t gcm[PROTOCORE_AES128GCM_BORROW]; ///< this direction's AEAD borrow. Carries both keyed
49 ///< contexts: the record AEAD and the header-protection
50 ///< block. Replaces the raw keys, so neither stays
51 ///< resident, and both are keyed once.
52 uint8_t iv[12]; ///< AEAD nonce base (XOR'd with the padded packet number).
54
55/** @brief Both directions' Initial secrets derived from the client's Destination Connection ID. */
56typedef struct
57{
58 QuicPacketKeys client; ///< Protects client-sent Initial packets (server opens with this).
59 QuicPacketKeys server; ///< Protects server-sent Initial packets (server seals with this).
61
62/** @brief Dispatch table. Addressed by offset, so the layout is asserted below. */
63typedef struct
64{
65 void (*derive_initial_secrets)(uint8_t *, uint8_t *, const uint8_t *, size_t, QuicInitialSecrets *);
66 void (*keys_from_secret)(uint8_t *, uint8_t *, const uint8_t *, QuicPacketKeys *);
67 size_t (*packet_protect)(uint8_t *, uint8_t *, size_t, size_t, uint8_t, uint64_t, size_t, QuicPacketKeys *,
69 size_t (*packet_unprotect)(uint8_t *, uint8_t *, size_t, size_t, uint64_t, QuicPacketKeys *, proto_bool, uint8_t *,
70 uint64_t *);
71 void (*retry_integrity_tag)(uint8_t *, const uint8_t *, size_t, const uint8_t *, size_t, uint8_t *);
73PROTOCORE_NS_LAYOUT(QuicCryptoNs, derive_initial_secrets, keys_from_secret, packet_protect, packet_unprotect,
74 retry_integrity_tag);
75
76/**
77 * @brief Derive the Initial packet-protection secrets (RFC 9001 sec 5.2). .
78 * @param work PROTOCORE_QUIC_CRYPTO_BORROW bytes the caller took. Not held past the call.
79 * @param keys_work Keys work
80 * @param dcid Dcid
81 * @param dcid_len Dcid len
82 * @param out Out
83 */
84void protocore_quic_crypto_derive_initial_secrets(uint8_t *work, uint8_t *keys_work, const uint8_t *dcid,
85 size_t dcid_len, QuicInitialSecrets *out);
86/**
87 * @brief Expand one traffic secret into a {key, iv, hp} triple (RFC 9001 sec .
88 * @param work PROTOCORE_QUIC_CRYPTO_BORROW bytes the caller took. Not held past the call.
89 * @param keys_work Keys work
90 * @param secret PROTOCORE_HKDF_HASH_LEN bytes
91 * @param out Out
92 */
93void protocore_quic_crypto_keys_from_secret(uint8_t *work, uint8_t *keys_work, const uint8_t *secret,
94 QuicPacketKeys *out);
95/**
96 * @brief Protect one QUIC packet in place: AEAD-seal the payload, then apply .
97 * @param work PROTOCORE_QUIC_CRYPTO_BORROW bytes the caller took. Not held past the call.
98 * @param pkt Buffer holding header || plaintext payload; rewritten to header || ciphertext
99 * @param cap Capacity of pkt; must be >= pn_offset + pn_len + payload_len + 16
100 * @param pn_offset Offset of the packet number within the header
101 * @param pn_len Packet-number length in bytes (1..4)
102 * @param full_pn Full (untruncated) packet number, for the AEAD nonce
103 * @param payload_len Plaintext payload length in bytes
104 * @param keys The {key, iv, hp} triple for this encryption level
105 * @param is_long True for a long header (Initial/Handshake), false for a 1-RTT short header
106 * @return The size_t.
107 */
108size_t protocore_quic_crypto_packet_protect(uint8_t *work, uint8_t *pkt, size_t cap, size_t pn_offset, uint8_t pn_len,
109 uint64_t full_pn, size_t payload_len, QuicPacketKeys *keys,
110 proto_bool is_long);
111/**
112 * @brief Remove header protection and AEAD-open one QUIC packet in place .
113 * @param work PROTOCORE_QUIC_CRYPTO_BORROW bytes the caller took. Not held past the call.
114 * @param pkt Buffer holding the protected packet (mutated: header unprotected in place)
115 * @param pn_offset Offset of the protected packet number
116 * @param length QUIC Length field (packet-number + payload + tag bytes)
117 * @param largest_pn Largest packet number already received at this level (0 if none yet)
118 * @param keys The {key, iv, hp} triple for this encryption level
119 * @param is_long True for a long header, false for a 1-RTT short header
120 * @param out Output plaintext frames (>= length - pn_len - 16 bytes); may alias pkt payload
121 * @param out_pn Receives the reconstructed full packet number (may be NULL)
122 * @return The size_t.
123 */
124size_t protocore_quic_crypto_packet_unprotect(uint8_t *work, uint8_t *pkt, size_t pn_offset, size_t length,
125 uint64_t largest_pn, QuicPacketKeys *keys, proto_bool is_long,
126 uint8_t *out, uint64_t *out_pn);
127/**
128 * @brief Compute the Retry Integrity Tag (RFC 9001 sec 5.8). .
129 * @param work PROTOCORE_QUIC_CRYPTO_BORROW bytes the caller took. Not held past the call.
130 * @param odcid Original Destination Connection ID (from the client's first Initial)
131 * @param odcid_len ODCID length in bytes
132 * @param retry Retry packet bytes from the first byte up to (not including) the tag
133 * @param retry_len Length of retry
134 * @param tag Output 16-byte integrity tag 16 bytes
135 */
136void protocore_quic_crypto_retry_integrity_tag(uint8_t *work, const uint8_t *odcid, size_t odcid_len,
137 const uint8_t *retry, size_t retry_len, uint8_t *tag);
138
139/** @brief Module namespace. */
146
148
149#endif // PROTOCORE_QUIC_CRYPTO_H
#define PROTOCORE_AES128GCM_BORROW
HKDF-SHA256 (RFC 5869) and TLS 1.3 HKDF-Expand-Label (RFC 8446 sec 7.1).
#define PROTOCORE_NS_LAYOUT(T,...)
Pin every dispatch slot of a table that is nothing but function pointers.
#define PROTOCORE_NS
Storage for a dispatch table. The const is load bearing.
void protocore_quic_crypto_keys_from_secret(uint8_t *work, uint8_t *keys_work, const uint8_t *secret, QuicPacketKeys *out)
Expand one traffic secret into a {key, iv, hp} triple (RFC 9001 sec .
size_t protocore_quic_crypto_packet_unprotect(uint8_t *work, uint8_t *pkt, size_t pn_offset, size_t length, uint64_t largest_pn, QuicPacketKeys *keys, proto_bool is_long, uint8_t *out, uint64_t *out_pn)
Remove header protection and AEAD-open one QUIC packet in place .
size_t protocore_quic_crypto_packet_protect(uint8_t *work, uint8_t *pkt, size_t cap, size_t pn_offset, uint8_t pn_len, uint64_t full_pn, size_t payload_len, QuicPacketKeys *keys, proto_bool is_long)
Protect one QUIC packet in place: AEAD-seal the payload, then apply .
void protocore_quic_crypto_retry_integrity_tag(uint8_t *work, const uint8_t *odcid, size_t odcid_len, const uint8_t *retry, size_t retry_len, uint8_t *tag)
Compute the Retry Integrity Tag (RFC 9001 sec 5.8). .
void protocore_quic_crypto_derive_initial_secrets(uint8_t *work, uint8_t *keys_work, const uint8_t *dcid, size_t dcid_len, QuicInitialSecrets *out)
Derive the Initial packet-protection secrets (RFC 9001 sec 5.2). .
PROTOCORE_NS QuicCryptoNs QuicCrypto PROTOCORE_UNUSED
Module namespace.
Dispatch table. Addressed by offset, so the layout is asserted below.
Definition quic_crypto.h:64
void(* derive_initial_secrets)(uint8_t *, uint8_t *, const uint8_t *, size_t, QuicInitialSecrets *)
Definition quic_crypto.h:65
Both directions' Initial secrets derived from the client's Destination Connection ID.
Definition quic_crypto.h:57
QuicPacketKeys server
Protects server-sent Initial packets (server seals with this).
Definition quic_crypto.h:59
QuicPacketKeys client
Protects client-sent Initial packets (server opens with this).
Definition quic_crypto.h:58
The client/server packet-protection secrets for one QUIC encryption level.
Definition quic_crypto.h:47
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97