ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
tls.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 tls.h
6 * @brief Deterministic TLS engine: mbedTLS over a static memory pool (PC_ENABLE_TLS).
7 *
8 * Wraps mbedTLS as a server-side TLS layer that keeps the library's zero-heap
9 * guarantee: mbedTLS is pointed at a fixed BSS arena via
10 * MBEDTLS_MEMORY_BUFFER_ALLOC_C (no system heap), per-connection ssl_context
11 * lives in BSS, the RNG is the ESP32 hardware CSPRNG, and the transport BIO is
12 * bridged directly to the existing lwIP `tcp_pcb` + per-connection rx ring - so
13 * there is no socket layer and no extra task. The handshake is pumped from the
14 * single `handle()` loop.
15 *
16 * ESP32/Arduino only - mbedTLS is not part of the native build. The header
17 * compiles everywhere (the functions are no-op stubs unless PC_ENABLE_TLS and
18 * ARDUINO are both set) so call sites need no extra guards.
19 *
20 * Lifecycle per connection:
21 * @code
22 * pc_tls_conn_begin(slot); // at accept on the TLS port
23 * // each EvtType::EVT_DATA, until established:
24 * int h = pc_tls_handshake(slot); // 1 done, 0 pending, <0 fatal
25 * // once established, app data:
26 * int n = pc_tls_read(slot, buf, sizeof buf); // >0 plaintext, 0 again, <0 closed
27 * pc_tls_write(slot, data, len); // encrypts -> tcp_write
28 * pc_tls_conn_end(slot); // close_notify + free slot ctx
29 * @endcode
30 */
31
32#ifndef PROTOCORE_TLS_H
33#define PROTOCORE_TLS_H
34
35#include "protocore_config.h"
36#include <stddef.h>
37#include <stdint.h>
38
39#if PC_ENABLE_TLS && defined(ARDUINO)
40
41/**
42 * @brief Initialize the global TLS engine: static pool, RNG, server cert/key.
43 *
44 * Call once before begin(). Parses the server certificate chain and private key
45 * (PEM - NUL-terminated incl. the terminator in the length - or DER) and builds
46 * the shared mbedTLS server config. All allocations come from the static arena.
47 *
48 * @param cert Certificate (chain) buffer.
49 * @param cert_len Length incl. the trailing NUL for PEM.
50 * @param key Private key buffer.
51 * @param key_len Length incl. the trailing NUL for PEM.
52 * @return true on success; false if the pool/cert/key setup failed.
53 */
54bool pc_tls_global_init(const uint8_t *cert, size_t cert_len, const uint8_t *key, size_t key_len);
55
56/** @brief True once pc_tls_global_init() has succeeded. */
57bool pc_tls_ready();
58
59/**
60 * @brief The ALPN protocol negotiated for @p slot ("h2" or "http/1.1"), or nullptr if the client
61 * offered no ALPN. Valid after the handshake completes. Used to select HTTP/2 vs HTTP/1.1.
62 */
63const char *pc_tls_alpn(uint8_t slot);
64
65/** @brief Begin a TLS session on connection @p slot (sets up ssl_context + BIO). */
66bool pc_tls_conn_begin(uint8_t slot);
67
68/**
69 * @brief Advance the TLS handshake for @p slot.
70 * @return 1 when established, 0 while still in progress (need more data),
71 * negative on a fatal error (caller should drop the connection).
72 */
73int pc_tls_handshake(uint8_t slot);
74
75#ifdef PC_TLS_HS_BENCH
76// Handshake-bench context (see tls.cpp): the last completed handshake's device-CPU time (summed over the
77// pumped mbedtls_ssl_handshake calls, so network waits between pumps are excluded) and wall time. The rig
78// firmware watches count and prints both. Compiled out unless PC_TLS_HS_BENCH is defined.
79struct TlsHsBenchCtx
80{
81 volatile long long last_cpu_us;
82 volatile long long last_wall_us;
83 volatile unsigned count;
84 volatile long long pumps[8]; // per-pump device CPU (us) for pumps > 2 ms - localizes the cost to a flight
85 volatile int n_pumps;
86};
87extern TlsHsBenchCtx pc_tls_hs_bench;
88#endif
89
90/** @brief True once the handshake on @p slot has completed. */
91bool pc_tls_established(uint8_t slot);
92
93/**
94 * @brief Read decrypted application data from @p slot.
95 * @return >0 plaintext bytes, 0 if none are available yet, <0 on close/error.
96 */
97int pc_tls_read(uint8_t slot, uint8_t *buf, size_t len);
98
99/**
100 * @brief Encrypt and send @p len bytes on @p slot (loops over partial writes).
101 * @return bytes written, or <0 on error.
102 */
103int pc_tls_write(uint8_t slot, const void *data, size_t len);
104
105/** @brief Send close_notify and tear down the per-connection TLS context. */
106void pc_tls_conn_end(uint8_t slot);
107
108/** @brief Tear down the TLS context without close_notify (abrupt disconnect/timeout). */
109void pc_tls_conn_free(uint8_t slot);
110
111/** @brief Peak bytes ever used from the static arena (for sizing PC_TLS_ARENA_SIZE). */
112size_t pc_tls_arena_peak();
113
114/**
115 * @brief TLS BIO send/recv callbacks (mbedTLS signatures) - the transport
116 * abstraction the engine reads/writes ciphertext through.
117 *
118 * Both sides conform to this: the server registers BIO functions that read the
119 * connection's rx ring and write via the transport (pc_conn_raw_send), and the
120 * outbound client passes its own pair to pc_tls_client_run(). The engine itself
121 * never touches lwIP directly.
122 */
123typedef int (*pc_tls_bio_send_fn)(void *ctx, const unsigned char *buf, size_t len);
124typedef int (*pc_tls_bio_recv_fn)(void *ctx, unsigned char *buf, size_t len);
125
126#if PC_ENABLE_MTLS
127/**
128 * @brief Require a verified client certificate (mTLS): install the trust-anchor CA.
129 *
130 * Call after pc_tls_global_init(). Parses @p ca (PEM - length incl. the trailing
131 * NUL - or DER) as the CA chain and switches the server to
132 * MBEDTLS_SSL_VERIFY_REQUIRED, so the handshake demands a client certificate that
133 * chains to @p ca and aborts the connection otherwise.
134 *
135 * @return true on success; false if the engine is not initialized or the CA
136 * failed to parse.
137 */
138bool pc_tls_set_client_ca(const uint8_t *ca, size_t ca_len);
139
140/**
141 * @brief Copy the established peer's certificate subject DN into @p out.
142 *
143 * Valid once the handshake on @p slot has completed with a verified client cert.
144 * @return the subject string length written (excl. NUL), or <0 if there is no
145 * verified peer certificate.
146 */
147int pc_tls_peer_subject(uint8_t slot, char *out, size_t out_len);
148#endif // PC_ENABLE_MTLS
149
150#if PC_ENABLE_HTTP_CLIENT_TLS
151/**
152 * @brief Run a blocking client-side TLS exchange over caller-supplied BIO callbacks.
153 *
154 * Performs a TLS 1.2+ client handshake (SNI = @p host, server cert not verified -
155 * see note), writes @p req, then reads the decrypted response into @p out until
156 * the peer closes or @p out fills. Uses the shared static arena (installs the
157 * allocator if the server side has not). Yields with pcdelay() while waiting, up to
158 * @p deadline_ms (millis() timestamp).
159 *
160 * NOTE: server authentication is OFF by default (no trust store on the device);
161 * the transport is encrypted but unauthenticated unless a CA and/or a cert pin is
162 * installed via pc_tls_client_set_ca() / pc_tls_client_set_pin().
163 *
164 * @return 0 on success (@p out_len set), <0 on handshake/verification/IO failure.
165 */
166int pc_tls_client_run(const char *host, const uint8_t *req, size_t reqlen, uint8_t *out, size_t out_cap,
167 size_t *out_len, pc_tls_bio_send_fn send_fn, pc_tls_bio_recv_fn recv_fn, uint32_t deadline_ms);
168#endif // PC_ENABLE_HTTP_CLIENT_TLS
169
170#if PC_ENABLE_CLIENT_TLS
171/**
172 * @brief Install a CA trust anchor for outbound TLS (HTTPS/MQTTS) verification.
173 *
174 * Pass PEM (length incl. the trailing NUL) or DER; nullptr/0 clears it. With a CA
175 * installed, the client handshake verifies the server's certificate chain and its
176 * hostname (SNI) and aborts the connection on failure.
177 */
178void pc_tls_client_set_ca(const uint8_t *ca, size_t ca_len);
179
180/**
181 * @brief Pin the outbound server's certificate by SHA-256 (32 bytes of the DER).
182 *
183 * After a successful handshake the peer certificate is hashed and constant-time
184 * compared to @p sha256; a mismatch (or no peer cert) fails the connection. Pass
185 * nullptr to clear. Can be combined with pc_tls_client_set_ca().
186 */
187void pc_tls_client_set_pin(const uint8_t sha256[32]);
188
189/** @brief Clear any installed client CA and cert pin (back to encrypt-only). */
190void pc_tls_client_clear_verify();
191
192// --- Persistent client TLS session (one outbound connection at a time) ---
193// For a long-lived encrypted client (MQTTS): handshake once, then read/write
194// application data over the caller's BIO until pc_tls_client_session_end(). Honors the
195// CA/pin installed above. The BIO callbacks read ciphertext from the caller's
196// receive ring and write it to the socket.
197
198/** @brief Begin a client TLS session to @p host over the given BIO. @return false on setup failure. */
199bool pc_tls_client_session_begin(const char *host, pc_tls_bio_send_fn send_fn, pc_tls_bio_recv_fn recv_fn);
200
201/** @brief True while a client TLS session is live (begun, not yet ended). The session is a singleton shared
202 * across all client-TLS users, so a would-be caller checks this to avoid tearing down an active session. */
203bool pc_tls_client_session_active();
204
205/** @brief Advance the handshake. @return 1 established (CA/pin checked), 0 pending, <0 fatal. */
206int pc_tls_client_session_handshake();
207
208/** @brief Read decrypted application data. @return >0 bytes, 0 none yet, <0 closed/error. */
209int pc_tls_client_session_read(uint8_t *buf, size_t len);
210
211/** @brief Encrypt and send @p len bytes. @return bytes written, or <0 on error. */
212int pc_tls_client_session_write(const uint8_t *data, size_t len);
213
214/** @brief Send close_notify and tear down the session. */
215void pc_tls_client_session_end();
216
217/**
218 * @brief Discard the saved TLS session so the next csess handshake is a full one.
219 *
220 * With PC_ENABLE_TLS_RESUMPTION the client keeps the last session's ticket and
221 * presents it on the next pc_tls_client_session_begin() for an abbreviated handshake. Call
222 * this to force a fresh full handshake (e.g. after a credential change). A no-op
223 * when resumption is disabled.
224 */
225void pc_tls_client_session_forget_session();
226#endif // PC_ENABLE_CLIENT_TLS
227
228#else // stubs (TLS disabled or native build)
229
230static inline bool pc_tls_global_init(const uint8_t *, size_t, const uint8_t *, size_t)
231{
232 return false;
233}
234static inline bool pc_tls_ready()
235{
236 return false;
237}
238static inline bool pc_tls_conn_begin(uint8_t)
239{
240 return false;
241}
242static inline int pc_tls_handshake(uint8_t)
243{
244 return -1;
245}
246static inline bool pc_tls_established(uint8_t)
247{
248 return false;
249}
250static inline int pc_tls_read(uint8_t, uint8_t *, size_t)
251{
252 return -1;
253}
254static inline int pc_tls_write(uint8_t, const void *, size_t)
255{
256 return -1;
257}
258static inline void pc_tls_conn_end(uint8_t)
259{
260}
261static inline void pc_tls_conn_free(uint8_t)
262{
263}
264static inline size_t pc_tls_arena_peak()
265{
266 return 0;
267}
268
269#if PC_ENABLE_MTLS
270static inline bool pc_tls_set_client_ca(const uint8_t *, size_t)
271{
272 return false;
273}
274static inline int pc_tls_peer_subject(uint8_t, char *, size_t)
275{
276 return -1;
277}
278#endif // PC_ENABLE_MTLS
279
280#if PC_ENABLE_CLIENT_TLS
281typedef int (*pc_tls_bio_send_fn)(void *ctx, const unsigned char *buf, size_t len);
282typedef int (*pc_tls_bio_recv_fn)(void *ctx, unsigned char *buf, size_t len);
283static inline void pc_tls_client_set_ca(const uint8_t *, size_t)
284{
285}
286static inline void pc_tls_client_set_pin(const uint8_t *)
287{
288}
289static inline void pc_tls_client_clear_verify()
290{
291}
292static inline bool pc_tls_client_session_begin(const char *, pc_tls_bio_send_fn, pc_tls_bio_recv_fn)
293{
294 return false;
295}
296static inline bool pc_tls_client_session_active()
297{
298 return false;
299}
300static inline int pc_tls_client_session_handshake()
301{
302 return -1;
303}
304static inline int pc_tls_client_session_read(uint8_t *, size_t)
305{
306 return -1;
307}
308static inline int pc_tls_client_session_write(const uint8_t *, size_t)
309{
310 return -1;
311}
312static inline void pc_tls_client_session_end()
313{
314}
315static inline void pc_tls_client_session_forget_session()
316{
317}
318#endif // PC_ENABLE_CLIENT_TLS
319
320#if PC_ENABLE_HTTP_CLIENT_TLS
321static inline int pc_tls_client_run(const char *, const uint8_t *, size_t, uint8_t *, size_t, size_t *,
322 pc_tls_bio_send_fn, pc_tls_bio_recv_fn, uint32_t)
323{
324 return -1;
325}
326#endif // PC_ENABLE_HTTP_CLIENT_TLS
327
328#endif // PC_ENABLE_TLS && ARDUINO
329
330#endif // PROTOCORE_TLS_H
User-facing configuration for ProtoCore.