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}