code wiki / (root) / hmac.nx

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}