ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
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 hkdf.cpp
6 * @brief HKDF-SHA256 and TLS 1.3 HKDF-Expand-Label (see pc_hkdf.h).
7 */
8
9#include "crypto/kdf/hkdf.h"
10
11#if (PC_ENABLE_HTTP3 || PC_ENABLE_DTLS)
12
14#include <string.h>
15
16void pc_hkdf_extract(const uint8_t *salt, size_t salt_len, const uint8_t *ikm, size_t ikm_len,
17 uint8_t prk[PC_HKDF_HASH_LEN])
18{
19 // RFC 5869 sec 2.2: PRK = HMAC-Hash(salt, IKM). pc_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 pc_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[PC_HKDF_HASH_LEN], const uint8_t *info, size_t info_len, uint8_t *out,
30 size_t out_len)
31{
32 uint8_t t[PC_HKDF_HASH_LEN];
33 size_t t_len = 0; // 0 for T(0) (empty), PC_HKDF_HASH_LEN afterwards
34 size_t done = 0;
35 uint8_t counter = 0;
36 while (done < out_len)
37 {
38 counter++;
40 pc_hmac_sha256_init(&ctx, prk, PC_HKDF_HASH_LEN);
41 pc_hmac_sha256_update(&ctx, t, t_len);
42 pc_hmac_sha256_update(&ctx, info, info_len);
43 pc_hmac_sha256_update(&ctx, &counter, 1);
44 pc_hmac_sha256_final(&ctx, t);
45 t_len = PC_HKDF_HASH_LEN;
46
47 size_t take = out_len - done;
48 if (take > PC_HKDF_HASH_LEN)
49 {
50 take = PC_HKDF_HASH_LEN;
51 }
52 memcpy(out + done, t, take);
53 done += take;
54 }
55}
56} // namespace
57
58void pc_hkdf_expand_label_ctx(const uint8_t secret[PC_HKDF_HASH_LEN], const char *label, const uint8_t *context,
59 size_t context_len, uint8_t *out, size_t out_len, const char *label_prefix)
60{
61 // HkdfLabel (RFC 8446 sec 7.1): uint16 length | opaque label<..> = label_prefix + label | opaque context.
62 // The prefix is "tls13 " for TLS/QUIC (RFC 8446) or "dtls13" for DTLS 1.3 (RFC 9147 sec 5.9); the
63 // caller supplies whichever applies, so this primitive stays protocol-agnostic. Label length maxes
64 // out well under 255 (longest is "tls13 client in" = 15); the context is a Transcript-Hash (<= 32)
65 // for Derive-Secret and empty for packet-protection keys. A fixed 2 + 1 + 255 + 1 + 255 scratch
66 // buffer covers every caller.
67 // Bound the scans: the combined label is opaque<7..255> (RFC 8446 sec 7.1), so cap prefix+label at
68 // 255 - this both fits the reserved region below and keeps the single length byte from wrapping,
69 // even if a caller ever passed a non-NUL-terminated string.
70 size_t prefix_len = strnlen(label_prefix, 255);
71 size_t label_len = strnlen(label, 255 - prefix_len);
72 uint8_t info[2 + 1 + 255 + 1 + 255];
73 size_t p = 0;
74 info[p++] = (uint8_t)(out_len >> 8);
75 info[p++] = (uint8_t)(out_len & 0xff);
76 info[p++] = (uint8_t)(prefix_len + label_len); // full label length, prefix included
77 memcpy(info + p, label_prefix, prefix_len);
78 p += prefix_len;
79 memcpy(info + p, label, label_len);
80 p += label_len;
81 info[p++] = (uint8_t)context_len;
82 if (context_len)
83 {
84 memcpy(info + p, context, context_len);
85 p += context_len;
86 }
87
88 hkdf_expand(secret, info, p, out, out_len);
89}
90
91void pc_hkdf_expand_label(const uint8_t secret[PC_HKDF_HASH_LEN], const char *label, uint8_t *out, size_t out_len,
92 const char *label_prefix)
93{
94 pc_hkdf_expand_label_ctx(secret, label, nullptr, 0, out, out_len, label_prefix);
95}
96
97#endif // PC_ENABLE_HTTP3 || PC_ENABLE_DTLS
HKDF-SHA256 (RFC 5869) and TLS 1.3 HKDF-Expand-Label (RFC 8446 sec 7.1).
void pc_hmac_sha256(const uint8_t *key, size_t key_len, const uint8_t *data, size_t len, uint8_t mac[PC_HMAC_SHA256_LEN])
Compute HMAC-SHA2-256 over a single contiguous buffer.
void pc_hmac_sha256_update(pc_hmac_sha256_ctx *ctx, const uint8_t *data, size_t len)
Feed len bytes into the running HMAC.
void pc_hmac_sha256_init(pc_hmac_sha256_ctx *ctx, const uint8_t *key, size_t key_len)
Initialize a streaming HMAC-SHA2-256 context.
void pc_hmac_sha256_final(pc_hmac_sha256_ctx *ctx, uint8_t mac[PC_HMAC_SHA256_LEN])
Finalize and write the 32-byte MAC.
HMAC-SHA2-256 (RFC 2104 + FIPS 198-1) - streaming context and one-shot API.
Streaming HMAC-SHA2-256 context.
Definition hmac_sha256.h:57