DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
quic_hkdf.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 quic_hkdf.cpp
6 * @brief HKDF-SHA256 and TLS 1.3 HKDF-Expand-Label (see quic_hkdf.h).
7 */
8
10
11#if (DETWS_ENABLE_HTTP3 || DETWS_ENABLE_DTLS)
12
14#include <string.h>
15
16void quic_hkdf_extract(const uint8_t *salt, size_t salt_len, const uint8_t *ikm, size_t ikm_len,
17 uint8_t prk[QUIC_HKDF_HASH_LEN])
18{
19 // RFC 5869 sec 2.2: PRK = HMAC-Hash(salt, IKM). ssh_hmac_sha256 pre-hashes keys > 64 bytes and
20 // zero-pads shorter ones, which is exactly HMAC's own key handling, so the salt goes in as-is.
21 ssh_hmac_sha256(salt, salt_len, ikm, ikm_len, prk);
22}
23
24namespace
25{
26// RFC 5869 sec 2.3 HKDF-Expand for the QUIC case: the info block is small and fixed and the
27// requested length never exceeds one hash block, but the general N-block loop is written out so a
28// future >32-byte caller stays correct. T(i) = HMAC(PRK, T(i-1) || info || i), i counts from 1.
29void hkdf_expand(const uint8_t prk[QUIC_HKDF_HASH_LEN], const uint8_t *info, size_t info_len, uint8_t *out,
30 size_t out_len)
31{
32 uint8_t t[QUIC_HKDF_HASH_LEN];
33 size_t t_len = 0; // 0 for T(0) (empty), QUIC_HKDF_HASH_LEN afterwards
34 size_t done = 0;
35 uint8_t counter = 0;
36 while (done < out_len)
37 {
38 counter++;
39 SshHmacCtx ctx;
40 ssh_hmac_sha256_init(&ctx, prk, QUIC_HKDF_HASH_LEN);
41 ssh_hmac_sha256_update(&ctx, t, t_len);
42 ssh_hmac_sha256_update(&ctx, info, info_len);
43 ssh_hmac_sha256_update(&ctx, &counter, 1);
44 ssh_hmac_sha256_final(&ctx, t);
45 t_len = QUIC_HKDF_HASH_LEN;
46
47 size_t take = out_len - done;
48 if (take > QUIC_HKDF_HASH_LEN)
49 take = QUIC_HKDF_HASH_LEN;
50 memcpy(out + done, t, take);
51 done += take;
52 }
53}
54} // namespace
55
56void quic_hkdf_expand_label_ctx(const uint8_t secret[QUIC_HKDF_HASH_LEN], const char *label, const uint8_t *context,
57 size_t context_len, uint8_t *out, size_t out_len, const char *label_prefix)
58{
59 // HkdfLabel (RFC 8446 sec 7.1): uint16 length | opaque label<..> = label_prefix + label | opaque context.
60 // The prefix is "tls13 " for TLS/QUIC (RFC 8446) or "dtls13" for DTLS 1.3 (RFC 9147 sec 5.9); the
61 // caller supplies whichever applies, so this primitive stays protocol-agnostic. Label length maxes
62 // out well under 255 (longest is "tls13 client in" = 15); the context is a Transcript-Hash (<= 32)
63 // for Derive-Secret and empty for packet-protection keys. A fixed 2 + 1 + 255 + 1 + 255 scratch
64 // buffer covers every caller.
65 // Bound the scans: the combined label is opaque<7..255> (RFC 8446 sec 7.1), so cap prefix+label at
66 // 255 - this both fits the reserved region below and keeps the single length byte from wrapping,
67 // even if a caller ever passed a non-NUL-terminated string.
68 size_t prefix_len = strnlen(label_prefix, 255);
69 size_t label_len = strnlen(label, 255 - prefix_len);
70 uint8_t info[2 + 1 + 255 + 1 + 255];
71 size_t p = 0;
72 info[p++] = (uint8_t)(out_len >> 8);
73 info[p++] = (uint8_t)(out_len & 0xff);
74 info[p++] = (uint8_t)(prefix_len + label_len); // full label length, prefix included
75 memcpy(info + p, label_prefix, prefix_len);
76 p += prefix_len;
77 memcpy(info + p, label, label_len);
78 p += label_len;
79 info[p++] = (uint8_t)context_len;
80 if (context_len)
81 {
82 memcpy(info + p, context, context_len);
83 p += context_len;
84 }
85
86 hkdf_expand(secret, info, p, out, out_len);
87}
88
89void quic_hkdf_expand_label(const uint8_t secret[QUIC_HKDF_HASH_LEN], const char *label, uint8_t *out, size_t out_len,
90 const char *label_prefix)
91{
92 quic_hkdf_expand_label_ctx(secret, label, nullptr, 0, out, out_len, label_prefix);
93}
94
95#endif // DETWS_ENABLE_HTTP3 || DETWS_ENABLE_DTLS
HKDF-SHA256 (RFC 5869) and TLS 1.3 HKDF-Expand-Label (RFC 8446 sec 7.1).
void ssh_hmac_sha256_final(SshHmacCtx *ctx, uint8_t mac[SSH_HMAC_SHA256_LEN])
Finalize and write the 32-byte MAC.
void ssh_hmac_sha256_init(SshHmacCtx *ctx, const uint8_t *key, size_t key_len)
Initialize a streaming HMAC-SHA2-256 context.
void ssh_hmac_sha256_update(SshHmacCtx *ctx, const uint8_t *data, size_t len)
Feed len bytes into the running HMAC.
void ssh_hmac_sha256(const uint8_t *key, size_t key_len, const uint8_t *data, size_t len, uint8_t mac[SSH_HMAC_SHA256_LEN])
Compute HMAC-SHA2-256 over a single contiguous buffer.
HMAC-SHA2-256 (RFC 2104 + FIPS 198-1).
Streaming HMAC-SHA2-256 context.