nx_hmac.nx source
↩ module page · 155 lines · 6674 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}
125
126// Caller-owned HMAC workspace. Sizes are SHA-256 block/digest widths, not
127// caller-content ceilings. All nested SHA operations borrow initialized storage.
128const HMAC_E_WORKSPACE:i64=0-2
129const HMAC_E_INPUT:i64=0-1
130func hmac_sha256_workspace_bytes()->i64{
131 return HMAC_BLOCK*2+HMAC_HASH+sha256_workspace_bytes()
132}
133func hmac_sha256_workspace(key:*u8,key_len:i64,msg:*u8,msg_len:i64,out:*u8,workspace:*u8,capacity:i64)->i64{
134 if sha256_checked_input(key,key_len,out)!=1 || sha256_checked_input(msg,msg_len,out)!=1{return HMAC_E_INPUT}
135 if msg_len>SHA256_SIGNED_MAX/SHA256_BITS_PER_BYTE-HMAC_BLOCK{return HMAC_E_INPUT}
136 let needed:i64=hmac_sha256_workspace_bytes();let base:i64=workspace as i64
137 if base<=0 || capacity<needed || base>SHA256_SIGNED_MAX-needed || base%SHA256_WORD_ALIGN!=0{return HMAC_E_WORKSPACE}
138 if sha256_ranges_overlap(base,needed,key as i64,key_len)==1 || sha256_ranges_overlap(base,needed,msg as i64,msg_len)==1 || sha256_ranges_overlap(base,needed,out as i64,HMAC_HASH)==1{return HMAC_E_WORKSPACE}
139 let kp:*u8=workspace;let pad:*u8=workspace+HMAC_BLOCK;let digest:*u8=pad+HMAC_BLOCK
140 let sw:*u8=digest+HMAC_HASH;let sn:i64=sha256_workspace_bytes()
141 var i:i64=0;while i<HMAC_BLOCK{kp[i]=0 as u8;i=i+1}
142 var rc:i64=0
143 if key_len>HMAC_BLOCK{rc=sha256_digest_workspace(key,key_len,kp,sw,sn)}
144 else{i=0;while i<key_len{kp[i]=key[i];i=i+1}}
145 if rc!=0{return rc}
146 i=0;while i<HMAC_BLOCK{pad[i]=kp[i]^IPAD;i=i+1}
147 rc=sha256_init_workspace(sw,sn);if rc!=0{return rc}
148 let ctx:*Sha256=sw as *Sha256
149 sha256_update(ctx,pad,HMAC_BLOCK);sha256_update(ctx,msg,msg_len);sha256_final(ctx,digest)
150 i=0;while i<HMAC_BLOCK{pad[i]=kp[i]^OPAD;i=i+1}
151 rc=sha256_init_workspace(sw,sn);if rc!=0{return rc}
152 sha256_update(ctx,pad,HMAC_BLOCK);sha256_update(ctx,digest,HMAC_HASH);sha256_final(ctx,out)
153 i=0;while i<needed{workspace[i]=0 as u8;i=i+1}
154 return 0
155}