ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
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 tls.h
6 * @brief The TLS connection resource (RFC 8446), and the no-op surface a build without TLS sees.
7 *
8 * This file owns the connection: ::TlsConn, its configuration and its handshake phase. The layers
9 * that act on it are beside it - handshake/ drives the handshake (RFC 8446 sec 4), record/ frames
10 * for it (sec 5), key_schedule/ derives its secrets (sec 7.1).
11 *
12 * The portable TLS 1.3 arm (PROTOCORE_ENABLE_TLS) is the implementation; a caller reaches it
13 * through ::TlsConnection. The slot-indexed protocore_tls_* calls below are no-ops, so a call site
14 * that predates the portable arm still compiles and needs no extra guards.
15 */
16
17#ifndef PROTOCORE_TLS_H
18#define PROTOCORE_TLS_H
19#include "protocore_config.h" // the entry point: the enable gate below, and the widths
20
21#if PROTOCORE_ENABLE_TLS
22
24
25// ---------------------------------------------------------------------------
26// The connection resource (RFC 8446): what a handshake and a record layer act on
27// ---------------------------------------------------------------------------
28// This file owns the connection; handshake/ drives it and record/ frames for it.
29
33
34/** @brief Handshake progress. A client and a server walk the same phases from opposite ends. */
35typedef enum PROTO_ENUM_PACKED
36{
37 TLS_CONN_START, ///< server: awaiting ClientHello; client: nothing sent yet
38 TLS_CONN_WAIT_SH, ///< client only: ClientHello sent, awaiting ServerHello
39 TLS_CONN_WAIT_FLIGHT, ///< client only: awaiting the encrypted server flight
40 TLS_CONN_WAIT_FINISHED, ///< server only: flight sent, awaiting the client Finished
41 TLS_CONN_DONE, ///< handshake complete; application keys installed
42 TLS_CONN_FAILED ///< fatal (see @ref TlsConnNs::alert)
43} TlsConnState;
44
45/** @brief Which end of the handshake this connection drives. */
46typedef enum PROTO_ENUM_PACKED
47{
48 TLS_ROLE_SERVER = 0,
49 TLS_ROLE_CLIENT
50} TlsRole;
51
52/**
53 * @brief The long-lived credential plus this handshake's fresh randomness.
54 *
55 * @c ed25519_seed is this end's signing key and @c ed25519_pub the raw public key it presents
56 * (RFC 7250); a client that only verifies leaves both null. @c peer_pub is the raw public key the
57 * peer must present - this is the whole of peer authentication on the portable arm, so a client
58 * with no @c peer_pub is encrypt-only and unauthenticated. @c ephemeral_priv and @c random must be
59 * freshly generated per connection from a CSPRNG.
60 */
61typedef struct
62{
63 const uint8_t *ed25519_seed; ///< 32-byte Ed25519 signing seed, or NULL when this end does not sign
64 const uint8_t *ed25519_pub; ///< 32-byte raw public key this end presents (matches @c ed25519_seed)
65 const uint8_t *peer_pub; ///< 32-byte raw public key the peer must present, or NULL to skip verification
66 const uint8_t *ephemeral_priv; ///< 32-byte X25519 ephemeral private key (fresh per handshake)
67 const uint8_t *random; ///< 32-byte Hello random (fresh per handshake)
68 const char *hostname; ///< client: the SNI to offer, and the name a certificate must speak for
69 // Client: the trust anchor a presented X.509 chain is validated to (RFC 5280 sec 6.1). When it
70 // is set the peer is authenticated by certificate - the chain to this anchor, and @c hostname
71 // matched by RFC 6125 - and @c peer_pub is not consulted. When it is null the peer is
72 // authenticated by raw public key against @c peer_pub, which is what this arm does by default.
73 const uint8_t *ca_der; ///< one DER trust anchor, or NULL
74 size_t ca_len; ///< its length
75 uint64_t now; ///< seconds since the POSIX epoch, for the validity window (sec 6.1.3 (a)(2))
76 // Server: the DER certificate to present instead of a raw public key. @c ed25519_seed still
77 // signs the CertificateVerify, so it must be the key this certificate carries.
78 const uint8_t *cert_der; ///< one DER certificate, or NULL to present the raw public key
79 size_t cert_len; ///< its length
80 // Server: the protocols this listener answers, in descending preference (RFC 7301 sec 3.1). A
81 // null list leaves ALPN unanswered; a client that offered it then gets no extension back.
82 const char *const *alpn; ///< NUL-terminated protocol names, or NULL
83 uint8_t alpn_count; ///< how many
84 // The suite this end runs: a client offers it, a server answers with it, and it fixes the AEAD,
85 // the key schedule's hash and the Transcript-Hash together (RFC 8446 sec 7.1). Zero-initialising
86 // the config leaves TLS_CIPHER_AES_128_GCM_SHA256, the sec 9.1 mandatory-to-implement suite.
87 TlsCipher cipher;
88} TlsConnConfig;
89
90/**
91 * @brief One TLS 1.3 handshake: the session state, and the three regions of one secure-pool borrow.
92 *
93 * The borrow is taken once by @ref TlsConnNs::init from the pool's persistent end and split by
94 * offset - TX at 0, RX at PROTOCORE_TLS_CONN_MSG_CAP, TERMS after it. No storage is declared here: a
95 * message is built in TX and handed to the record layer, a record is opened into RX, and the four
96 * TLS13_SECRET_MAX-strided handshake terms sit in TERMS. No heap.
97 */
98typedef struct
99{
100 const TlsConnConfig *cfg; ///< the caller's, borrowed for the life of the connection
101 TlsConnState state;
102 TlsRole role;
103 uint8_t alert; ///< RFC 8446 sec 6 alert code when @c state is FAILED (0 otherwise)
104
105 uint8_t *transcript; ///< running Transcript-Hash over the handshake messages
106 Tls13KeySchedule ks; ///< TLS 1.3 key schedule
107 TlsRecordKeys hs_tx; ///< handshake traffic keys, this end writing
108 TlsRecordKeys hs_rx; ///< handshake traffic keys, this end reading
109 TlsRecordKeys ap_tx; ///< application traffic keys, this end writing
110 TlsRecordKeys ap_rx; ///< application traffic keys, this end reading
111 proto_bool hrr_sent; ///< a HelloRetryRequest has been answered on this connection (sec 4.1.4)
112 proto_bool hs_keys_ready; ///< the handshake traffic keys are installed
113 proto_bool ap_keys_ready; ///< the application traffic keys are installed
114
115 uint8_t *tx; ///< PROTOCORE_TLS_CONN_MSG_CAP: a message built to send, or a received record opened into it
116 uint8_t *rx; ///< PROTOCORE_TLS_CONN_REC_CAP: the record the worker filled
117 uint8_t
118 *terms; ///< PROTOCORE_TLS_CONN_TERMS_CAP: the five terms, one TLS13_SECRET_MAX slot each, at TLS_TERM_* offsets
119 uint8_t *hash_work; ///< PROTOCORE_TLS13_TRANSCRIPT_BORROW: the bytes @ref transcript works out of
120 uint8_t *sign_work; ///< PROTOCORE_SHA512_BORROW: the bytes the CertificateVerify signature works out of
121 uint8_t *ks_work; ///< PROTOCORE_TLS13_KS_BORROW: the bytes the key schedule works out of
122 Tls13ClientHello *hello; ///< the peer's parsed ClientHello
123 const char *alpn; ///< the protocol selected from TlsConnConfig::alpn, or NULL when none was
124} TlsConn;
125
126// The handle that drives one connection (network_drivers/tls/handshake). Declared here because
127// this file owns ::TlsConn: the driver acts on the resource, so the resource publishes the seam
128// and the driver defines it. A separate header would have to include this one, and this one
129// would have to reach back for the handle.
130/** @brief RFC 8446 sec 4: which end this connection drives, and what it presents. */
131typedef struct
132{
133 TlsRole role; ///< which end of the handshake this connection drives
134 const TlsConnConfig *cfg; ///< the credential and this handshake's randomness
135} TlsInitArgs;
136
137/** @brief The bytes one call moves: a received record in, application data out. */
138typedef struct
139{
140 size_t rx_len; ///< how much of TlsConn::rx the worker filled
141 const uint8_t *data; ///< application bytes a seal takes
142 size_t len; ///< how many
143 const uint8_t *rec; ///< the received application record an open takes
144 size_t rec_len; ///< how many bytes of it there are
145} TlsConnIoArgs;
146
147/** @brief Where a call writes, and what it wrote. */
148typedef struct
149{
150 uint8_t *out; ///< where the records this end owes are written
151 size_t out_cap; ///< how much room it has
152 size_t *out_len; ///< where an open reports the plaintext length, or NULL
153} TlsConnOut;
154
155/**
156 * @brief One TLS 1.3 handshake over a stream. ::TlsConn is the resource; this drives it.
157 *
158 * A caller sets the members a call takes, invokes it through ::TlsConnection, and reads the outcome
159 * off the same handle. The connection itself is the caller's, named in @ref TlsConnNs::conn.
160 *
161 * @var TlsConnNs::conn the connection every call acts on
162 * @var TlsConnNs::init_args which end this connection drives, and what it presents
163 * @var TlsConnNs::io the bytes one call moves
164 * @var TlsConnNs::out_args where a call writes, and what it wrote
165 * @var TlsConnNs::ok a call's true/false outcome
166 * @var TlsConnNs::n bytes written to @c out_args.out
167 * @var TlsConnNs::i32 bytes written, or a negative alert-bearing failure
168 * @var TlsConnNs::u8 the alert a lookup reports
169 * @var TlsConnNs::init bind a connection to its role and configuration. The borrow is taken
170 * on first use and kept, so a connection initialised again reuses the
171 * bytes it already holds
172 * @var TlsConnNs::start client only: write the ClientHello; bytes written, or 0
173 * @var TlsConnNs::process the worker filled @ref TlsConn::rx with one record of @c io.rx_len
174 * bytes; consume it, writing whatever it owes into @c out_args.out
175 * @var TlsConnNs::established whether the handshake has completed
176 * @var TlsConnNs::alert the alert that ended the connection, or 0
177 * @var TlsConnNs::seal_app seal application data into one record; bytes written, or 0
178 * @var TlsConnNs::open_app open one received application record; false on an AEAD failure
179 *
180 * No storage member: one secure-pool borrow per connection lives in ::TlsConn, taken by
181 * @ref TlsConnNs::init and split by offset.
182 */
183typedef struct
184{
185 TlsConn *conn;
186 TlsInitArgs init_args;
187 TlsConnIoArgs io;
188 TlsConnOut out_args;
189 proto_bool ok;
190 size_t n;
191 int i32;
192 uint8_t u8;
193} TlsConnectionVars;
194
195/** @brief The operands and the outcome. */
196extern TlsConnectionVars TlsConnectionV;
197
198/** @brief The entries. */
199typedef struct
200{
201 void (*const init)(uint8_t *work);
202 void (*const start)(uint8_t *work);
203 void (*const process)(uint8_t *work);
204 void (*const established)(uint8_t *work);
205 void (*const alert)(uint8_t *work);
206 void (*const seal_app)(uint8_t *work);
207 void (*const open_app)(uint8_t *work);
208} TlsConnNs;
209
210// What the table binds, defined once in the .c and taking one parameter each: everything
211// else an entry needs is an operand in TlsConnectionV or a region of the borrow at a fixed offset.
212void protocore_tls_connection_init(uint8_t *work);
213void protocore_tls_connection_start(uint8_t *work);
214void protocore_tls_connection_process(uint8_t *work);
215void protocore_tls_connection_established(uint8_t *work);
216void protocore_tls_connection_alert(uint8_t *work);
217void protocore_tls_connection_seal_app(uint8_t *work);
218void protocore_tls_connection_open_app(uint8_t *work);
219
220// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
221// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
222// `TlsConnection.init(work)` resolves to a named function and becomes a DIRECT call. An extern table
223// leaves the call indirect and the symbol live at every level, -O2 -flto included.
224static const TlsConnNs TlsConnection __attribute__((unused)) = {
225 .init = protocore_tls_connection_init,
226 .start = protocore_tls_connection_start,
227 .process = protocore_tls_connection_process,
228 .established = protocore_tls_connection_established,
229 .alert = protocore_tls_connection_alert,
230 .seal_app = protocore_tls_connection_seal_app,
231 .open_app = protocore_tls_connection_open_app,
232};
233
234/** @brief The connection standing on @p slot, or NULL when the index is out of range. */
235TlsConn *protocore_tls_conn_at(uint8_t slot);
236
237/**
238 * @brief The protocol ALPN selected on @p slot, or NULL when none was negotiated.
239 *
240 * Points into the listener's ::TlsConnConfig::alpn list, which outlives the connection.
241 */
242const char *protocore_tls_alpn(uint8_t slot);
243
244/** @brief Where a stepped TLS exchange stands. */
245typedef enum PROTO_ENUM_PACKED
246{
247 PROTOCORE_TLS_READY = 0, ///< the move landed
248 PROTOCORE_TLS_BUSY, ///< the peer's bytes are still in flight; ask again on the next tick
249 PROTOCORE_TLS_FAILED, ///< the session did not stand up, or it is closed / fatal
250} protocore_tls_state;
251
252/**
253 * @brief Install the credential this end presents, before any connection begins.
254 *
255 * The credential is an RFC 7250 raw public key: @p cert is the 32-byte Ed25519 public key and
256 * @p key the 32-byte signing seed that matches it. This end presents no X.509 chain, and asks
257 * the peer for none.
258 *
259 * @return true once a connection may begin.
260 */
261proto_bool protocore_tls_global_init(const uint8_t *cert, size_t cert_len, const uint8_t *key, size_t key_len);
262
263/** @brief Whether a credential is installed and a connection may begin. */
264proto_bool protocore_tls_ready(void);
265
266/** @brief Stand a TLS connection up on @p slot, taking the pcb it will write through. */
267proto_bool protocore_tls_conn_begin(uint8_t slot);
268
269/**
270 * @brief Pump the handshake on @p slot with whatever ciphertext has arrived.
271 *
272 * @return < 0 the connection failed and must be aborted; 0 still handshaking, ask again when more
273 * ciphertext lands; > 0 the handshake completed.
274 */
275int protocore_tls_handshake(uint8_t slot);
276
277/** @brief Whether the handshake on @p slot has completed. */
278proto_bool protocore_tls_established(uint8_t slot);
279
280/**
281 * @brief Open one received application record on @p slot into @p buf.
282 *
283 * @return the plaintext length, 0 when no whole record has arrived, or < 0 on an AEAD failure,
284 * which RFC 8446 sec 5.2 makes fatal to the connection.
285 */
286int protocore_tls_read(uint8_t slot, uint8_t *buf, size_t len);
287
288/**
289 * @brief Seal @p len bytes into one application record on @p slot and put it on the wire.
290 *
291 * @return @p len once the record is queued, or < 0 when it did not fit one record or the transport
292 * refused it.
293 */
294int protocore_tls_write(uint8_t slot, const void *data, size_t len);
295
296/** @brief End the connection on @p slot and wipe every key generation it installed. */
297void protocore_tls_conn_end(uint8_t slot);
298
299/** @brief Release @p slot's connection without ending it: the transport is already gone. */
300void protocore_tls_conn_free(uint8_t slot);
301
302/** @brief The high-water mark of the pool this engine's connections are taken from. */
303size_t protocore_tls_arena_peak(void);
304
306
307#endif // PROTOCORE_ENABLE_TLS
308
309#endif // PROTOCORE_TLS_H
PROTO_ENUM_PACKED
Application protocol spoken on a listener port or connection slot.
TLS 1.3 key schedule (RFC 8446 sec 7.1) for the QUIC handshake.
TLS 1.3 record layer over a reliable stream (RFC 8446 sec 5).
TLS 1.3 handshake messages for the QUIC handshake (RFC 8446 sec 4).
#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