ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 pc_tls13_msg.cpp
6 * @brief TLS 1.3 handshake messages for the QUIC handshake (see pc_tls13_msg.h).
7 */
8
10
11#if (PC_ENABLE_HTTP3 || PC_ENABLE_DTLS)
12
14#if PC_ENABLE_PQC_KEX
15#include "crypto/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_CLIENT_CERTIFICATE_TYPE = 0x0013; ///< RFC 7250 (IANA 19)
27 static constexpr uint16_t TLS_EXT_SERVER_CERTIFICATE_TYPE = 0x0014; ///< RFC 7250 (IANA 20)
28 static constexpr uint16_t TLS_EXT_SUPPORTED_VERSIONS = 0x002b;
29 static constexpr uint16_t TLS_EXT_COOKIE = 0x002c;
30 static constexpr uint16_t TLS_EXT_KEY_SHARE = 0x0033;
31 static constexpr uint16_t TLS_EXT_CONNECTION_ID = 0x0036; ///< RFC 9146 / RFC 9147 §9
32};
33
34// ---------------------------------------------------------------------------
35// A minimal bounds-checked byte writer (back-patches length prefixes).
36// ---------------------------------------------------------------------------
37namespace
38{
39struct Writer
40{
41 uint8_t *buf;
42 size_t cap;
43 size_t pos;
44 bool ok;
45};
46
47void w_u8(Writer *w, uint8_t v)
48{
49 // pos <= cap is an invariant (every write advances pos by exactly the checked amount), so the
50 // comparison is written to avoid a size_t addition that could wrap (cpp:S3519).
51 if (w->pos >= w->cap)
52 {
53 w->ok = false;
54 return;
55 }
56 w->buf[w->pos++] = v;
57}
58void w_u16(Writer *w, uint16_t v)
59{
60 w_u8(w, (uint8_t)(v >> 8));
61 w_u8(w, (uint8_t)v);
62}
63void w_u24(Writer *w, uint32_t v)
64{
65 w_u8(w, (uint8_t)(v >> 16));
66 w_u8(w, (uint8_t)(v >> 8));
67 w_u8(w, (uint8_t)v);
68}
69void w_bytes(Writer *w, const uint8_t *b, size_t n)
70{
71 if (n == 0)
72 {
73 return;
74 }
75 // Direct pre-copy bound: pos must be within cap AND n must fit in the remaining cap - pos. Written as
76 // two explicit guards (no intermediate) so the copy runs only when pos + n <= cap - no underflow of
77 // cap - pos (guarded by pos <= cap) and no pos + n wrap (never formed).
78 if (w->pos > w->cap || n > w->cap - w->pos) // GCOVR_EXCL_LINE w_u8 refuses to advance past cap, so pos <= cap
79 { // is an invariant and the first arm can never be taken
80 w->ok = false;
81 return;
82 }
83 memcpy(w->buf + w->pos, b, n);
84 w->pos += n;
85}
86// Reserve a 2- or 3-byte length placeholder; returns its position for w_patch16/24.
87size_t w_mark(Writer *w, size_t nbytes)
88{
89 size_t at = w->pos;
90 for (size_t i = 0; i < nbytes; i++)
91 {
92 w_u8(w, 0);
93 }
94 return at;
95}
96void w_patch16(Writer *w, size_t at)
97{
98 if (!w->ok)
99 {
100 return;
101 }
102 uint16_t len = (uint16_t)(w->pos - at - 2);
103 w->buf[at] = (uint8_t)(len >> 8);
104 w->buf[at + 1] = (uint8_t)len;
105}
106void w_patch24(Writer *w, size_t at)
107{
108 if (!w->ok)
109 {
110 return;
111 }
112 uint32_t len = (uint32_t)(w->pos - at - 3);
113 w->buf[at] = (uint8_t)(len >> 16);
114 w->buf[at + 1] = (uint8_t)(len >> 8);
115 w->buf[at + 2] = (uint8_t)len;
116}
117
118// A bounds-checked reader.
119struct Reader
120{
121 const uint8_t *buf;
122 size_t len;
123 size_t pos;
124};
125bool r_u8(Reader *r, uint8_t *v)
126{
127 if (r->pos + 1 > r->len)
128 {
129 return false;
130 }
131 *v = r->buf[r->pos++];
132 return true;
133}
134bool r_u16(Reader *r, uint16_t *v)
135{
136 if (r->pos + 2 > r->len)
137 {
138 return false;
139 }
140 *v = (uint16_t)((r->buf[r->pos] << 8) | r->buf[r->pos + 1]);
141 r->pos += 2;
142 return true;
143}
144bool r_u24(Reader *r, uint32_t *v)
145{
146 if (r->pos + 3 > r->len)
147 {
148 return false;
149 }
150 *v = (uint32_t)((r->buf[r->pos] << 16) | (r->buf[r->pos + 1] << 8) | r->buf[r->pos + 2]);
151 r->pos += 3;
152 return true;
153}
154// Take a view of the next n bytes.
155bool r_take(Reader *r, size_t n, const uint8_t **out)
156{
157 if (r->pos + n > r->len)
158 {
159 return false;
160 }
161 *out = r->buf + r->pos;
162 r->pos += n;
163 return true;
164}
165} // namespace
166
167// ---------------------------------------------------------------------------
168// ClientHello parsing
169// ---------------------------------------------------------------------------
170namespace
171{
172// Scan a list of 2-byte values for a target (used for versions/groups/sig algs).
173bool list16_contains(const uint8_t *body, size_t body_len, size_t list_len, uint16_t target)
174{
175 if (list_len > body_len || (list_len % 2) != 0)
176 {
177 return false;
178 }
179 for (size_t i = 0; i + 1 < list_len; i += 2)
180 {
181 if (((body[i] << 8) | body[i + 1]) == target)
182 {
183 return true;
184 }
185 }
186 return false;
187}
188
189// KEY_SHARE (RFC 8446 §4.2.8): 2-byte client_shares length, then KeyShareEntry { group(2), key_exchange<2> }.
190void parse_key_share(const uint8_t *body, size_t blen, Tls13ClientHello *out)
191{
192 if (blen < 2)
193 {
194 return;
195 }
196 size_t ll = (body[0] << 8) | body[1];
197 if (ll + 2 > blen)
198 {
199 return;
200 }
201 size_t i = 2;
202 size_t end = 2 + ll;
203 while (i + 4 <= end)
204 {
205 uint16_t group = (uint16_t)((body[i] << 8) | body[i + 1]);
206 uint16_t klen = (uint16_t)((body[i + 2] << 8) | body[i + 3]);
207 i += 4;
208 if (i + klen > end)
209 {
210 return;
211 }
212 if (group == TLS_GROUP_X25519 && klen == 32)
213 {
214 memcpy(out->client_x25519, body + i, 32);
215 out->has_key_share = true;
216 }
217#if PC_ENABLE_PQC_KEX
218 else if (group == TLS_GROUP_X25519MLKEM768 && klen == MLKEM768_EK_BYTES + 32)
219 {
220 out->client_mlkem_ek = body + i; // ML-KEM-768 ek (first)
221 memcpy(out->client_x25519, body + i + MLKEM768_EK_BYTES, 32); // X25519 (second)
222 out->has_hybrid_share = true;
223 }
224#endif
225 i += klen;
226 }
227}
228
229// ALPN (RFC 7301): 2-byte list length, then entries of 1-byte name length + name. Flags an "h3" offer.
230void parse_alpn(const uint8_t *body, size_t blen, Tls13ClientHello *out)
231{
232 if (blen < 2)
233 {
234 return;
235 }
236 size_t ll = (body[0] << 8) | body[1];
237 if (ll + 2 > blen)
238 {
239 return;
240 }
241 size_t i = 2;
242 size_t end = 2 + ll;
243 while (i + 1 <= end)
244 {
245 size_t nl = body[i++];
246 if (i + nl > end)
247 {
248 return;
249 }
250 if (nl == 2 && body[i] == 'h' && body[i + 1] == '3')
251 {
252 out->offers_h3_alpn = true;
253 }
254 i += nl;
255 }
256}
257
258// SNI (RFC 6066 §3): ServerNameList of 2-byte length, then entries type(1), name<2>. Take the first host_name.
259void parse_server_name(const uint8_t *body, size_t blen, Tls13ClientHello *out)
260{
261 if (blen < 2)
262 {
263 return;
264 }
265 size_t ll = (body[0] << 8) | body[1];
266 if (ll + 2 > blen)
267 {
268 return;
269 }
270 size_t i = 2;
271 size_t end = 2 + ll;
272 if (i + 3 > end)
273 {
274 return;
275 }
276 uint8_t nt = body[i++];
277 size_t nl = (body[i] << 8) | body[i + 1];
278 i += 2;
279 if (nt == 0 && i + nl <= end)
280 {
281 out->sni = body + i;
282 out->sni_len = nl;
283 }
284}
285
286void parse_extension(uint16_t type, const uint8_t *body, size_t blen, Tls13ClientHello *out, bool dtls)
287{
288 switch (type)
289 {
290 case TlsExt::TLS_EXT_SUPPORTED_VERSIONS: {
291 // 1-byte list length, then 2-byte versions. DTLS 1.3 advertises 0xFEFC, TLS 1.3 advertises 0x0304.
292 if (blen < 1)
293 {
294 return;
295 }
296 size_t ll = body[0];
297 out->offers_tls13 = list16_contains(body + 1, blen - 1, ll, dtls ? PC_TLS_VERSION_DTLS_1_3 : TLS_VERSION_1_3);
298 break;
299 }
300 case TlsExt::TLS_EXT_SUPPORTED_GROUPS: {
301 if (blen < 2)
302 {
303 return;
304 }
305 size_t ll = (body[0] << 8) | body[1];
306 out->offers_x25519 = list16_contains(body + 2, blen - 2, ll, TLS_GROUP_X25519);
307#if PC_ENABLE_PQC_KEX
308 out->offers_x25519mlkem768 = list16_contains(body + 2, blen - 2, ll, TLS_GROUP_X25519MLKEM768);
309#endif
310 break;
311 }
312 case TlsExt::TLS_EXT_SIGNATURE_ALGORITHMS: {
313 if (blen < 2)
314 {
315 return;
316 }
317 size_t ll = (body[0] << 8) | body[1];
318 out->offers_ed25519 = list16_contains(body + 2, blen - 2, ll, TLS_SIG_ED25519);
319 break;
320 }
321 case TlsExt::TLS_EXT_KEY_SHARE:
322 parse_key_share(body, blen, out);
323 break;
324 case TlsExt::TLS_EXT_ALPN:
325 parse_alpn(body, blen, out);
326 break;
327#if PC_ENABLE_TLS_RPK
328 case TlsExt::TLS_EXT_SERVER_CERTIFICATE_TYPE: {
329 // server_certificate_type (RFC 7250 sec 4.2): a 1-byte list length then 1-byte CertificateType
330 // values. RawPublicKey(2) in the list means the client accepts a bare SubjectPublicKeyInfo from us.
331 if (blen < 1)
332 {
333 return;
334 }
335 size_t ll = body[0];
336 if (1 + ll > blen)
337 {
338 return;
339 }
340 for (size_t i = 0; i < ll; i++)
341 {
342 if (body[1 + i] == TLS_CERT_TYPE_RAW_PUBLIC_KEY)
343 {
344 out->offers_rpk_server_cert = true;
345 }
346 }
347 break;
348 }
349#endif
350 case TLS_EXT_QUIC_TRANSPORT_PARAMS:
351 out->pc_quic_tp = body;
352 out->pc_quic_tp_len = blen;
353 break;
354 case TlsExt::TLS_EXT_COOKIE: {
355 // Cookie { opaque cookie<1..2^16-1> } (RFC 8446 §4.2.2): 2-byte length then the cookie bytes.
356 if (blen < 2)
357 {
358 return;
359 }
360 size_t cl = (size_t)((body[0] << 8) | body[1]);
361 if (cl + 2 > blen)
362 {
363 return;
364 }
365 out->cookie = body + 2;
366 out->cookie_len = cl;
367 break;
368 }
369 case TlsExt::TLS_EXT_CONNECTION_ID: {
370 // ConnectionId { opaque cid<0..2^8-1> } (RFC 9146 §3): a 1-byte length then the CID the client
371 // wants the server to place in records it sends to the client (an empty CID is legal).
372 if (blen < 1)
373 {
374 return;
375 }
376 size_t cl = body[0];
377 if (1 + cl > blen)
378 {
379 return;
380 }
381 out->has_conn_id = true;
382 out->conn_id = body + 1;
383 out->conn_id_len = cl;
384 break;
385 }
386 case TlsExt::TLS_EXT_SERVER_NAME:
387 parse_server_name(body, blen, out);
388 break;
389 default:
390 break;
391 }
392}
393} // namespace
394
395bool pc_tls13_parse_client_hello(const uint8_t *msg, size_t len, Tls13ClientHello *out, bool dtls)
396{
397 memset(out, 0, sizeof(*out));
398
399 Reader r = {msg, len, 0};
400 uint8_t type = 0;
401 uint32_t body_len = 0;
402 if (!r_u8(&r, &type) || type != TlsHs::TLS_HS_CLIENT_HELLO || !r_u24(&r, &body_len))
403 {
404 return false;
405 }
406 // The handshake body must fit; trailing bytes past it are not part of this message.
407 if (r.pos + body_len > len)
408 {
409 return false;
410 }
411 r.len = r.pos + body_len;
412
413 uint16_t legacy_version = 0;
414 const uint8_t *random = nullptr;
415 if (!r_u16(&r, &legacy_version) || !r_take(&r, 32, &random))
416 {
417 return false;
418 }
419
420 uint8_t sid_len = 0;
421 if (!r_u8(&r, &sid_len) || sid_len > 32)
422 {
423 return false;
424 }
425 if (!r_take(&r, sid_len, &out->session_id))
426 {
427 return false;
428 }
429 out->session_id_len = sid_len;
430
431 // DTLS ClientHello carries a legacy_cookie between session_id and cipher_suites (RFC 9147 §5.3);
432 // it is zero-length in DTLS 1.3 but the field is always present. TLS/QUIC ClientHellos omit it.
433 if (dtls)
434 {
435 uint8_t cookie_len = 0;
436 const uint8_t *cookie = nullptr;
437 if (!r_u8(&r, &cookie_len) || !r_take(&r, cookie_len, &cookie))
438 {
439 return false;
440 }
441 }
442
443 uint16_t cs_len = 0;
444 const uint8_t *cs = nullptr;
445 if (!r_u16(&r, &cs_len) || (cs_len % 2) != 0 || !r_take(&r, cs_len, &cs))
446 {
447 return false;
448 }
449
450 uint8_t comp_len = 0;
451 const uint8_t *comp = nullptr;
452 if (!r_u8(&r, &comp_len) || !r_take(&r, comp_len, &comp))
453 {
454 return false;
455 }
456
457 // Extensions (a ClientHello for TLS 1.3 always has them).
458 uint16_t ext_total = 0;
459 if (!r_u16(&r, &ext_total))
460 {
461 return false;
462 }
463 size_t ext_end = r.pos + ext_total;
464 if (ext_end > r.len)
465 {
466 return false;
467 }
468 while (r.pos < ext_end)
469 {
470 uint16_t etype = 0;
471 uint16_t elen = 0;
472 const uint8_t *ebody = nullptr;
473 if (!r_u16(&r, &etype) || !r_u16(&r, &elen) || !r_take(&r, elen, &ebody))
474 {
475 return false;
476 }
477 parse_extension(etype, ebody, elen, out, dtls);
478 }
479 return true;
480}
481
482// ---------------------------------------------------------------------------
483// Builders
484// ---------------------------------------------------------------------------
485size_t pc_tls13_build_server_hello(uint8_t *out, size_t cap, const uint8_t random[32], const uint8_t *session_id,
486 uint8_t session_id_len, const uint8_t *share, size_t share_len, uint16_t group,
487 bool dtls, const uint8_t *conn_id, size_t conn_id_len)
488{
489 Writer w = {out, cap, 0, true};
490 w_u8(&w, TlsHs::TLS_HS_SERVER_HELLO);
491 size_t hs_len = w_mark(&w, 3);
492
493 // legacy_version is 0x0303 (TLS 1.2) for TLS/QUIC, 0xFEFD (DTLS 1.2) for DTLS (RFC 9147 §5.3).
494 w_u16(&w, dtls ? PC_TLS_LEGACY_VERSION_DTLS : (uint16_t)0x0303);
495 w_bytes(&w, random, 32);
496 w_u8(&w, session_id_len);
497 w_bytes(&w, session_id, session_id_len);
498 w_u16(&w, TLS_CIPHER_AES_128_GCM_SHA256);
499 w_u8(&w, 0x00); // legacy_compression_method
500
501 size_t ext_len = w_mark(&w, 2);
502 // key_share -> server KeyShareEntry { group, key_exchange }. (Ordered key_share then
503 // supported_versions to match the RFC 8448 sec 3 ServerHello; extension order is not significant.)
504 w_u16(&w, TlsExt::TLS_EXT_KEY_SHARE);
505 w_u16(&w, (uint16_t)(4 + share_len));
506 w_u16(&w, group);
507 w_u16(&w, (uint16_t)share_len);
508 w_bytes(&w, share, share_len);
509 // supported_versions -> selected version (DTLS 1.3 = 0xFEFC, TLS 1.3 = 0x0304).
510 w_u16(&w, TlsExt::TLS_EXT_SUPPORTED_VERSIONS);
511 w_u16(&w, 2);
512 w_u16(&w, dtls ? PC_TLS_VERSION_DTLS_1_3 : TLS_VERSION_1_3);
513 // connection_id (RFC 9146 / RFC 9147 §9) -> the server's CID the client must place in the records
514 // it sends. Sent in the ServerHello (epoch 0) so the client uses it from its first protected record.
515 if (conn_id)
516 {
517 w_u16(&w, TlsExt::TLS_EXT_CONNECTION_ID);
518 w_u16(&w, (uint16_t)(1 + conn_id_len));
519 w_u8(&w, (uint8_t)conn_id_len);
520 w_bytes(&w, conn_id, conn_id_len);
521 }
522 w_patch16(&w, ext_len);
523
524 w_patch24(&w, hs_len);
525 return w.ok ? w.pos : 0;
526}
527
528// SHA-256("HelloRetryRequest") - RFC 8446 §4.1.3. A ServerHello with this random is a HelloRetryRequest.
529const uint8_t pc_tls13_hrr_random[32] = {0xCF, 0x21, 0xAD, 0x74, 0xE5, 0x9A, 0x61, 0x11, 0xBE, 0x1D, 0x8C,
530 0x02, 0x1E, 0x65, 0xB8, 0x91, 0xC2, 0xA2, 0x11, 0x16, 0x7A, 0xBB,
531 0x8C, 0x5E, 0x07, 0x9E, 0x09, 0xE2, 0xC8, 0xA8, 0x33, 0x9C};
532
533size_t pc_tls13_build_hello_retry_request(uint8_t *out, size_t cap, const uint8_t *session_id, uint8_t session_id_len,
534 uint16_t selected_group, const uint8_t *cookie, size_t cookie_len, bool dtls)
535{
536 if (cookie_len > 0xFFFD)
537 {
538 return 0; // cookie extension body (cookie_len + 2) must fit a uint16
539 }
540 Writer w = {out, cap, 0, true};
541 w_u8(&w, TlsHs::TLS_HS_SERVER_HELLO);
542 size_t hs_len = w_mark(&w, 3);
543
544 // legacy_version and the supported_versions selection use the DTLS codepoints for DTLS 1.3
545 // (0xFEFD / 0xFEFC, RFC 9147 §5.3), the TLS ones (0x0303 / 0x0304) otherwise - a HelloRetryRequest
546 // is a ServerHello, so it carries the same version fields.
547 w_u16(&w, dtls ? PC_TLS_LEGACY_VERSION_DTLS : (uint16_t)0x0303); // legacy_version
548 w_bytes(&w, pc_tls13_hrr_random, 32);
549 w_u8(&w, session_id_len);
550 w_bytes(&w, session_id, session_id_len);
551 w_u16(&w, TLS_CIPHER_AES_128_GCM_SHA256);
552 w_u8(&w, 0x00); // legacy_compression_method
553
554 size_t ext_len = w_mark(&w, 2);
555 // supported_versions -> the selected version.
556 w_u16(&w, TlsExt::TLS_EXT_SUPPORTED_VERSIONS);
557 w_u16(&w, 2);
558 w_u16(&w, dtls ? PC_TLS_VERSION_DTLS_1_3 : TLS_VERSION_1_3);
559 // key_share (HelloRetryRequest form) -> just the selected group (RFC 8446 §4.2.8).
560 w_u16(&w, TlsExt::TLS_EXT_KEY_SHARE);
561 w_u16(&w, 2);
562 w_u16(&w, selected_group);
563 // cookie -> the return-routability token the client must echo (RFC 8446 §4.2.2).
564 if (cookie_len)
565 {
566 w_u16(&w, TlsExt::TLS_EXT_COOKIE);
567 w_u16(&w, (uint16_t)(cookie_len + 2));
568 w_u16(&w, (uint16_t)cookie_len);
569 w_bytes(&w, cookie, cookie_len);
570 }
571 w_patch16(&w, ext_len);
572
573 w_patch24(&w, hs_len);
574 return w.ok ? w.pos : 0;
575}
576
577#if PC_ENABLE_TLS_RPK
578// server_certificate_type response (RFC 7250 sec 4.2): a single CertificateType value, RawPublicKey.
579static void w_server_cert_type_rpk(Writer *w)
580{
581 w_u16(w, TlsExt::TLS_EXT_SERVER_CERTIFICATE_TYPE);
582 w_u16(w, 1); // extension_data length
583 w_u8(w, TLS_CERT_TYPE_RAW_PUBLIC_KEY);
584}
585#endif
586
587size_t pc_tls13_build_encrypted_extensions_empty(uint8_t *out, size_t cap, bool rpk_server_cert)
588{
589 Writer w = {out, cap, 0, true};
590 w_u8(&w, TlsHs::TLS_HS_ENCRYPTED_EXTENSIONS);
591 size_t hs_len = w_mark(&w, 3);
592 // The DTLS profile carries no ALPN / transport params; the only possible extension is the
593 // negotiated server_certificate_type (RFC 7250) when RawPublicKey was selected.
594 size_t ext_len = w_mark(&w, 2);
595#if PC_ENABLE_TLS_RPK
596 if (rpk_server_cert)
597 {
598 w_server_cert_type_rpk(&w);
599 }
600#else
601 (void)rpk_server_cert;
602#endif
603 w_patch16(&w, ext_len);
604 w_patch24(&w, hs_len);
605 return w.ok ? w.pos : 0;
606}
607
608size_t pc_tls13_build_message_hash(uint8_t *out, size_t cap, const uint8_t ch1_hash[32])
609{
610 Writer w = {out, cap, 0, true};
611 w_u8(&w, 254); // message_hash synthetic handshake type (RFC 8446 §4.4.1)
612 w_u24(&w, 32); // Hash.length for SHA-256
613 w_bytes(&w, ch1_hash, 32);
614 return w.ok ? w.pos : 0;
615}
616
617size_t pc_tls13_build_encrypted_extensions(uint8_t *out, size_t cap, const uint8_t *pc_quic_tp, size_t pc_quic_tp_len,
618 bool rpk_server_cert)
619{
620 Writer w = {out, cap, 0, true};
621 w_u8(&w, TlsHs::TLS_HS_ENCRYPTED_EXTENSIONS);
622 size_t hs_len = w_mark(&w, 3);
623
624 size_t ext_len = w_mark(&w, 2);
625 // ALPN -> ProtocolNameList [ "h3" ].
626 w_u16(&w, TlsExt::TLS_EXT_ALPN);
627 w_u16(&w, 5); // ext body length: 2 (list len) + 1 + 2
628 w_u16(&w, 3); // ProtocolNameList length
629 w_u8(&w, 2); // name length
630 w_bytes(&w, (const uint8_t *)"h3", 2);
631 // pc_quic_transport_parameters.
632 w_u16(&w, TLS_EXT_QUIC_TRANSPORT_PARAMS);
633 w_u16(&w, (uint16_t)pc_quic_tp_len);
634 w_bytes(&w, pc_quic_tp, pc_quic_tp_len);
635#if PC_ENABLE_TLS_RPK
636 // negotiated server_certificate_type = RawPublicKey (RFC 7250), when selected.
637 if (rpk_server_cert)
638 {
639 w_server_cert_type_rpk(&w);
640 }
641#else
642 (void)rpk_server_cert;
643#endif
644 w_patch16(&w, ext_len);
645
646 w_patch24(&w, hs_len);
647 return w.ok ? w.pos : 0;
648}
649
650size_t pc_tls13_build_certificate(uint8_t *out, size_t cap, const uint8_t *cert_der, size_t cert_len)
651{
652 Writer w = {out, cap, 0, true};
653 w_u8(&w, TlsHs::TLS_HS_CERTIFICATE);
654 size_t hs_len = w_mark(&w, 3);
655
656 w_u8(&w, 0); // certificate_request_context: empty
657 size_t list_len = w_mark(&w, 3);
658 w_u24(&w, (uint32_t)cert_len); // CertificateEntry cert_data length
659 w_bytes(&w, cert_der, cert_len);
660 w_u16(&w, 0); // entry extensions: empty
661 w_patch24(&w, list_len);
662
663 w_patch24(&w, hs_len);
664 return w.ok ? w.pos : 0;
665}
666
667#if PC_ENABLE_TLS_RPK
668size_t pc_tls13_ed25519_spki(uint8_t *out, size_t cap, const uint8_t pub[32])
669{
670 // DER SubjectPublicKeyInfo for id-Ed25519 (RFC 8410 sec 4): a fixed 12-byte prefix - SEQUENCE
671 // { SEQUENCE { OID 1.3.101.112 } , BIT STRING (33, 0 unused) } - then the 32-byte public key.
672 static const uint8_t PREFIX[12] = {0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00};
673 if (cap < PC_TLS13_ED25519_SPKI_LEN)
674 {
675 return 0;
676 }
677 memcpy(out, PREFIX, sizeof(PREFIX));
678 memcpy(out + sizeof(PREFIX), pub, 32);
679 return PC_TLS13_ED25519_SPKI_LEN;
680}
681
682size_t pc_tls13_build_certificate_rpk(uint8_t *out, size_t cap, const uint8_t ed25519_pub[32])
683{
684 uint8_t spki[PC_TLS13_ED25519_SPKI_LEN];
685 // GCOVR_EXCL_START spki[] is exactly PC_TLS13_ED25519_SPKI_LEN, so the SPKI encode never overflows.
686 if (!pc_tls13_ed25519_spki(spki, sizeof(spki), ed25519_pub))
687 {
688 return 0;
689 }
690 // GCOVR_EXCL_STOP
691 return pc_tls13_build_certificate(out, cap, spki, sizeof(spki));
692}
693#endif
694
695size_t pc_tls13_cert_verify_content(uint8_t *out, size_t cap, const uint8_t transcript_hash[32], bool is_server)
696{
697 // RFC 8446 sec 4.4.3: 64 spaces || context string || 0x00 || transcript hash.
698 static const char SRV[] = "TLS 1.3, server CertificateVerify";
699 static const char CLI[] = "TLS 1.3, client CertificateVerify";
700 const char *ctx = is_server ? SRV : CLI;
701 size_t ctx_len = is_server ? sizeof(SRV) - 1 : sizeof(CLI) - 1;
702 size_t total = 64 + ctx_len + 1 + 32;
703 if (total > cap)
704 {
705 return 0;
706 }
707 memset(out, 0x20, 64);
708 memcpy(out + 64, ctx, ctx_len);
709 out[64 + ctx_len] = 0x00;
710 memcpy(out + 64 + ctx_len + 1, transcript_hash, 32);
711 return total;
712}
713
714size_t pc_tls13_build_cert_verify(uint8_t *out, size_t cap, const uint8_t transcript_hash[32], const uint8_t seed[32])
715{
716 uint8_t content[64 + 33 + 1 + 32];
717 size_t clen = pc_tls13_cert_verify_content(content, sizeof(content), transcript_hash, true);
718 // GCOVR_EXCL_START content[] is sized to the exact maximum (64 + ctx 33 + 1 + hash 32), so
719 // pc_tls13_cert_verify_content always succeeds here; the guard cannot fire.
720 if (!clen)
721 {
722 return 0;
723 }
724 // GCOVR_EXCL_STOP
725 uint8_t sig[PC_ED25519_SIG_LEN];
726 pc_ed25519_sign(sig, content, clen, seed);
727
728 Writer w = {out, cap, 0, true};
729 w_u8(&w, TlsHs::TLS_HS_CERTIFICATE_VERIFY);
730 size_t hs_len = w_mark(&w, 3);
731 w_u16(&w, TLS_SIG_ED25519);
732 w_u16(&w, PC_ED25519_SIG_LEN);
733 w_bytes(&w, sig, PC_ED25519_SIG_LEN);
734 w_patch24(&w, hs_len);
735 return w.ok ? w.pos : 0;
736}
737
738size_t pc_tls13_build_finished(uint8_t *out, size_t cap, const uint8_t verify_data[32])
739{
740 Writer w = {out, cap, 0, true};
741 w_u8(&w, TlsHs::TLS_HS_FINISHED);
742 size_t hs_len = w_mark(&w, 3);
743 w_bytes(&w, verify_data, 32);
744 w_patch24(&w, hs_len);
745 return w.ok ? w.pos : 0;
746}
747
748#endif // PC_ENABLE_HTTP3 || PC_ENABLE_DTLS
void pc_ed25519_sign(uint8_t sig[64], const uint8_t *msg, size_t mlen, const uint8_t seed[32])
Definition ed25519.cpp:594
Ed25519 signatures (RFC 8032) for ssh-ed25519 host keys + client auth.
#define PC_ED25519_SIG_LEN
Ed25519 signature length (R || S).
Definition ed25519.h:31
ML-KEM-768 (FIPS 203): Encaps (responder) + KeyGen and Decaps (initiator).