DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
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 (DETWS_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 DETWS_ENABLE_TLS and
18 * ARDUINO are both set) so call sites need no extra guards.
19 *
20 * Lifecycle per connection:
21 * @code
22 * det_tls_conn_begin(slot); // at accept on the TLS port
23 * // each EvtType::EVT_DATA, until established:
24 * int h = det_tls_handshake(slot); // 1 done, 0 pending, <0 fatal
25 * // once established, app data:
26 * int n = det_tls_read(slot, buf, sizeof buf); // >0 plaintext, 0 again, <0 closed
27 * det_tls_write(slot, data, len); // encrypts -> tcp_write
28 * det_tls_conn_end(slot); // close_notify + free slot ctx
29 * @endcode
30 */
31
32#ifndef DETERMINISTICESPASYNCWEBSERVER_DET_TLS_H
33#define DETERMINISTICESPASYNCWEBSERVER_DET_TLS_H
34
35#include "ServerConfig.h"
36#include <stddef.h>
37#include <stdint.h>
38
39#if DETWS_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 det_tls_global_init(const uint8_t *cert, size_t cert_len, const uint8_t *key, size_t key_len);
55
56/** @brief True once det_tls_global_init() has succeeded. */
57bool det_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 *det_tls_alpn(uint8_t slot);
64
65/** @brief Begin a TLS session on connection @p slot (sets up ssl_context + BIO). */
66bool det_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 det_tls_handshake(uint8_t slot);
74
75/** @brief True once the handshake on @p slot has completed. */
76bool det_tls_established(uint8_t slot);
77
78/**
79 * @brief Read decrypted application data from @p slot.
80 * @return >0 plaintext bytes, 0 if none are available yet, <0 on close/error.
81 */
82int det_tls_read(uint8_t slot, uint8_t *buf, size_t len);
83
84/**
85 * @brief Encrypt and send @p len bytes on @p slot (loops over partial writes).
86 * @return bytes written, or <0 on error.
87 */
88int det_tls_write(uint8_t slot, const void *data, size_t len);
89
90/** @brief Send close_notify and tear down the per-connection TLS context. */
91void det_tls_conn_end(uint8_t slot);
92
93/** @brief Tear down the TLS context without close_notify (abrupt disconnect/timeout). */
94void det_tls_conn_free(uint8_t slot);
95
96/** @brief Peak bytes ever used from the static arena (for sizing DETWS_TLS_ARENA_SIZE). */
97size_t det_tls_arena_peak();
98
99/**
100 * @brief TLS BIO send/recv callbacks (mbedTLS signatures) - the transport
101 * abstraction the engine reads/writes ciphertext through.
102 *
103 * Both sides conform to this: the server registers BIO functions that read the
104 * connection's rx ring and write via the transport (det_conn_raw_send), and the
105 * outbound client passes its own pair to det_tls_client_run(). The engine itself
106 * never touches lwIP directly.
107 */
108typedef int (*det_tls_bio_send_fn)(void *ctx, const unsigned char *buf, size_t len);
109typedef int (*det_tls_bio_recv_fn)(void *ctx, unsigned char *buf, size_t len);
110
111#if DETWS_ENABLE_MTLS
112/**
113 * @brief Require a verified client certificate (mTLS): install the trust-anchor CA.
114 *
115 * Call after det_tls_global_init(). Parses @p ca (PEM - length incl. the trailing
116 * NUL - or DER) as the CA chain and switches the server to
117 * MBEDTLS_SSL_VERIFY_REQUIRED, so the handshake demands a client certificate that
118 * chains to @p ca and aborts the connection otherwise.
119 *
120 * @return true on success; false if the engine is not initialized or the CA
121 * failed to parse.
122 */
123bool det_tls_set_client_ca(const uint8_t *ca, size_t ca_len);
124
125/**
126 * @brief Copy the established peer's certificate subject DN into @p out.
127 *
128 * Valid once the handshake on @p slot has completed with a verified client cert.
129 * @return the subject string length written (excl. NUL), or <0 if there is no
130 * verified peer certificate.
131 */
132int det_tls_peer_subject(uint8_t slot, char *out, size_t out_len);
133#endif // DETWS_ENABLE_MTLS
134
135#if DETWS_ENABLE_HTTP_CLIENT_TLS
136/**
137 * @brief Run a blocking client-side TLS exchange over caller-supplied BIO callbacks.
138 *
139 * Performs a TLS 1.2+ client handshake (SNI = @p host, server cert not verified -
140 * see note), writes @p req, then reads the decrypted response into @p out until
141 * the peer closes or @p out fills. Uses the shared static arena (installs the
142 * allocator if the server side has not). Yields with delay() while waiting, up to
143 * @p deadline_ms (millis() timestamp).
144 *
145 * NOTE: server authentication is OFF by default (no trust store on the device);
146 * the transport is encrypted but unauthenticated unless a CA and/or a cert pin is
147 * installed via det_tls_client_set_ca() / det_tls_client_set_pin().
148 *
149 * @return 0 on success (@p out_len set), <0 on handshake/verification/IO failure.
150 */
151int det_tls_client_run(const char *host, const uint8_t *req, size_t reqlen, uint8_t *out, size_t out_cap,
152 size_t *out_len, det_tls_bio_send_fn send_fn, det_tls_bio_recv_fn recv_fn, uint32_t deadline_ms);
153#endif // DETWS_ENABLE_HTTP_CLIENT_TLS
154
155#if DETWS_ENABLE_CLIENT_TLS
156/**
157 * @brief Install a CA trust anchor for outbound TLS (HTTPS/MQTTS) verification.
158 *
159 * Pass PEM (length incl. the trailing NUL) or DER; nullptr/0 clears it. With a CA
160 * installed, the client handshake verifies the server's certificate chain and its
161 * hostname (SNI) and aborts the connection on failure.
162 */
163void det_tls_client_set_ca(const uint8_t *ca, size_t ca_len);
164
165/**
166 * @brief Pin the outbound server's certificate by SHA-256 (32 bytes of the DER).
167 *
168 * After a successful handshake the peer certificate is hashed and constant-time
169 * compared to @p sha256; a mismatch (or no peer cert) fails the connection. Pass
170 * nullptr to clear. Can be combined with det_tls_client_set_ca().
171 */
172void det_tls_client_set_pin(const uint8_t sha256[32]);
173
174/** @brief Clear any installed client CA and cert pin (back to encrypt-only). */
175void det_tls_client_clear_verify();
176
177// --- Persistent client TLS session (one outbound connection at a time) ---
178// For a long-lived encrypted client (MQTTS): handshake once, then read/write
179// application data over the caller's BIO until det_tls_csess_end(). Honors the
180// CA/pin installed above. The BIO callbacks read ciphertext from the caller's
181// receive ring and write it to the socket.
182
183/** @brief Begin a client TLS session to @p host over the given BIO. @return false on setup failure. */
184bool det_tls_csess_begin(const char *host, det_tls_bio_send_fn send_fn, det_tls_bio_recv_fn recv_fn);
185
186/** @brief Advance the handshake. @return 1 established (CA/pin checked), 0 pending, <0 fatal. */
187int det_tls_csess_handshake();
188
189/** @brief Read decrypted application data. @return >0 bytes, 0 none yet, <0 closed/error. */
190int det_tls_csess_read(uint8_t *buf, size_t len);
191
192/** @brief Encrypt and send @p len bytes. @return bytes written, or <0 on error. */
193int det_tls_csess_write(const uint8_t *data, size_t len);
194
195/** @brief Send close_notify and tear down the session. */
196void det_tls_csess_end();
197
198/**
199 * @brief Discard the saved TLS session so the next csess handshake is a full one.
200 *
201 * With DETWS_ENABLE_TLS_RESUMPTION the client keeps the last session's ticket and
202 * presents it on the next det_tls_csess_begin() for an abbreviated handshake. Call
203 * this to force a fresh full handshake (e.g. after a credential change). A no-op
204 * when resumption is disabled.
205 */
206void det_tls_csess_forget_session();
207#endif // DETWS_ENABLE_CLIENT_TLS
208
209#else // stubs (TLS disabled or native build)
210
211static inline bool det_tls_global_init(const uint8_t *, size_t, const uint8_t *, size_t)
212{
213 return false;
214}
215static inline bool det_tls_ready()
216{
217 return false;
218}
219static inline bool det_tls_conn_begin(uint8_t)
220{
221 return false;
222}
223static inline int det_tls_handshake(uint8_t)
224{
225 return -1;
226}
227static inline bool det_tls_established(uint8_t)
228{
229 return false;
230}
231static inline int det_tls_read(uint8_t, uint8_t *, size_t)
232{
233 return -1;
234}
235static inline int det_tls_write(uint8_t, const void *, size_t)
236{
237 return -1;
238}
239static inline void det_tls_conn_end(uint8_t)
240{
241}
242static inline void det_tls_conn_free(uint8_t)
243{
244}
245static inline size_t det_tls_arena_peak()
246{
247 return 0;
248}
249
250#if DETWS_ENABLE_MTLS
251static inline bool det_tls_set_client_ca(const uint8_t *, size_t)
252{
253 return false;
254}
255static inline int det_tls_peer_subject(uint8_t, char *, size_t)
256{
257 return -1;
258}
259#endif // DETWS_ENABLE_MTLS
260
261#if DETWS_ENABLE_CLIENT_TLS
262typedef int (*det_tls_bio_send_fn)(void *ctx, const unsigned char *buf, size_t len);
263typedef int (*det_tls_bio_recv_fn)(void *ctx, unsigned char *buf, size_t len);
264static inline void det_tls_client_set_ca(const uint8_t *, size_t)
265{
266}
267static inline void det_tls_client_set_pin(const uint8_t *)
268{
269}
270static inline void det_tls_client_clear_verify()
271{
272}
273static inline bool det_tls_csess_begin(const char *, det_tls_bio_send_fn, det_tls_bio_recv_fn)
274{
275 return false;
276}
277static inline int det_tls_csess_handshake()
278{
279 return -1;
280}
281static inline int det_tls_csess_read(uint8_t *, size_t)
282{
283 return -1;
284}
285static inline int det_tls_csess_write(const uint8_t *, size_t)
286{
287 return -1;
288}
289static inline void det_tls_csess_end()
290{
291}
292static inline void det_tls_csess_forget_session()
293{
294}
295#endif // DETWS_ENABLE_CLIENT_TLS
296
297#if DETWS_ENABLE_HTTP_CLIENT_TLS
298static inline int det_tls_client_run(const char *, const uint8_t *, size_t, uint8_t *, size_t, size_t *,
299 det_tls_bio_send_fn, det_tls_bio_recv_fn, uint32_t)
300{
301 return -1;
302}
303#endif // DETWS_ENABLE_HTTP_CLIENT_TLS
304
305#endif // DETWS_ENABLE_TLS && ARDUINO
306
307#endif // DETERMINISTICESPASYNCWEBSERVER_DET_TLS_H
User-facing configuration for DeterministicESPAsyncWebServer.