code wiki / (root) / nx_hmac.nx

nx_hmac.nx source

↩ module page · 124 lines · 4818 B

1// hmac.nx -- HMAC-SHA-256 (RFC 2104, FIPS 198-1). 2// 3// license_tier: INDEPENDENT_REDERIVE 4// genealogy_id: international-research-sources/nist/fips_198_1 5// 6// Keyed-hash MAC built on sha256.nx. Used throughout TLS 1.3 key 7// schedule (RFC 8446 ยง7.1) as the HKDF primitive, in older TLS as 8// Finished-message signer, and wherever we need a symmetric 9// authenticator without a fresh nonce (unlike Poly1305). 10// 11// Construction (RFC 2104): 12// HMAC(K, M) = H( (K' xor opad) || H( (K' xor ipad) || M ) ) 13// where H = SHA-256, block size B = 64 bytes, and K' = 14// - H(K) if len(K) > B -- pre-hash oversized keys 15// - K || zeros if len(K) < B -- zero-pad short keys 16// - K if len(K) == B 17// ipad = 0x36 repeated; opad = 0x5c repeated. 18// 19// Why not Poly1305 everywhere: 20// - Poly1305 is one-time-key (P4 in poly1305.nx); reusing a key 21// across messages breaks it. HMAC is many-to-one: a long-lived 22// HMAC key authenticates many messages safely. TLS transcripts 23// span the whole handshake so HMAC is the right tool there. 24// 25// Invariants: 26// HM1 Key processing depends only on key LENGTH, not key VALUE: 27// short keys zero-padded, long keys pre-hashed through SHA- 28// 256 (which is constant-time by construction). 29// HM2 XOR loops read every byte of the block regardless of key 30// content; no early exit. 31// HM3 Output is exactly 32 bytes (SHA-256 digest width). 32// 33// References: 34// RFC 2104 (HMAC), FIPS 198-1, NIST SP 800-107. Test vectors 35// from RFC 4231 (HMAC-SHA-256 specifically). 36// 37// nx_safety_envelope: 38// intended_use: "HMAC-SHA-256 -- TLS 1.3 MAC + HKDF foundation 39// + general keyed-hash authentication" 40// sil_target: SIL3 (authentication primitive; MAC 41// forgery = arbitrary message 42// acceptance) 43// asil_target: QM 44// dal_target: DAL B 45// iec_62304_class: B 46// evidence: [no_FP, sealed_verdict, 47// RFC_4231_test_vectors_VERIFIED, 48// constant_time_via_nx_sha256_inheritance, 49// license_tier_INDEPENDENT_REDERIVE] 50// hazard_register: [bug-tape-key-shorter-than-blocksize-not-hashed, 51// bug-tape-non-constant-time-MAC-compare, 52// bug-tape-MAC-prefix-length-extension] 53// residual_risk: "MAC comparison MUST be constant-time on 54// caller side; substrate provides the MAC 55// value but does not enforce the comparator 56// timing. Use nx_ct_compare for verification." 57// verdict: NOT_YET_EVALUATED 58 59import "nx_syscalls.nx" 60import "nx_sha256.nx" 61 62const HMAC_BLOCK: i64 = 64 // SHA-256 block size 63const HMAC_HASH: i64 = 32 // SHA-256 output size 64const IPAD: i64 = 0x36 65const OPAD: i64 = 0x5C 66 67// HMAC-SHA-256. Writes 32-byte tag to `out`. 68func hmac_sha256(key: *u8, key_len: i64, msg: *u8, msg_len: i64, 69 out: *u8) -> i64 { 70 // Step 1: derive K' from the key. 71 let kp: *u8 = sys_mmap(HMAC_BLOCK) 72 var i: i64 = 0 73 while i < HMAC_BLOCK { kp[i] = 0; i = i + 1 } 74 75 if key_len > HMAC_BLOCK { 76 // Oversized: replace with SHA-256(key). Remainder stays zero. 77 sha256_digest(key, key_len, kp) 78 } else { 79 var j: i64 = 0 80 while j < key_len { kp[j] = key[j]; j = j + 1 } 81 } 82 83 // Step 2: inner pad + hash inner. 84 let inner_key: *u8 = sys_mmap(HMAC_BLOCK) 85 let ii: *Sha256 = sys_mmap(512) as *Sha256 86 sha256_init(ii) 87 88 var b: i64 = 0 89 while b < HMAC_BLOCK { 90 inner_key[b] = kp[b] ^ IPAD 91 b = b + 1 92 } 93 sha256_update(ii, inner_key, HMAC_BLOCK) 94 sha256_update(ii, msg, msg_len) 95 let inner_digest: *u8 = sys_mmap(HMAC_HASH) 96 sha256_final(ii, inner_digest) 97 98 // Step 3: outer pad + hash outer. 99 let outer_key: *u8 = sys_mmap(HMAC_BLOCK) 100 let oi: *Sha256 = sys_mmap(512) as *Sha256 101 sha256_init(oi) 102 103 b = 0 104 while b < HMAC_BLOCK { 105 outer_key[b] = kp[b] ^ OPAD 106 b = b + 1 107 } 108 sha256_update(oi, outer_key, HMAC_BLOCK) 109 sha256_update(oi, inner_digest, HMAC_HASH) 110 sha256_final(oi, out) 111 return 0 112} 113 114// Self-test: compile-only. Real validation is via RFC 4231 test 115// case 1: key = 0x0b*20, data = "Hi There" -> b0344c61d8db3853... 116func main() -> i64 { 117 let key: *u8 = sys_mmap(20) 118 let msg: *u8 = "Hi There" 119 let tag: *u8 = sys_mmap(32) 120 var i: i64 = 0 121 while i < 20 { key[i] = 0x0B; i = i + 1 } 122 hmac_sha256(key, 20, msg, 8, tag) 123 return tag[0] as i64 124}