DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
tls13_kdf.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 tls13_kdf.h
6 * @brief TLS 1.3 key schedule (RFC 8446 sec 7.1) for the QUIC handshake.
7 *
8 * QUIC runs TLS 1.3 as its handshake protocol (RFC 9001), and mbedTLS exposes no QUIC-TLS callback
9 * API, so the handshake is hand-rolled here. This module is the key schedule: the chain of
10 * HKDF-Extract and Derive-Secret steps (RFC 8446 sec 7.1) that turns the (EC)DHE shared secret and
11 * the running handshake transcript hash into the traffic secrets for each encryption level, plus the
12 * per-message Finished MAC (sec 4.4.4). It is cipher-suite TLS_AES_128_GCM_SHA256 only, so the hash
13 * is SHA-256 throughout and every secret is 32 bytes.
14 *
15 * The schedule is transcript-hash-driven: each step takes a Transcript-Hash the caller computed over
16 * the handshake messages so far, so this module has no dependency on the message wire formats and is
17 * host-testable in isolation against the RFC 8448 sec 3 worked trace (which lists every intermediate
18 * secret and the (EC)DHE input directly). The QUIC packet-protection keys ({key, iv, hp}) are then
19 * derived from these traffic secrets by quic_keys_from_secret() (RFC 9001 sec 5.1).
20 *
21 * Pure, zero heap, host-tested against RFC 8448 sec 3.
22 *
23 * @author Douglas Quigg (dstroy0)
24 * @date 2026
25 */
26
27#ifndef DETERMINISTICESPASYNCWEBSERVER_TLS13_KDF_H
28#define DETERMINISTICESPASYNCWEBSERVER_TLS13_KDF_H
29
30#include "ServerConfig.h"
31
32// Shared by the HTTP/3 (QUIC) handshake and the DTLS 1.3 handshake - both run the same TLS 1.3 key
33// schedule (see tls13_msg.h for the matching guard on the message layer).
34#if (DETWS_ENABLE_HTTP3 || DETWS_ENABLE_DTLS)
35
36#include <stddef.h>
37#include <stdint.h>
38
39/** @brief SHA-256 secret length; every TLS 1.3 secret here is 32 bytes. */
40#define TLS13_SECRET_LEN 32
41
42/**
43 * @brief The one thing that differs between the TLS 1.3 and DTLS 1.3 key schedules: the
44 * HKDF-Expand-Label prefix ("tls13 " for TLS/QUIC per RFC 8446 sec 7.1, "dtls13" for DTLS 1.3 per
45 * RFC 9147 sec 5.9). A caller picks the variant once (@ref TLS13_KDF or @ref DTLS13_KDF) and the
46 * key schedule carries it, so no per-call flag is threaded through the derivation steps.
47 */
48struct Tls13Kdf
49{
50 const char *label_prefix;
51};
52
53/** @brief TLS 1.3 / QUIC variant ("tls13 " prefix, RFC 8446). */
54extern const Tls13Kdf TLS13_KDF;
55/** @brief DTLS 1.3 variant ("dtls13" prefix, RFC 9147 sec 5.9). */
56extern const Tls13Kdf DTLS13_KDF;
57
58/**
59 * @brief The running key-schedule state for one handshake (server side).
60 *
61 * Filled in three steps as the handshake progresses: tls13_ks_early() (which also binds the @ref
62 * Tls13Kdf variant) before any (EC)DHE, tls13_ks_handshake() once ClientHello..ServerHello is hashed
63 * and the shared secret is known, and tls13_ks_master() once ClientHello..server Finished is hashed.
64 * Each step also derives that level's client and server traffic secrets, from which the record/packet
65 * keys are made.
66 */
67struct Tls13KeySchedule
68{
69 const Tls13Kdf *kdf; ///< variant (label prefix) bound by tls13_ks_early()
70 uint8_t early_secret[TLS13_SECRET_LEN]; ///< HKDF-Extract(0, PSK|0) - no-PSK: Extract(0, 0^32)
71 uint8_t handshake_secret[TLS13_SECRET_LEN]; ///< HKDF-Extract(Derive(early,"derived"), (EC)DHE)
72 uint8_t master_secret[TLS13_SECRET_LEN]; ///< HKDF-Extract(Derive(handshake,"derived"), 0^32)
73 uint8_t client_hs_traffic[TLS13_SECRET_LEN]; ///< Derive-Secret(handshake, "c hs traffic", CH..SH)
74 uint8_t server_hs_traffic[TLS13_SECRET_LEN]; ///< Derive-Secret(handshake, "s hs traffic", CH..SH)
75 uint8_t client_ap_traffic[TLS13_SECRET_LEN]; ///< Derive-Secret(master, "c ap traffic", CH..SFIN)
76 uint8_t server_ap_traffic[TLS13_SECRET_LEN]; ///< Derive-Secret(master, "s ap traffic", CH..SFIN)
77};
78
79/**
80 * @brief HKDF-Expand-Label under a KDF variant (RFC 8446 sec 7.1 with the @p kdf label prefix).
81 *
82 * The record-key derivations (key/iv/sn) call this so the label prefix follows the negotiated
83 * protocol without the record layer knowing the prefix string.
84 */
85void tls13_kdf_expand_label(const Tls13Kdf *kdf, const uint8_t secret[TLS13_SECRET_LEN], const char *label,
86 uint8_t *out, size_t out_len);
87
88/**
89 * @brief Derive-Secret (RFC 8446 sec 7.1): HKDF-Expand-Label(secret, label, transcript_hash, 32).
90 *
91 * @param kdf KDF variant (label prefix).
92 * @param secret A 32-byte PRK / traffic secret.
93 * @param label Short label without the prefix, e.g. "c hs traffic", "derived".
94 * @param transcript_hash Transcript-Hash of the relevant messages (32 bytes; H("") for "derived").
95 * @param out 32-byte derived secret.
96 */
97void tls13_derive_secret(const Tls13Kdf *kdf, const uint8_t secret[TLS13_SECRET_LEN], const char *label,
98 const uint8_t transcript_hash[TLS13_SECRET_LEN], uint8_t out[TLS13_SECRET_LEN]);
99
100/** @brief Step 1: bind the @p kdf variant and compute early_secret = HKDF-Extract(0, 0^32) (no-PSK). */
101void tls13_ks_early(const Tls13Kdf *kdf, Tls13KeySchedule *ks);
102
103/**
104 * @brief Step 2: handshake_secret and the client/server handshake traffic secrets.
105 *
106 * handshake_secret = HKDF-Extract(Derive-Secret(early, "derived", H("")), @p ecdhe); the traffic
107 * secrets are Derive-Secret(handshake_secret, "c hs traffic"/"s hs traffic", @p ch_sh_hash). The
108 * variant bound by tls13_ks_early() is used throughout.
109 *
110 * @param ecdhe The (EC)DHE shared secret: 32 bytes for X25519, or the 64-byte concatenation
111 * ML-KEM_secret || X25519_secret for the X25519MLKEM768 hybrid group.
112 * @param ch_sh_hash Transcript-Hash of ClientHello..ServerHello.
113 * @param ecdhe_len Length of @p ecdhe (32 for X25519, 64 for the hybrid).
114 */
115void tls13_ks_handshake(Tls13KeySchedule *ks, const uint8_t *ecdhe, const uint8_t ch_sh_hash[TLS13_SECRET_LEN],
116 size_t ecdhe_len = TLS13_SECRET_LEN);
117
118/**
119 * @brief Step 3: master_secret and the client/server application traffic secrets.
120 *
121 * master_secret = HKDF-Extract(Derive-Secret(handshake, "derived", H("")), 0^32); the traffic
122 * secrets are Derive-Secret(master_secret, "c ap traffic"/"s ap traffic", @p ch_sfin_hash).
123 *
124 * @param ch_sfin_hash Transcript-Hash of ClientHello..server Finished.
125 */
126void tls13_ks_master(Tls13KeySchedule *ks, const uint8_t ch_sfin_hash[TLS13_SECRET_LEN]);
127
128/**
129 * @brief The Finished verify_data (RFC 8446 sec 4.4.4).
130 *
131 * finished_key = HKDF-Expand-Label(@p base_secret, "finished", "", 32); the output is
132 * HMAC-SHA256(finished_key, @p transcript_hash). @p base_secret is the sender's handshake traffic
133 * secret (server_hs_traffic for the server's Finished, client_hs_traffic to verify the client's).
134 *
135 * @param kdf KDF variant (label prefix).
136 * @param base_secret The Finished sender's handshake traffic secret.
137 * @param transcript_hash Transcript-Hash of the handshake up to but excluding this Finished.
138 * @param out 32-byte verify_data.
139 */
140void tls13_finished_mac(const Tls13Kdf *kdf, const uint8_t base_secret[TLS13_SECRET_LEN],
141 const uint8_t transcript_hash[TLS13_SECRET_LEN], uint8_t out[TLS13_SECRET_LEN]);
142
143#endif // DETWS_ENABLE_HTTP3
144#endif // DETERMINISTICESPASYNCWEBSERVER_TLS13_KDF_H
User-facing configuration for DeterministicESPAsyncWebServer.