hmac.nx source
↩ module page · 110 lines · 4472 B
1// ############################################################################################
2// ## DO NOT IMPORT THIS FILE. USE nx_hmac.nx INSTEAD. (2026-07-31, debt 1785524913) ##
3// ## This is a CODE-IDENTICAL twin of nx_hmac.nx (49/49 statements) that hangs off the ##
4// ## LEGACY syscalls.nx + sha256.nx family. Both files define hmac_sha256 AND main, and the ##
5// ## import expander dedupes BY FILE PATH, NOT BY SYMBOL -- so importing this one anywhere ##
6// ## the nx_ family is also reachable produces `duplicate definition of hmac_sha256` and ##
7// ## breaks EVERY organ in that closure. That is not hypothetical: it happened the moment ##
8// ## nx_https_fetch_follow began importing nx_tls12_req (which imported this file), and it ##
9// ## broke the whole research-fetch family until all 9 importers were repointed. ##
10// ## Kept (not deleted) so no work is destroyed; importers: 0. Keep it that way. ##
11// ############################################################################################
12// hmac.nx -- HMAC-SHA-256 (RFC 2104, FIPS 198-1).
13//
14// Keyed-hash MAC built on sha256.nx. Used throughout TLS 1.3 key
15// schedule (RFC 8446 ยง7.1) as the HKDF primitive, in older TLS as
16// Finished-message signer, and wherever we need a symmetric
17// authenticator without a fresh nonce (unlike Poly1305).
18//
19// Construction (RFC 2104):
20// HMAC(K, M) = H( (K' xor opad) || H( (K' xor ipad) || M ) )
21// where H = SHA-256, block size B = 64 bytes, and K' =
22// - H(K) if len(K) > B -- pre-hash oversized keys
23// - K || zeros if len(K) < B -- zero-pad short keys
24// - K if len(K) == B
25// ipad = 0x36 repeated; opad = 0x5c repeated.
26//
27// Why not Poly1305 everywhere:
28// - Poly1305 is one-time-key (P4 in poly1305.nx); reusing a key
29// across messages breaks it. HMAC is many-to-one: a long-lived
30// HMAC key authenticates many messages safely. TLS transcripts
31// span the whole handshake so HMAC is the right tool there.
32//
33// Invariants:
34// HM1 Key processing depends only on key LENGTH, not key VALUE:
35// short keys zero-padded, long keys pre-hashed through SHA-
36// 256 (which is constant-time by construction).
37// HM2 XOR loops read every byte of the block regardless of key
38// content; no early exit.
39// HM3 Output is exactly 32 bytes (SHA-256 digest width).
40//
41// References:
42// RFC 2104 (HMAC), FIPS 198-1, NIST SP 800-107. Test vectors
43// from RFC 4231 (HMAC-SHA-256 specifically).
44
45import "syscalls.nx"
46import "sha256.nx"
47
48const HMAC_BLOCK: i64 = 64 // SHA-256 block size
49const HMAC_HASH: i64 = 32 // SHA-256 output size
50const IPAD: i64 = 0x36
51const OPAD: i64 = 0x5C
52
53// HMAC-SHA-256. Writes 32-byte tag to `out`.
54func hmac_sha256(key: *u8, key_len: i64, msg: *u8, msg_len: i64,
55 out: *u8) -> i64 {
56 // Step 1: derive K' from the key.
57 let kp: *u8 = sys_mmap(HMAC_BLOCK)
58 var i: i64 = 0
59 while i < HMAC_BLOCK { kp[i] = 0; i = i + 1 }
60
61 if key_len > HMAC_BLOCK {
62 // Oversized: replace with SHA-256(key). Remainder stays zero.
63 sha256_digest(key, key_len, kp)
64 } else {
65 var j: i64 = 0
66 while j < key_len { kp[j] = key[j]; j = j + 1 }
67 }
68
69 // Step 2: inner pad + hash inner.
70 let inner_key: *u8 = sys_mmap(HMAC_BLOCK)
71 let ii: *Sha256 = sys_mmap(512) as *Sha256
72 sha256_init(ii)
73
74 var b: i64 = 0
75 while b < HMAC_BLOCK {
76 inner_key[b] = kp[b] ^ IPAD
77 b = b + 1
78 }
79 sha256_update(ii, inner_key, HMAC_BLOCK)
80 sha256_update(ii, msg, msg_len)
81 let inner_digest: *u8 = sys_mmap(HMAC_HASH)
82 sha256_final(ii, inner_digest)
83
84 // Step 3: outer pad + hash outer.
85 let outer_key: *u8 = sys_mmap(HMAC_BLOCK)
86 let oi: *Sha256 = sys_mmap(512) as *Sha256
87 sha256_init(oi)
88
89 b = 0
90 while b < HMAC_BLOCK {
91 outer_key[b] = kp[b] ^ OPAD
92 b = b + 1
93 }
94 sha256_update(oi, outer_key, HMAC_BLOCK)
95 sha256_update(oi, inner_digest, HMAC_HASH)
96 sha256_final(oi, out)
97 return 0
98}
99
100// Self-test: compile-only. Real validation is via RFC 4231 test
101// case 1: key = 0x0b*20, data = "Hi There" -> b0344c61d8db3853...
102func main() -> i64 {
103 let key: *u8 = sys_mmap(20)
104 let msg: *u8 = "Hi There"
105 let tag: *u8 = sys_mmap(32)
106 var i: i64 = 0
107 while i < 20 { key[i] = 0x0B; i = i + 1 }
108 hmac_sha256(key, 20, msg, 8, tag)
109 return tag[0] as i64
110}