feat(crypto): HKDF-SHA256 for the TLS 1.3 key schedule (rv2 9 phase B)

- wo_hkdf_sha256_extract/expand (RFC 5869) + expand_label (RFC 8446 §7.1),
  internal C over the existing hmac_sha256; SHA-256 (mandatory-suite hash;
  SHA-384 a later add for the AES-256 suite)
- no builtin, no compiler change -- no .wo consumer yet (the TLS handshake
  is the consumer); exposed for the C unit test
- KAT-gated in test_crypto: RFC 5869 Test Case 1 (PRK + 42-byte OKM) and
  three HKDF-Expand-Label vectors (key/iv/derived-secret shape); 57/0,
  ASan/UBSan clean; runtime battery green
- rv2 9 ladder: A (AEAD, = rv2 8) and B (HKDF) now done; next C X25519

(cherry picked from commit c8d27b6c89a80cd97a996ff7d8b64ff4895e4b26)
This commit is contained in:
shoney.arickathil 2026-09-08 13:57:02 +02:00
parent 2db3766b66
commit 36ce2332ef
3 changed files with 104 additions and 0 deletions

View file

@ -199,6 +199,63 @@ void wo_hmac_sha256(const uint8_t *key, size_t klen, const uint8_t *msg,
wo_sha256(outer, 96, out);
}
/* ---- HKDF-SHA256 (rv2 9 phase B: the TLS 1.3 key schedule) --------------
* RFC 5869 (Extract/Expand) + RFC 8446 §7.1 (Expand-Label), built on the
* existing HMAC-SHA256. Internal C consumed by the TLS handshake; no `.wo`
* builtin until a `.wo` consumer exists. SHA-256 only — the hash of the
* mandatory suites (TLS_AES_128_GCM_SHA256, TLS_CHACHA20_POLY1305_SHA256);
* SHA-384 is a later addition for the AES-256 suite. */
void wo_hkdf_sha256_extract(const uint8_t *salt, size_t saltlen,
const uint8_t *ikm, size_t ikmlen, uint8_t prk[32]) {
uint8_t zero[32] = { 0 };
if (!salt || saltlen == 0) { salt = zero; saltlen = 32; }
wo_hmac_sha256(salt, saltlen, ikm, ikmlen, prk);
}
/* OKM = T(1)||T(2)||…, T(i) = HMAC(PRK, T(i-1)||info||i). 0 ok, -1 on a
* too-long request (>255*32) or OOM. */
int wo_hkdf_sha256_expand(const uint8_t prk[32], const uint8_t *info,
size_t infolen, uint8_t *okm, size_t okmlen) {
if (okmlen > 255u * 32u) return -1;
uint8_t t[32];
size_t tlen = 0, done = 0;
uint8_t counter = 1;
while (done < okmlen) {
size_t mlen = tlen + infolen + 1;
uint8_t *m = (uint8_t *)malloc(mlen ? mlen : 1);
if (!m) return -1;
if (tlen) memcpy(m, t, tlen);
if (infolen) memcpy(m + tlen, info, infolen);
m[tlen + infolen] = counter;
wo_hmac_sha256(prk, 32, m, mlen, t);
free(m);
tlen = 32;
size_t n = okmlen - done < 32 ? okmlen - done : 32;
memcpy(okm + done, t, n);
done += n; counter++;
}
return 0;
}
/* RFC 8446 §7.1: HKDF-Expand-Label(secret, label, context, len) where
* HkdfLabel = uint16 len || opaque("tls13 "+label) || opaque(context). */
int wo_hkdf_sha256_expand_label(const uint8_t secret[32], const char *label,
size_t labellen, const uint8_t *ctx,
size_t ctxlen, uint8_t *out, size_t outlen) {
if (labellen > 249 || ctxlen > 255 || outlen > 65535) return -1;
uint8_t info[2 + 1 + 255 + 1 + 255];
size_t p = 0;
info[p++] = (uint8_t)(outlen >> 8);
info[p++] = (uint8_t)outlen;
info[p++] = (uint8_t)(6 + labellen);
memcpy(info + p, "tls13 ", 6); p += 6;
memcpy(info + p, label, labellen); p += labellen;
info[p++] = (uint8_t)ctxlen;
if (ctxlen) { memcpy(info + p, ctx, ctxlen); p += ctxlen; }
return wo_hkdf_sha256_expand(secret, info, p, out, outlen);
}
/* ---- ChaCha20-Poly1305 AEAD (rv2 8 phase A, RFC 8439) ------------------
* Hand-rolled, libc-only, constant-time by construction (add/xor/rotate and
* limb arithmetic; no data-dependent branches, no table lookups). The

View file

@ -38,6 +38,16 @@ int wo_aes_gcm_open(const uint8_t *key, size_t keylen, const uint8_t nonce[12],
const uint8_t *aad, size_t aadlen, const uint8_t *ct,
size_t ctlen, const uint8_t tag[16], uint8_t *out);
/* HKDF-SHA256 (rv2 9 phase B: the TLS 1.3 key schedule). RFC 5869 + RFC 8446
* §7.1. Internal to the runtime's crypto/TLS code (no `.wo` builtin yet). */
void wo_hkdf_sha256_extract(const uint8_t *salt, size_t saltlen,
const uint8_t *ikm, size_t ikmlen, uint8_t prk[32]);
int wo_hkdf_sha256_expand(const uint8_t prk[32], const uint8_t *info,
size_t infolen, uint8_t *okm, size_t okmlen);
int wo_hkdf_sha256_expand_label(const uint8_t secret[32], const char *label,
size_t labellen, const uint8_t *ctx,
size_t ctxlen, uint8_t *out, size_t outlen);
int wo_builtin_crypto(wo_vm *vm, uint64_t *R, uint32_t ins, const char **msg);
#endif

