ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
quic_tls.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/**
5 * @file protocore_quic_tls.h
6 * @brief TLS 1.3 server handshake state machine for QUIC (RFC 9001 / RFC 8446).
7 *
8 * Drives the server side of the TLS 1.3 handshake that QUIC carries in CRYPTO frames. It ties the
9 * key schedule (protocore_tls13_kdf), the handshake messages (protocore_tls13_msg), and the transport parameters
10 * (quic_tp) together: it runs the transcript hash, consumes the client's ClientHello, produces the
11 * server flight (ServerHello at the Initial level; EncryptedExtensions + Certificate +
12 * CertificateVerify + Finished at the Handshake level), derives the Handshake and 1-RTT packet keys
13 * for both directions, and verifies the client's Finished.
14 *
15 * The transport engine (protocore_quic_conn) owns CRYPTO stream reassembly and packet protection; this module
16 * is transport-free. It consumes an in-order byte run (protocore_quic_tls_recv_crypto returns how many bytes it
17 * used) and exposes the outbound flight per encryption level (protocore_quic_tls_flight), so it is fully
18 * host-testable by feeding it a captured ClientHello and inspecting the flight and derived keys.
19 *
20 * Profile: TLS_AES_128_GCM_SHA256, X25519 (or the X25519MLKEM768 PQ/T hybrid when PROTOCORE_ENABLE_PQC_KEX),
21 * Ed25519 certificate, no PSK / 0-RTT / client authentication. A client that offers X25519MLKEM768 but
22 * sends only a classical key_share is answered with a HelloRetryRequest (RFC 8446 sec 4.1.4) so it
23 * retries with the hybrid share instead of being downgraded to X25519. The ephemeral X25519 private key
24 * and the ServerHello random are supplied in the config (the caller draws them from its RNG, or fixes
25 * them in a test).
26 *
27 * @author Douglas Quigg (dstroy0)
28 * @date 2026
29 */
30
31#ifndef PROTOCORE_QUIC_TLS_H
32#define PROTOCORE_QUIC_TLS_H
33
34#include "protocore_config.h" // the entry point: protocore_types.h for the widths
35
36#if PROTOCORE_ENABLE_HTTP3
37
38#include "network_drivers/presentation/http/http3/quic_crypto/quic_crypto.h" // the complete type a public struct below holds by value
39#include "network_drivers/presentation/http/http3/quic_tp/quic_tp.h" // the complete type a public struct below holds by value
40#include "network_drivers/tls/key_schedule/key_schedule.h" // the complete type a public struct below holds by value
41
43
44// This module holds nothing between calls, so it carves no borrow and states none. An entry
45// takes one all the same, and never reads it, so every namespace in the tree is invoked the
46// same way.
47
48/** @brief QUIC encryption levels (RFC 9001 sec 4). 0-RTT is not supported. */
49#define QUIC_ENC_INITIAL 0
50#define QUIC_ENC_HANDSHAKE 1
51#define QUIC_ENC_APP 2 ///< 1-RTT (application) keys
52
53/** @brief Server handshake configuration (certificate, key, transport params, ephemeral inputs). */
54typedef struct QuicTlsConfig
55{
56 const uint8_t *cert_der; ///< DER X.509 leaf certificate (Ed25519 public key)
57 size_t cert_len;
58 uint8_t ed25519_seed[32]; ///< Ed25519 private seed matching the certificate
59 QuicTransportParams params; ///< the server's transport parameters (caller sets the CIDs)
60 uint8_t ephemeral_priv[32]; ///< server X25519 private key
61 uint8_t random[32]; ///< ServerHello random
62#if PROTOCORE_ENABLE_PQC_KEX
63 uint8_t mlkem_m[32]; ///< ML-KEM Encaps randomness (X25519MLKEM768 hybrid); fresh per handshake
64#endif
65} QuicTlsConfig;
66
67/** @brief Handshake state (a mutually-exclusive internal state, not a wire value). */
68typedef enum PROTO_ENUM_PACKED
69{
70 QTLS_START = 0, ///< awaiting ClientHello
71 QTLS_WAIT_FINISHED, ///< server flight sent; awaiting client Finished
72 QTLS_DONE, ///< client Finished verified
73 QTLS_FAILED, ///< a fatal handshake error (see alert)
74} QtlsState;
75
76/** @brief One server handshake's state (fixed storage, no heap). */
77typedef struct
78{
79 QuicTlsConfig cfg;
80 uint8_t *transcript; ///< running Transcript-Hash over the handshake messages
81 Tls13KeySchedule ks; ///< TLS 1.3 key schedule, over @ref ks_store
82 uint8_t ks_store[PROTOCORE_TLS13_KS_BORROW]; ///< the schedule's terms and its HKDF's bytes
83 // The transcript hash and the one-off hashes taken beside it work out of these. Live and die with
84 // this connection, so no hash on the handshake path touches a pool.
85 uint8_t hash_work[PROTOCORE_TLS13_TRANSCRIPT_BORROW];
86 uint8_t hash_work2[PROTOCORE_TLS13_TRANSCRIPT_BORROW];
87 uint8_t sign_work[PROTOCORE_SHA512_BORROW]; ///< the CertificateVerify signature's SHA-512
88 uint8_t keys_work[PROTOCORE_QUIC_KEYS_BORROW]; ///< the packet-key expansion at each encryption level
89
90 QtlsState state;
91 uint8_t alert; ///< TLS alert code (RFC 8446 sec 6) when state == QTLS_FAILED
92#if PROTOCORE_ENABLE_PQC_KEX
93 proto_bool hrr_sent; ///< a HelloRetryRequest was sent (X25519MLKEM768); the next ClientHello is the retry
94#endif
95 proto_bool hs_keys_ready; ///< Handshake-level keys derived (after ServerHello)
96 proto_bool ap_keys_ready; ///< 1-RTT keys derived (after the server Finished)
97 proto_bool complete; ///< client Finished verified
98
99 QuicPacketKeys hs_client; ///< Handshake: opens client packets
100 QuicPacketKeys hs_server; ///< Handshake: seals server packets
101 QuicPacketKeys ap_client; ///< 1-RTT: opens client packets
102 QuicPacketKeys ap_server; ///< 1-RTT: seals server packets
103
104 uint8_t hs_finished_hash[TLS13_SECRET_MAX]; ///< H(ClientHello..server Finished), to verify client Finished
105
106#if PROTOCORE_ENABLE_PQC_KEX
107 uint8_t flight_initial[1400]; ///< outbound Initial CRYPTO (ServerHello; hybrid key_share is ~1.1 KB)
108#else
109 uint8_t flight_initial[256]; ///< outbound Initial CRYPTO (ServerHello)
110#endif
111 size_t flight_initial_len;
112 uint8_t flight_hs[PROTOCORE_H3_CRYPTO_BUF]; ///< outbound Handshake CRYPTO (EE..Finished)
113 size_t flight_hs_len;
114
115 QuicTransportParams peer; ///< the client's parsed transport parameters
116 proto_bool have_peer;
117} QuicTls;
118
119/** @brief What server_init takes: qt, cfg. */
120typedef struct
121{
122 QuicTls *qt;
123 const QuicTlsConfig *cfg;
124} QuicTlsServerServerInitArgs;
125
126/** @brief What recv_crypto takes: qt, level, data, len. */
127typedef struct
128{
129 QuicTls *qt;
130 int level;
131 const uint8_t *data;
132 size_t len;
133} QuicTlsServerRecvCryptoArgs;
134
135/** @brief What flight takes: qt, level, len. */
136typedef struct
137{
138 const QuicTls *qt;
139 int level;
140 size_t *len;
141} QuicTlsServerFlightArgs;
142
143/** @brief What keys takes: qt, level, is_server. */
144typedef struct
145{
146 QuicTls *qt;
147 int level;
148 proto_bool is_server;
149} QuicTlsServerKeysArgs;
150
151/** @brief What peer_params takes: qt. */
152typedef struct
153{
154 const QuicTls *qt;
155} QuicTlsServerPeerParamsArgs;
156
157/**
158 * @brief TLS 1.3 server handshake state machine for QUIC (RFC 9001 / RFC 8446).
159 *
160 * A caller sets the members a call takes, invokes it through ::QuicTlsServer with the bytes it runs
161 * out of, and reads the outcome off the same handle.
162 *
163 * QuicTlsServer.server_init_args.qt = ...;
164 * QuicTlsServer.server_init_args.cfg = ...;
165 * QuicTlsServer.server_init(work);
166 *
167 * @var QuicTlsServerNs::server_init_args what server_init takes: qt, cfg
168 * @var QuicTlsServerNs::recv_crypto_args what recv_crypto takes: qt, level, data, len
169 * @var QuicTlsServerNs::flight_args what flight takes: qt, level, len
170 * @var QuicTlsServerNs::keys_args what keys takes: qt, level, is_server
171 * @var QuicTlsServerNs::peer_params_args what peer_params takes: qt
172 * @var QuicTlsServerNs::ok a call's true/false outcome
173 * @var QuicTlsServerNs::n the count a call reports
174 * @var QuicTlsServerNs::bytes a pointer to the flight bytes and its length via len (0 if none). ...
175 * @var QuicTlsServerNs::pkt_keys what a call reports
176 * @var QuicTlsServerNs::peer what a call reports
177 * @var QuicTlsServerNs::server_init initialize a server handshake with cfg (copied). Resets the ...
178 * @var QuicTlsServerNs::recv_crypto feed in-order CRYPTO stream bytes for encryption level level. ...
179 * @var QuicTlsServerNs::flight the pending outbound CRYPTO flight for level (QUIC_ENC_INITIAL / ...
180 * @var QuicTlsServerNs::keys the packet-protection keys for level (QUIC_ENC_HANDSHAKE / ...
181 * @var QuicTlsServerNs::peer_params the client's parsed transport parameters (valid once the ...
182 *
183 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
184 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
185 * a caller drives every namespace the same way.
186 */
187typedef struct
188{
189 QuicTlsServerServerInitArgs server_init_args;
190 QuicTlsServerRecvCryptoArgs recv_crypto_args;
191 QuicTlsServerFlightArgs flight_args;
192 QuicTlsServerKeysArgs keys_args;
193 QuicTlsServerPeerParamsArgs peer_params_args;
194 proto_bool ok;
195 size_t n;
196 const uint8_t *bytes;
197 QuicPacketKeys *pkt_keys;
198 const QuicTransportParams *peer;
199} QuicTlsServerVars;
200
201/** @brief The operands and the outcome. */
202extern QuicTlsServerVars QuicTlsServerV;
203
204/** @brief The entries. */
205typedef struct
206{
207 void (*const server_init)(uint8_t *work);
208 void (*const recv_crypto)(uint8_t *work);
209 void (*const flight)(uint8_t *work);
210 void (*const keys)(uint8_t *work);
211 void (*const peer_params)(uint8_t *work);
212} QuicTlsServerNs;
213
214// What the table binds, defined once in the .c and taking one parameter each: everything
215// else an entry needs is an operand in QuicTlsServerV or a region of the borrow at a fixed offset.
216void protocore_quic_tls_server_server_init(uint8_t *work);
217void protocore_quic_tls_server_recv_crypto(uint8_t *work);
218void protocore_quic_tls_server_flight(uint8_t *work);
219void protocore_quic_tls_server_keys(uint8_t *work);
220void protocore_quic_tls_server_peer_params(uint8_t *work);
221
222// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
223// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
224// `QuicTlsServer.server_init(work)` resolves to a named function and becomes a DIRECT call. An extern table
225// leaves the call indirect and the symbol live at every level, -O2 -flto included.
226static const QuicTlsServerNs QuicTlsServer __attribute__((unused)) = {
227 .server_init = protocore_quic_tls_server_server_init,
228 .recv_crypto = protocore_quic_tls_server_recv_crypto,
229 .flight = protocore_quic_tls_server_flight,
230 .keys = protocore_quic_tls_server_keys,
231 .peer_params = protocore_quic_tls_server_peer_params,
232};
233
235
236#endif // PROTOCORE_ENABLE_HTTP3
237
238#endif // PROTOCORE_QUIC_TLS_H
#define PROTOCORE_H3_CRYPTO_BUF
Maximum bytes of one QUIC/TLS handshake CRYPTO flight (RFC 9001).
#define PROTOCORE_QUIC_KEYS_BORROW
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
#define PROTOCORE_SHA512_BORROW
#define PROTOCORE_TLS13_KS_BORROW
#define PROTOCORE_TLS13_TRANSCRIPT_BORROW
TLS 1.3 key schedule (RFC 8446 sec 7.1) for the QUIC handshake.
QUIC packet protection: Initial secrets, AEAD payload protection, header protection,...
QUIC transport parameters (RFC 9000 sec 18) carried in the TLS quic_transport_parameters extension (R...
The client/server packet-protection secrets for one QUIC encryption level.
Definition quic_crypto.h:47
The transport parameters we encode / decode, with RFC 9000 sec 18.2 defaults applied.
Definition quic_tp.h:58
#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