diff --git a/runtime/src/crypto.c b/runtime/src/crypto.c index f1e5e8c..3f5d9ae 100644 --- a/runtime/src/crypto.c +++ b/runtime/src/crypto.c @@ -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 diff --git a/runtime/src/crypto.h b/runtime/src/crypto.h index 9468b67..b6ded4a 100644 --- a/runtime/src/crypto.h +++ b/runtime/src/crypto.h @@ -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 diff --git a/runtime/test/test_crypto.c b/runtime/test/test_crypto.c index 615f2d9..ecaa575 100644 --- a/runtime/test/test_crypto.c +++ b/runtime/test/test_crypto.c @@ -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"); }