View file

@ -251,5 +251,42 @@ int main(void) {
"76fc6ece0f4e1768cddf8853bb2d551b");
wo_aes_force_software = 0;
/* HKDF-SHA256 (rv2 9 phase B): RFC 5869 Test Case 1 (Extract + Expand). */
{
uint8_t ikm[22], salt[13], info[10], prk[32], okm[42];
char got[85];
memset(ikm, 0x0b, 22);
for (int i = 0; i < 13; i++) salt[i] = (uint8_t)i;
for (int i = 0; i < 10; i++) info[i] = (uint8_t)(0xf0 + i);
wo_hkdf_sha256_extract(salt, 13, ikm, 22, prk);
hex(prk, 32, got);
T_CHECK(strcmp(got,
"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5") == 0);
T_CHECK(wo_hkdf_sha256_expand(prk, info, 10, okm, 42) == 0);
hex(okm, 42, got);
T_CHECK(strcmp(got,
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf"
"34007208d5b887185865") == 0);
}
/* HKDF-Expand-Label (RFC 8446 §7.1), reference values from a known-good
* HKDF-Expand over the tls13 label struct. secret = 0x00..0x1f. */
{
uint8_t secret[32], out[32], h[32];
char got[65];
for (int i = 0; i < 32; i++) secret[i] = (uint8_t)i;
T_CHECK(wo_hkdf_sha256_expand_label(secret, "key", 3, NULL, 0, out, 16) == 0);
hex(out, 16, got);
T_CHECK(strcmp(got, "9c9783cf77ea32d44f369da41f19f3cc") == 0);
T_CHECK(wo_hkdf_sha256_expand_label(secret, "iv", 2, NULL, 0, out, 12) == 0);
hex(out, 12, got);
T_CHECK(strcmp(got, "2f41c846a431a163814bcd71") == 0);
/* with a context = SHA-256("") (a Derive-Secret shape) */
wo_sha256((const uint8_t *)"", 0, h);
T_CHECK(wo_hkdf_sha256_expand_label(secret, "derived", 7, h, 32, out, 32) == 0);
hex(out, 32, got);
T_CHECK(strcmp(got,
"a5b1caa258481fdf573ac069f281e534e4a2379ec9e457e0c8494c227efb40e6") == 0);
}
return t_report("test_crypto");
}