DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
tls13_msg.cpp
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 tls13_msg.cpp
6 * @brief TLS 1.3 handshake messages for the QUIC handshake (see tls13_msg.h).
7 */
8
10
11#if (DETWS_ENABLE_HTTP3 || DETWS_ENABLE_DTLS)
12
14#if DETWS_ENABLE_PQC_KEX
15#include "network_drivers/presentation/pqc/mlkem.h" // MLKEM768_EK_BYTES (X25519MLKEM768 share sizing)
16#endif
17#include <string.h>
18
19// TLS extension types used here (RFC 8446 sec 4.2 + RFC 9001).
20struct TlsExt
21{
22 static constexpr uint16_t TLS_EXT_SERVER_NAME = 0x0000;
23 static constexpr uint16_t TLS_EXT_SUPPORTED_GROUPS = 0x000a;
24 static constexpr uint16_t TLS_EXT_SIGNATURE_ALGORITHMS = 0x000d;
25 static constexpr uint16_t TLS_EXT_ALPN = 0x0010;
26 static constexpr uint16_t TLS_EXT_SUPPORTED_VERSIONS = 0x002b;
27 static constexpr uint16_t TLS_EXT_COOKIE = 0x002c;
28 static constexpr uint16_t TLS_EXT_KEY_SHARE = 0x0033;
29 static constexpr uint16_t TLS_EXT_CONNECTION_ID = 0x0036; ///< RFC 9146 / RFC 9147 §9
30};
31
32// ---------------------------------------------------------------------------
33// A minimal bounds-checked byte writer (back-patches length prefixes).
34// ---------------------------------------------------------------------------
35namespace
36{
37struct Writer
38{
39 uint8_t *buf;
40 size_t cap;
41 size_t pos;
42 bool ok;
43};
44
45void w_u8(Writer *w, uint8_t v)
46{
47 // pos <= cap is an invariant (every write advances pos by exactly the checked amount), so the
48 // comparison is written to avoid a size_t addition that could wrap (cpp:S3519).
49 if (w->pos >= w->cap)
50 {
51 w->ok = false;
52 return;
53 }
54 w->buf[w->pos++] = v;
55}
56void w_u16(Writer *w, uint16_t v)
57{
58 w_u8(w, (uint8_t)(v >> 8));
59 w_u8(w, (uint8_t)v);
60}
61void w_u24(Writer *w, uint32_t v)
62{
63 w_u8(w, (uint8_t)(v >> 16));
64 w_u8(w, (uint8_t)(v >> 8));
65 w_u8(w, (uint8_t)v);
66}
67void w_bytes(Writer *w, const uint8_t *b, size_t n)
68{
69 if (n == 0)
70 return;
71 // Direct pre-copy bound: pos must be within cap AND n must fit in the remaining cap - pos. Written as
72 // two explicit guards (no intermediate) so the copy runs only when pos + n <= cap - no underflow of
73 // cap - pos (guarded by pos <= cap) and no pos + n wrap (never formed).
74 if (w->pos > w->cap || n > w->cap - w->pos)
75 {
76 w->ok = false;
77 return;
78 }
79 memcpy(w->buf + w->pos, b, n);
80 w->pos += n;
81}
82// Reserve a 2- or 3-byte length placeholder; returns its position for w_patch16/24.
83size_t w_mark(Writer *w, size_t nbytes)
84{
85 size_t at = w->pos;
86 for (size_t i = 0; i < nbytes; i++)
87 w_u8(w, 0);
88 return at;
89}
90void w_patch16(Writer *w, size_t at)
91{
92 if (!w->ok)
93 return;
94 uint16_t len = (uint16_t)(w->pos - at - 2);
95 w->buf[at] = (uint8_t)(len >> 8);
96 w->buf[at + 1] = (uint8_t)len;
97}
98void w_patch24(Writer *w, size_t at)
99{
100 if (!w->ok)
101 return;
102 uint32_t len = (uint32_t)(w->pos - at - 3);
103 w->buf[at] = (uint8_t)(len >> 16);
104 w->buf[at + 1] = (uint8_t)(len >> 8);
105 w->buf[at + 2] = (uint8_t)len;
106}
107
108// A bounds-checked reader.
109struct Reader
110{
111 const uint8_t *buf;
112 size_t len;
113 size_t pos;
114};
115bool r_u8(Reader *r, uint8_t *v)
116{
117 if (r->pos + 1 > r->len)
118 return false;
119 *v = r->buf[r->pos++];
120 return true;
121}
122bool r_u16(Reader *r, uint16_t *v)
123{
124 if (r->pos + 2 > r->len)
125 return false;
126 *v = (uint16_t)((r->buf[r->pos] << 8) | r->buf[r->pos + 1]);
127 r->pos += 2;
128 return true;
129}
130bool r_u24(Reader *r, uint32_t *v)
131{
132 if (r->pos + 3 > r->len)
133 return false;
134 *v = (uint32_t)((r->buf[r->pos] << 16) | (r->buf[r->pos + 1] << 8) | r->buf[r->pos + 2]);
135 r->pos += 3;
136 return true;
137}
138// Take a view of the next n bytes.
139bool r_take(Reader *r, size_t n, const uint8_t **out)
140{
141 if (r->pos + n > r->len)
142 return false;
143 *out = r->buf + r->pos;
144 r->pos += n;
145 return true;
146}
147} // namespace
148
149// ---------------------------------------------------------------------------
150// ClientHello parsing
151// ---------------------------------------------------------------------------
152namespace
153{
154// Scan a list of 2-byte values for a target (used for versions/groups/sig algs).
155bool list16_contains(const uint8_t *body, size_t body_len, size_t list_len, uint16_t target)
156{
157 if (list_len > body_len || (list_len % 2) != 0)
158 return false;
159 for (size_t i = 0; i + 1 < list_len; i += 2)
160 if (((body[i] << 8) | body[i + 1]) == target)
161 return true;
162 return false;
163}
164
165// KEY_SHARE (RFC 8446 §4.2.8): 2-byte client_shares length, then KeyShareEntry { group(2), key_exchange<2> }.
166void parse_key_share(const uint8_t *body, size_t blen, Tls13ClientHello *out)
167{
168 if (blen < 2)
169 return;
170 size_t ll = (body[0] << 8) | body[1];
171 if (ll + 2 > blen)
172 return;
173 size_t i = 2;
174 size_t end = 2 + ll;
175 while (i + 4 <= end)
176 {
177 uint16_t group = (uint16_t)((body[i] << 8) | body[i + 1]);
178 uint16_t klen = (uint16_t)((body[i + 2] << 8) | body[i + 3]);
179 i += 4;
180 if (i + klen > end)
181 return;
182 if (group == TLS_GROUP_X25519 && klen == 32)
183 {
184 memcpy(out->client_x25519, body + i, 32);
185 out->has_key_share = true;
186 }
187#if DETWS_ENABLE_PQC_KEX
188 else if (group == TLS_GROUP_X25519MLKEM768 && klen == MLKEM768_EK_BYTES + 32)
189 {
190 out->client_mlkem_ek = body + i; // ML-KEM-768 ek (first)
191 memcpy(out->client_x25519, body + i + MLKEM768_EK_BYTES, 32); // X25519 (second)
192 out->has_hybrid_share = true;
193 }
194#endif
195 i += klen;
196 }
197}
198
199// ALPN (RFC 7301): 2-byte list length, then entries of 1-byte name length + name. Flags an "h3" offer.
200void parse_alpn(const uint8_t *body, size_t blen, Tls13ClientHello *out)
201{
202 if (blen < 2)
203 return;
204 size_t ll = (body[0] << 8) | body[1];
205 if (ll + 2 > blen)
206 return;
207 size_t i = 2;
208 size_t end = 2 + ll;
209 while (i + 1 <= end)
210 {
211 size_t nl = body[i++];
212 if (i + nl > end)
213 return;
214 if (nl == 2 && body[i] == 'h' && body[i + 1] == '3')
215 out->offers_h3_alpn = true;
216 i += nl;
217 }
218}
219
220// SNI (RFC 6066 §3): ServerNameList of 2-byte length, then entries type(1), name<2>. Take the first host_name.
221void parse_server_name(const uint8_t *body, size_t blen, Tls13ClientHello *out)
222{
223 if (blen < 2)
224 return;
225 size_t ll = (body[0] << 8) | body[1];
226 if (ll + 2 > blen)
227 return;
228 size_t i = 2;
229 size_t end = 2 + ll;
230 if (i + 3 > end)
231 return;
232 uint8_t nt = body[i++];
233 size_t nl = (body[i] << 8) | body[i + 1];
234 i += 2;
235 if (nt == 0 && i + nl <= end)
236 {
237 out->sni = body + i;
238 out->sni_len = nl;
239 }
240}
241
242void parse_extension(uint16_t type, const uint8_t *body, size_t blen, Tls13ClientHello *out, bool dtls)
243{
244 switch (type)
245 {
246 case TlsExt::TLS_EXT_SUPPORTED_VERSIONS: {
247 // 1-byte list length, then 2-byte versions. DTLS 1.3 advertises 0xFEFC, TLS 1.3 advertises 0x0304.
248 if (blen < 1)
249 return;
250 size_t ll = body[0];
251 out->offers_tls13 = list16_contains(body + 1, blen - 1, ll, dtls ? TLS_VERSION_DTLS_1_3 : TLS_VERSION_1_3);
252 break;
253 }
254 case TlsExt::TLS_EXT_SUPPORTED_GROUPS: {
255 if (blen < 2)
256 return;
257 size_t ll = (body[0] << 8) | body[1];
258 out->offers_x25519 = list16_contains(body + 2, blen - 2, ll, TLS_GROUP_X25519);
259#if DETWS_ENABLE_PQC_KEX
260 out->offers_x25519mlkem768 = list16_contains(body + 2, blen - 2, ll, TLS_GROUP_X25519MLKEM768);
261#endif
262 break;
263 }
264 case TlsExt::TLS_EXT_SIGNATURE_ALGORITHMS: {
265 if (blen < 2)
266 return;
267 size_t ll = (body[0] << 8) | body[1];
268 out->offers_ed25519 = list16_contains(body + 2, blen - 2, ll, TLS_SIG_ED25519);
269 break;
270 }
271 case TlsExt::TLS_EXT_KEY_SHARE:
272 parse_key_share(body, blen, out);
273 break;
274 case TlsExt::TLS_EXT_ALPN:
275 parse_alpn(body, blen, out);
276 break;
277 case TLS_EXT_QUIC_TRANSPORT_PARAMS:
278 out->quic_tp = body;
279 out->quic_tp_len = blen;
280 break;
281 case TlsExt::TLS_EXT_COOKIE: {
282 // Cookie { opaque cookie<1..2^16-1> } (RFC 8446 §4.2.2): 2-byte length then the cookie bytes.
283 if (blen < 2)
284 return;
285 size_t cl = (size_t)((body[0] << 8) | body[1]);
286 if (cl + 2 > blen)
287 return;
288 out->cookie = body + 2;
289 out->cookie_len = cl;
290 break;
291 }
292 case TlsExt::TLS_EXT_CONNECTION_ID: {
293 // ConnectionId { opaque cid<0..2^8-1> } (RFC 9146 §3): a 1-byte length then the CID the client
294 // wants the server to place in records it sends to the client (an empty CID is legal).
295 if (blen < 1)
296 return;
297 size_t cl = body[0];
298 if (1 + cl > blen)
299 return;
300 out->has_conn_id = true;
301 out->conn_id = body + 1;
302 out->conn_id_len = cl;
303 break;
304 }
305 case TlsExt::TLS_EXT_SERVER_NAME:
306 parse_server_name(body, blen, out);
307 break;
308 default:
309 break;
310 }
311}
312} // namespace
313
314bool tls13_parse_client_hello(const uint8_t *msg, size_t len, Tls13ClientHello *out, bool dtls)
315{
316 memset(out, 0, sizeof(*out));
317
318 Reader r = {msg, len, 0};
319 uint8_t type = 0;
320 uint32_t body_len = 0;
321 if (!r_u8(&r, &type) || type != TlsHs::TLS_HS_CLIENT_HELLO || !r_u24(&r, &body_len))
322 return false;
323 // The handshake body must fit; trailing bytes past it are not part of this message.
324 if (r.pos + body_len > len)
325 return false;
326 r.len = r.pos + body_len;
327
328 uint16_t legacy_version = 0;
329 const uint8_t *random = nullptr;
330 if (!r_u16(&r, &legacy_version) || !r_take(&r, 32, &random))
331 return false;
332
333 uint8_t sid_len = 0;
334 if (!r_u8(&r, &sid_len) || sid_len > 32)
335 return false;
336 if (!r_take(&r, sid_len, &out->session_id))
337 return false;
338 out->session_id_len = sid_len;
339
340 // DTLS ClientHello carries a legacy_cookie between session_id and cipher_suites (RFC 9147 §5.3);
341 // it is zero-length in DTLS 1.3 but the field is always present. TLS/QUIC ClientHellos omit it.
342 if (dtls)
343 {
344 uint8_t cookie_len = 0;
345 const uint8_t *cookie = nullptr;
346 if (!r_u8(&r, &cookie_len) || !r_take(&r, cookie_len, &cookie))
347 return false;
348 }
349
350 uint16_t cs_len = 0;
351 const uint8_t *cs = nullptr;
352 if (!r_u16(&r, &cs_len) || (cs_len % 2) != 0 || !r_take(&r, cs_len, &cs))
353 return false;
354
355 uint8_t comp_len = 0;
356 const uint8_t *comp = nullptr;
357 if (!r_u8(&r, &comp_len) || !r_take(&r, comp_len, &comp))
358 return false;
359
360 // Extensions (a ClientHello for TLS 1.3 always has them).
361 uint16_t ext_total = 0;
362 if (!r_u16(&r, &ext_total))
363 return false;
364 size_t ext_end = r.pos + ext_total;
365 if (ext_end > r.len)
366 return false;
367 while (r.pos < ext_end)
368 {
369 uint16_t etype = 0;
370 uint16_t elen = 0;
371 const uint8_t *ebody = nullptr;
372 if (!r_u16(&r, &etype) || !r_u16(&r, &elen) || !r_take(&r, elen, &ebody))
373 return false;
374 parse_extension(etype, ebody, elen, out, dtls);
375 }
376 return true;
377}
378
379// ---------------------------------------------------------------------------
380// Builders
381// ---------------------------------------------------------------------------
382size_t tls13_build_server_hello(uint8_t *out, size_t cap, const uint8_t random[32], const uint8_t *session_id,
383 uint8_t session_id_len, const uint8_t *share, size_t share_len, uint16_t group,
384 bool dtls, const uint8_t *conn_id, size_t conn_id_len)
385{
386 Writer w = {out, cap, 0, true};
387 w_u8(&w, TlsHs::TLS_HS_SERVER_HELLO);
388 size_t hs_len = w_mark(&w, 3);
389
390 // legacy_version is 0x0303 (TLS 1.2) for TLS/QUIC, 0xFEFD (DTLS 1.2) for DTLS (RFC 9147 §5.3).
391 w_u16(&w, dtls ? TLS_LEGACY_VERSION_DTLS : (uint16_t)0x0303);
392 w_bytes(&w, random, 32);
393 w_u8(&w, session_id_len);
394 w_bytes(&w, session_id, session_id_len);
395 w_u16(&w, TLS_CIPHER_AES_128_GCM_SHA256);
396 w_u8(&w, 0x00); // legacy_compression_method
397
398 size_t ext_len = w_mark(&w, 2);
399 // key_share -> server KeyShareEntry { group, key_exchange }. (Ordered key_share then
400 // supported_versions to match the RFC 8448 sec 3 ServerHello; extension order is not significant.)
401 w_u16(&w, TlsExt::TLS_EXT_KEY_SHARE);
402 w_u16(&w, (uint16_t)(4 + share_len));
403 w_u16(&w, group);
404 w_u16(&w, (uint16_t)share_len);
405 w_bytes(&w, share, share_len);
406 // supported_versions -> selected version (DTLS 1.3 = 0xFEFC, TLS 1.3 = 0x0304).
407 w_u16(&w, TlsExt::TLS_EXT_SUPPORTED_VERSIONS);
408 w_u16(&w, 2);
409 w_u16(&w, dtls ? TLS_VERSION_DTLS_1_3 : TLS_VERSION_1_3);
410 // connection_id (RFC 9146 / RFC 9147 §9) -> the server's CID the client must place in the records
411 // it sends. Sent in the ServerHello (epoch 0) so the client uses it from its first protected record.
412 if (conn_id)
413 {
414 w_u16(&w, TlsExt::TLS_EXT_CONNECTION_ID);
415 w_u16(&w, (uint16_t)(1 + conn_id_len));
416 w_u8(&w, (uint8_t)conn_id_len);
417 w_bytes(&w, conn_id, conn_id_len);
418 }
419 w_patch16(&w, ext_len);
420
421 w_patch24(&w, hs_len);
422 return w.ok ? w.pos : 0;
423}
424
425// SHA-256("HelloRetryRequest") - RFC 8446 §4.1.3. A ServerHello with this random is a HelloRetryRequest.
426const uint8_t tls13_hrr_random[32] = {0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C,
427 0x02, 0x1E, 0x65, 0xB8, 0x91, 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB,
428 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C};
429
430size_t tls13_build_hello_retry_request(uint8_t *out, size_t cap, const uint8_t *session_id, uint8_t session_id_len,
431 uint16_t selected_group, const uint8_t *cookie, size_t cookie_len, bool dtls)
432{
433 if (cookie_len > 0xFFFD)
434 return 0; // cookie extension body (cookie_len + 2) must fit a uint16
435 Writer w = {out, cap, 0, true};
436 w_u8(&w, TlsHs::TLS_HS_SERVER_HELLO);
437 size_t hs_len = w_mark(&w, 3);
438
439 // legacy_version and the supported_versions selection use the DTLS codepoints for DTLS 1.3
440 // (0xFEFD / 0xFEFC, RFC 9147 §5.3), the TLS ones (0x0303 / 0x0304) otherwise - a HelloRetryRequest
441 // is a ServerHello, so it carries the same version fields.
442 w_u16(&w, dtls ? TLS_LEGACY_VERSION_DTLS : (uint16_t)0x0303); // legacy_version
443 w_bytes(&w, tls13_hrr_random, 32);
444 w_u8(&w, session_id_len);
445 w_bytes(&w, session_id, session_id_len);
446 w_u16(&w, TLS_CIPHER_AES_128_GCM_SHA256);
447 w_u8(&w, 0x00); // legacy_compression_method
448
449 size_t ext_len = w_mark(&w, 2);
450 // supported_versions -> the selected version.
451 w_u16(&w, TlsExt::TLS_EXT_SUPPORTED_VERSIONS);
452 w_u16(&w, 2);
453 w_u16(&w, dtls ? TLS_VERSION_DTLS_1_3 : TLS_VERSION_1_3);
454 // key_share (HelloRetryRequest form) -> just the selected group (RFC 8446 §4.2.8).
455 w_u16(&w, TlsExt::TLS_EXT_KEY_SHARE);
456 w_u16(&w, 2);
457 w_u16(&w, selected_group);
458 // cookie -> the return-routability token the client must echo (RFC 8446 §4.2.2).
459 if (cookie_len)
460 {
461 w_u16(&w, TlsExt::TLS_EXT_COOKIE);
462 w_u16(&w, (uint16_t)(cookie_len + 2));
463 w_u16(&w, (uint16_t)cookie_len);
464 w_bytes(&w, cookie, cookie_len);
465 }
466 w_patch16(&w, ext_len);
467
468 w_patch24(&w, hs_len);
469 return w.ok ? w.pos : 0;
470}
471
472size_t tls13_build_encrypted_extensions_empty(uint8_t *out, size_t cap)
473{
474 Writer w = {out, cap, 0, true};
475 w_u8(&w, TlsHs::TLS_HS_ENCRYPTED_EXTENSIONS);
476 size_t hs_len = w_mark(&w, 3);
477 w_u16(&w, 0); // extensions: empty (the DTLS profile carries no ALPN / transport params)
478 w_patch24(&w, hs_len);
479 return w.ok ? w.pos : 0;
480}
481
482size_t tls13_build_message_hash(uint8_t *out, size_t cap, const uint8_t ch1_hash[32])
483{
484 Writer w = {out, cap, 0, true};
485 w_u8(&w, 254); // message_hash synthetic handshake type (RFC 8446 §4.4.1)
486 w_u24(&w, 32); // Hash.length for SHA-256
487 w_bytes(&w, ch1_hash, 32);
488 return w.ok ? w.pos : 0;
489}
490
491size_t tls13_build_encrypted_extensions(uint8_t *out, size_t cap, const uint8_t *quic_tp, size_t quic_tp_len)
492{
493 Writer w = {out, cap, 0, true};
494 w_u8(&w, TlsHs::TLS_HS_ENCRYPTED_EXTENSIONS);
495 size_t hs_len = w_mark(&w, 3);
496
497 size_t ext_len = w_mark(&w, 2);
498 // ALPN -> ProtocolNameList [ "h3" ].
499 w_u16(&w, TlsExt::TLS_EXT_ALPN);
500 w_u16(&w, 5); // ext body length: 2 (list len) + 1 + 2
501 w_u16(&w, 3); // ProtocolNameList length
502 w_u8(&w, 2); // name length
503 w_bytes(&w, (const uint8_t *)"h3", 2);
504 // quic_transport_parameters.
505 w_u16(&w, TLS_EXT_QUIC_TRANSPORT_PARAMS);
506 w_u16(&w, (uint16_t)quic_tp_len);
507 w_bytes(&w, quic_tp, quic_tp_len);
508 w_patch16(&w, ext_len);
509
510 w_patch24(&w, hs_len);
511 return w.ok ? w.pos : 0;
512}
513
514size_t tls13_build_certificate(uint8_t *out, size_t cap, const uint8_t *cert_der, size_t cert_len)
515{
516 Writer w = {out, cap, 0, true};
517 w_u8(&w, TlsHs::TLS_HS_CERTIFICATE);
518 size_t hs_len = w_mark(&w, 3);
519
520 w_u8(&w, 0); // certificate_request_context: empty
521 size_t list_len = w_mark(&w, 3);
522 w_u24(&w, (uint32_t)cert_len); // CertificateEntry cert_data length
523 w_bytes(&w, cert_der, cert_len);
524 w_u16(&w, 0); // entry extensions: empty
525 w_patch24(&w, list_len);
526
527 w_patch24(&w, hs_len);
528 return w.ok ? w.pos : 0;
529}
530
531size_t tls13_cert_verify_content(uint8_t *out, size_t cap, const uint8_t transcript_hash[32], bool is_server)
532{
533 // RFC 8446 sec 4.4.3: 64 spaces || context string || 0x00 || transcript hash.
534 static const char SRV[] = "TLS 1.3, server CertificateVerify";
535 static const char CLI[] = "TLS 1.3, client CertificateVerify";
536 const char *ctx = is_server ? SRV : CLI;
537 size_t ctx_len = is_server ? sizeof(SRV) - 1 : sizeof(CLI) - 1;
538 size_t total = 64 + ctx_len + 1 + 32;
539 if (total > cap)
540 return 0;
541 memset(out, 0x20, 64);
542 memcpy(out + 64, ctx, ctx_len);
543 out[64 + ctx_len] = 0x00;
544 memcpy(out + 64 + ctx_len + 1, transcript_hash, 32);
545 return total;
546}
547
548size_t tls13_build_cert_verify(uint8_t *out, size_t cap, const uint8_t transcript_hash[32], const uint8_t seed[32])
549{
550 uint8_t content[64 + 33 + 1 + 32];
551 size_t clen = tls13_cert_verify_content(content, sizeof(content), transcript_hash, true);
552 // GCOVR_EXCL_START content[] is sized to the exact maximum (64 + ctx 33 + 1 + hash 32), so
553 // tls13_cert_verify_content always succeeds here; the guard cannot fire.
554 if (!clen)
555 return 0;
556 // GCOVR_EXCL_STOP
557 uint8_t sig[SSH_ED25519_SIG_LEN];
558 ssh_ed25519_sign(sig, content, clen, seed);
559
560 Writer w = {out, cap, 0, true};
561 w_u8(&w, TlsHs::TLS_HS_CERTIFICATE_VERIFY);
562 size_t hs_len = w_mark(&w, 3);
563 w_u16(&w, TLS_SIG_ED25519);
564 w_u16(&w, SSH_ED25519_SIG_LEN);
565 w_bytes(&w, sig, SSH_ED25519_SIG_LEN);
566 w_patch24(&w, hs_len);
567 return w.ok ? w.pos : 0;
568}
569
570size_t tls13_build_finished(uint8_t *out, size_t cap, const uint8_t verify_data[32])
571{
572 Writer w = {out, cap, 0, true};
573 w_u8(&w, TlsHs::TLS_HS_FINISHED);
574 size_t hs_len = w_mark(&w, 3);
575 w_bytes(&w, verify_data, 32);
576 w_patch24(&w, hs_len);
577 return w.ok ? w.pos : 0;
578}
579
580#endif // DETWS_ENABLE_HTTP3 || DETWS_ENABLE_DTLS
ML-KEM-768 encapsulation (FIPS 203), responder side only.
void ssh_ed25519_sign(uint8_t sig[64], const uint8_t *msg, size_t mlen, const uint8_t seed[32])
Ed25519 signatures (RFC 8032) for ssh-ed25519 host keys + client auth.
#define SSH_ED25519_SIG_LEN
Ed25519 signature length (R || S).
Definition ssh_ed25519.h:31
TLS 1.3 handshake messages for the QUIC handshake (RFC 8446 sec 4).