code wiki / (root) / nx_hkdf_sha384_wasm.nx

nx_hkdf_sha384_wasm.nx source

↩ module page · 408 lines · 16648 B

1// nx_hkdf_sha384_wasm.nx -- HKDF-SHA-384 (RFC 5869) for WAT target. 2// 3// Built on inline HMAC-SHA-384 (= SHA-512 compression + SHA-384 IV + 4// 48-byte truncation). PRK length = 48 (one HMAC-SHA-384 output); 5// HashLen = 48; max OKM = 255 * 48 = 12,240 bytes. 6// 7// Used in the TLS 1.3 key schedule when the ciphersuite is 8// TLS_AES_256_GCM_SHA384. 9// 10// API: 11// nx_hkdf384_extract(salt, salt_len, ikm, ikm_len, scratch, prk_out) 12// nx_hkdf384_expand(prk, info, info_len, okm_len, scratch, okm_out) 13// 14// Verified vs RFC-style cross-check against Node OpenSSL HKDF-SHA-384. 15// 16// license_tier: INDEPENDENT_REDERIVE 17// genealogy_id: international-research-sources/ietf/rfc_5869 + rfc_4231 18// lineage_id: nishi_hkdf_sha384_wasm_q1 19// safe_shift_audit: rotr-style (shift exponent = nn, mask exponent = 64-nn; differs -> safe) 20 21const W_OFF: i64 = 200 22const HMAC_B: i64 = 128 23 24func _rotr64(x: i64, n: i64) -> i64 { 25 let nn: i64 = n & 63 26 let low_mask: i64 = (1 << (64 - nn)) - 1 27 let lo: i64 = (x >> nn) & low_mask 28 let hi: i64 = x << (64 - nn) 29 return lo | hi 30} 31 32func _be64_read(buf: *u8, off: i64) -> i64 { 33 let b0: i64 = buf[off] 34 let b1: i64 = buf[off + 1] 35 let b2: i64 = buf[off + 2] 36 let b3: i64 = buf[off + 3] 37 let b4: i64 = buf[off + 4] 38 let b5: i64 = buf[off + 5] 39 let b6: i64 = buf[off + 6] 40 let b7: i64 = buf[off + 7] 41 return (b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | 42 (b4 << 24) | (b5 << 16) | (b6 << 8) | b7 43} 44func _be64_write(buf: *u8, off: i64, v: i64) -> i64 { 45 buf[off] = (v >> 56) & 0xFF 46 buf[off + 1] = (v >> 48) & 0xFF 47 buf[off + 2] = (v >> 40) & 0xFF 48 buf[off + 3] = (v >> 32) & 0xFF 49 buf[off + 4] = (v >> 24) & 0xFF 50 buf[off + 5] = (v >> 16) & 0xFF 51 buf[off + 6] = (v >> 8) & 0xFF 52 buf[off + 7] = v & 0xFF 53 return 0 54} 55 56func _i64_le_get(buf: *u8, idx: i64) -> i64 { 57 let off: i64 = idx * 8 58 return (buf[off] as i64) | ((buf[off + 1] as i64) << 8) | 59 ((buf[off + 2] as i64) << 16) | ((buf[off + 3] as i64) << 24) | 60 ((buf[off + 4] as i64) << 32) | ((buf[off + 5] as i64) << 40) | 61 ((buf[off + 6] as i64) << 48) | ((buf[off + 7] as i64) << 56) 62} 63func _i64_le_set(buf: *u8, idx: i64, v: i64) -> i64 { 64 let off: i64 = idx * 8 65 buf[off] = v & 0xFF 66 buf[off + 1] = (v >> 8) & 0xFF 67 buf[off + 2] = (v >> 16) & 0xFF 68 buf[off + 3] = (v >> 24) & 0xFF 69 buf[off + 4] = (v >> 32) & 0xFF 70 buf[off + 5] = (v >> 40) & 0xFF 71 buf[off + 6] = (v >> 48) & 0xFF 72 buf[off + 7] = (v >> 56) & 0xFF 73 return 0 74} 75func _h_get(ctx: *u8, i: i64) -> i64 { 76 let off: i64 = 128 + i * 8 77 return (ctx[off] as i64) | ((ctx[off + 1] as i64) << 8) | 78 ((ctx[off + 2] as i64) << 16) | ((ctx[off + 3] as i64) << 24) | 79 ((ctx[off + 4] as i64) << 32) | ((ctx[off + 5] as i64) << 40) | 80 ((ctx[off + 6] as i64) << 48) | ((ctx[off + 7] as i64) << 56) 81} 82func _h_set(ctx: *u8, i: i64, v: i64) -> i64 { 83 let off: i64 = 128 + i * 8 84 ctx[off] = v & 0xFF 85 ctx[off + 1] = (v >> 8) & 0xFF 86 ctx[off + 2] = (v >> 16) & 0xFF 87 ctx[off + 3] = (v >> 24) & 0xFF 88 ctx[off + 4] = (v >> 32) & 0xFF 89 ctx[off + 5] = (v >> 40) & 0xFF 90 ctx[off + 6] = (v >> 48) & 0xFF 91 ctx[off + 7] = (v >> 56) & 0xFF 92 return 0 93} 94 95func _sha512_k(i: i64) -> i64 { 96 if i == 0 { return 0x428a2f98d728ae22 } if i == 1 { return 0x7137449123ef65cd } 97 if i == 2 { return 0xb5c0fbcfec4d3b2f } if i == 3 { return 0xe9b5dba58189dbbc } 98 if i == 4 { return 0x3956c25bf348b538 } if i == 5 { return 0x59f111f1b605d019 } 99 if i == 6 { return 0x923f82a4af194f9b } if i == 7 { return 0xab1c5ed5da6d8118 } 100 if i == 8 { return 0xd807aa98a3030242 } if i == 9 { return 0x12835b0145706fbe } 101 if i == 10 { return 0x243185be4ee4b28c } if i == 11 { return 0x550c7dc3d5ffb4e2 } 102 if i == 12 { return 0x72be5d74f27b896f } if i == 13 { return 0x80deb1fe3b1696b1 } 103 if i == 14 { return 0x9bdc06a725c71235 } if i == 15 { return 0xc19bf174cf692694 } 104 if i == 16 { return 0xe49b69c19ef14ad2 } if i == 17 { return 0xefbe4786384f25e3 } 105 if i == 18 { return 0x0fc19dc68b8cd5b5 } if i == 19 { return 0x240ca1cc77ac9c65 } 106 if i == 20 { return 0x2de92c6f592b0275 } if i == 21 { return 0x4a7484aa6ea6e483 } 107 if i == 22 { return 0x5cb0a9dcbd41fbd4 } if i == 23 { return 0x76f988da831153b5 } 108 if i == 24 { return 0x983e5152ee66dfab } if i == 25 { return 0xa831c66d2db43210 } 109 if i == 26 { return 0xb00327c898fb213f } if i == 27 { return 0xbf597fc7beef0ee4 } 110 if i == 28 { return 0xc6e00bf33da88fc2 } if i == 29 { return 0xd5a79147930aa725 } 111 if i == 30 { return 0x06ca6351e003826f } if i == 31 { return 0x142929670a0e6e70 } 112 if i == 32 { return 0x27b70a8546d22ffc } if i == 33 { return 0x2e1b21385c26c926 } 113 if i == 34 { return 0x4d2c6dfc5ac42aed } if i == 35 { return 0x53380d139d95b3df } 114 if i == 36 { return 0x650a73548baf63de } if i == 37 { return 0x766a0abb3c77b2a8 } 115 if i == 38 { return 0x81c2c92e47edaee6 } if i == 39 { return 0x92722c851482353b } 116 if i == 40 { return 0xa2bfe8a14cf10364 } if i == 41 { return 0xa81a664bbc423001 } 117 if i == 42 { return 0xc24b8b70d0f89791 } if i == 43 { return 0xc76c51a30654be30 } 118 if i == 44 { return 0xd192e819d6ef5218 } if i == 45 { return 0xd69906245565a910 } 119 if i == 46 { return 0xf40e35855771202a } if i == 47 { return 0x106aa07032bbd1b8 } 120 if i == 48 { return 0x19a4c116b8d2d0c8 } if i == 49 { return 0x1e376c085141ab53 } 121 if i == 50 { return 0x2748774cdf8eeb99 } if i == 51 { return 0x34b0bcb5e19b48a8 } 122 if i == 52 { return 0x391c0cb3c5c95a63 } if i == 53 { return 0x4ed8aa4ae3418acb } 123 if i == 54 { return 0x5b9cca4f7763e373 } if i == 55 { return 0x682e6ff3d6b2b8a3 } 124 if i == 56 { return 0x748f82ee5defb2fc } if i == 57 { return 0x78a5636f43172f60 } 125 if i == 58 { return 0x84c87814a1f0ab72 } if i == 59 { return 0x8cc702081a6439ec } 126 if i == 60 { return 0x90befffa23631e28 } if i == 61 { return 0xa4506cebde82bde9 } 127 if i == 62 { return 0xbef9a3f7b2c67915 } if i == 63 { return 0xc67178f2e372532b } 128 if i == 64 { return 0xca273eceea26619c } if i == 65 { return 0xd186b8c721c0c207 } 129 if i == 66 { return 0xeada7dd6cde0eb1e } if i == 67 { return 0xf57d4f7fee6ed178 } 130 if i == 68 { return 0x06f067aa72176fba } if i == 69 { return 0x0a637dc5a2c898a6 } 131 if i == 70 { return 0x113f9804bef90dae } if i == 71 { return 0x1b710b35131c471b } 132 if i == 72 { return 0x28db77f523047d84 } if i == 73 { return 0x32caab7b40c72493 } 133 if i == 74 { return 0x3c9ebe0a15c9bebc } if i == 75 { return 0x431d67c49c100d4c } 134 if i == 76 { return 0x4cc5d4becb3e42b6 } if i == 77 { return 0x597f299cfc657e2a } 135 if i == 78 { return 0x5fcb6fab3ad6faec } 136 return 0x6c44198c4a475817 137} 138 139func _sha512_compress(ctx: *u8) -> i64 { 140 var i: i64 = 0 141 while i < 16 { 142 let w: i64 = _be64_read(ctx, i * 8) 143 _i64_le_set(ctx, (W_OFF / 8) + i, w) 144 i = i + 1 145 } 146 i = 16 147 while i < 80 { 148 let w2: i64 = _i64_le_get(ctx, (W_OFF / 8) + i - 2) 149 let w15: i64 = _i64_le_get(ctx, (W_OFF / 8) + i - 15) 150 let w7: i64 = _i64_le_get(ctx, (W_OFF / 8) + i - 7) 151 let w16: i64 = _i64_le_get(ctx, (W_OFF / 8) + i - 16) 152 let w2_shr6: i64 = (w2 >> 6) & 0x03ffffffffffffff 153 let w15_shr7: i64 = (w15 >> 7) & 0x01ffffffffffffff 154 let s0: i64 = _rotr64(w15, 1) ^ _rotr64(w15, 8) ^ w15_shr7 155 let s1: i64 = _rotr64(w2, 19) ^ _rotr64(w2, 61) ^ w2_shr6 156 let wi: i64 = w16 + s0 + w7 + s1 157 _i64_le_set(ctx, (W_OFF / 8) + i, wi) 158 i = i + 1 159 } 160 var a: i64 = _h_get(ctx, 0) 161 var b: i64 = _h_get(ctx, 1) 162 var c: i64 = _h_get(ctx, 2) 163 var d: i64 = _h_get(ctx, 3) 164 var e: i64 = _h_get(ctx, 4) 165 var f: i64 = _h_get(ctx, 5) 166 var g: i64 = _h_get(ctx, 6) 167 var h: i64 = _h_get(ctx, 7) 168 i = 0 169 while i < 80 { 170 let w: i64 = _i64_le_get(ctx, (W_OFF / 8) + i) 171 let s1: i64 = _rotr64(e, 14) ^ _rotr64(e, 18) ^ _rotr64(e, 41) 172 let ch: i64 = (e & f) ^ ((~e) & g) 173 let temp1: i64 = h + s1 + ch + _sha512_k(i) + w 174 let s0: i64 = _rotr64(a, 28) ^ _rotr64(a, 34) ^ _rotr64(a, 39) 175 let maj: i64 = (a & b) ^ (a & c) ^ (b & c) 176 let temp2: i64 = s0 + maj 177 h = g; g = f; f = e 178 e = d + temp1 179 d = c; c = b; b = a 180 a = temp1 + temp2 181 i = i + 1 182 } 183 _h_set(ctx, 0, _h_get(ctx, 0) + a) 184 _h_set(ctx, 1, _h_get(ctx, 1) + b) 185 _h_set(ctx, 2, _h_get(ctx, 2) + c) 186 _h_set(ctx, 3, _h_get(ctx, 3) + d) 187 _h_set(ctx, 4, _h_get(ctx, 4) + e) 188 _h_set(ctx, 5, _h_get(ctx, 5) + f) 189 _h_set(ctx, 6, _h_get(ctx, 6) + g) 190 _h_set(ctx, 7, _h_get(ctx, 7) + h) 191 return 0 192} 193 194func _sha512_init(ctx: *u8) -> i64 { 195 // SHA-384 IV per FIPS 180-4 §5.3.4 (drives this whole module into 196 // SHA-384 mode; the SHA-512 compression+pad logic below is unchanged). 197 _h_set(ctx, 0, 0xcbbb9d5dc1059ed8) 198 _h_set(ctx, 1, 0x629a292a367cd507) 199 _h_set(ctx, 2, 0x9159015a3070dd17) 200 _h_set(ctx, 3, 0x152fecd8f70e5939) 201 _h_set(ctx, 4, 0x67332667ffc00b31) 202 _h_set(ctx, 5, 0x8eb44a8768581511) 203 _h_set(ctx, 6, 0xdb0c2e0d64f98fa7) 204 _h_set(ctx, 7, 0x47b5481dbefa4fa4) 205 return 0 206} 207 208func _sha512_one_shot(in_ptr: *u8, in_len: i64, ctx: *u8, out_ptr: *u8) -> i64 { 209 _sha512_init(ctx) 210 var idx: i64 = 0 211 var bit_len: i64 = 0 212 var i: i64 = 0 213 while i < in_len { 214 ctx[idx] = in_ptr[i] 215 idx = idx + 1 216 bit_len = bit_len + 8 217 if idx == 128 { 218 _sha512_compress(ctx) 219 idx = 0 220 } 221 i = i + 1 222 } 223 ctx[idx] = 0x80 224 idx = idx + 1 225 if idx > 112 { 226 while idx < 128 { ctx[idx] = 0; idx = idx + 1 } 227 _sha512_compress(ctx) 228 idx = 0 229 } 230 while idx < 112 { ctx[idx] = 0; idx = idx + 1 } 231 ctx[112] = 0; ctx[113] = 0; ctx[114] = 0; ctx[115] = 0 232 ctx[116] = 0; ctx[117] = 0; ctx[118] = 0; ctx[119] = 0 233 ctx[120] = (bit_len >> 56) & 0xFF 234 ctx[121] = (bit_len >> 48) & 0xFF 235 ctx[122] = (bit_len >> 40) & 0xFF 236 ctx[123] = (bit_len >> 32) & 0xFF 237 ctx[124] = (bit_len >> 24) & 0xFF 238 ctx[125] = (bit_len >> 16) & 0xFF 239 ctx[126] = (bit_len >> 8) & 0xFF 240 ctx[127] = bit_len & 0xFF 241 _sha512_compress(ctx) 242 // SHA-384 truncates state to first 6 H-words = 48 bytes. 243 var k: i64 = 0 244 while k < 6 { 245 _be64_write(out_ptr, k * 8, _h_get(ctx, k)) 246 k = k + 1 247 } 248 return 0 249} 250 251// ===== HMAC-SHA-512 ===== 252// 253// scratch layout (>= 2048 bytes): 254// 0..127 : K' (block-sized key buffer) 255// 128..255 : K' XOR ipad 256// 256..383 : K' XOR opad 257// 384..447 : inner-hash output (64 bytes) 258// 512..1431 : SHA-512 ctx scratch (920 bytes; needs 128 + 64 + 8 + 640 = 840) 259// 1536..1727 : opad_block || inner_hash concat for outer (192 bytes) 260func _hmac_sha512(key_ptr: *u8, key_len: i64, 261 msg_ptr: *u8, msg_len: i64, 262 scratch_ptr: *u8, out_ptr: *u8) -> i64 { 263 let kprime: *u8 = scratch_ptr 264 var j: i64 = 0 265 while j < HMAC_B { kprime[j] = 0; j = j + 1 } 266 if key_len > HMAC_B { 267 let h_ctx: *u8 = (scratch_ptr as i64 + 512) as *u8 268 _sha512_one_shot(key_ptr, key_len, h_ctx, kprime) 269 } else { 270 var ki: i64 = 0 271 while ki < key_len { kprime[ki] = key_ptr[ki]; ki = ki + 1 } 272 } 273 let ipad_blk: *u8 = (scratch_ptr as i64 + 128) as *u8 274 var ii: i64 = 0 275 while ii < HMAC_B { ipad_blk[ii] = kprime[ii] ^ 0x36; ii = ii + 1 } 276 let opad_blk: *u8 = (scratch_ptr as i64 + 256) as *u8 277 var oi: i64 = 0 278 while oi < HMAC_B { opad_blk[oi] = kprime[oi] ^ 0x5c; oi = oi + 1 } 279 // Inner hash: streaming SHA-512 of (ipad || msg). 280 let inner_ctx: *u8 = (scratch_ptr as i64 + 512) as *u8 281 _sha512_init(inner_ctx) 282 var inner_idx: i64 = 0 283 var inner_bits: i64 = 0 284 var ip: i64 = 0 285 while ip < HMAC_B { 286 inner_ctx[inner_idx] = ipad_blk[ip] 287 inner_idx = inner_idx + 1 288 inner_bits = inner_bits + 8 289 if inner_idx == 128 { _sha512_compress(inner_ctx); inner_idx = 0 } 290 ip = ip + 1 291 } 292 var mp: i64 = 0 293 while mp < msg_len { 294 inner_ctx[inner_idx] = msg_ptr[mp] 295 inner_idx = inner_idx + 1 296 inner_bits = inner_bits + 8 297 if inner_idx == 128 { _sha512_compress(inner_ctx); inner_idx = 0 } 298 mp = mp + 1 299 } 300 inner_ctx[inner_idx] = 0x80 301 inner_idx = inner_idx + 1 302 if inner_idx > 112 { 303 while inner_idx < 128 { inner_ctx[inner_idx] = 0; inner_idx = inner_idx + 1 } 304 _sha512_compress(inner_ctx) 305 inner_idx = 0 306 } 307 while inner_idx < 112 { inner_ctx[inner_idx] = 0; inner_idx = inner_idx + 1 } 308 inner_ctx[112] = 0; inner_ctx[113] = 0; inner_ctx[114] = 0; inner_ctx[115] = 0 309 inner_ctx[116] = 0; inner_ctx[117] = 0; inner_ctx[118] = 0; inner_ctx[119] = 0 310 inner_ctx[120] = (inner_bits >> 56) & 0xFF 311 inner_ctx[121] = (inner_bits >> 48) & 0xFF 312 inner_ctx[122] = (inner_bits >> 40) & 0xFF 313 inner_ctx[123] = (inner_bits >> 32) & 0xFF 314 inner_ctx[124] = (inner_bits >> 24) & 0xFF 315 inner_ctx[125] = (inner_bits >> 16) & 0xFF 316 inner_ctx[126] = (inner_bits >> 8) & 0xFF 317 inner_ctx[127] = inner_bits & 0xFF 318 _sha512_compress(inner_ctx) 319 // SHA-384 truncates inner state to first 6 H-words = 48 bytes. 320 let inner_out: *u8 = (scratch_ptr as i64 + 384) as *u8 321 var dk: i64 = 0 322 while dk < 6 { 323 _be64_write(inner_out, dk * 8, _h_get(inner_ctx, dk)) 324 dk = dk + 1 325 } 326 // Outer hash: SHA-384(opad_blk || inner_out). _sha512_one_shot already 327 // truncates to 48 bytes (via the same `while k < 6` change above), so 328 // out_ptr receives exactly 48 HMAC-SHA-384 bytes. 329 let outer_in: *u8 = (scratch_ptr as i64 + 1536) as *u8 330 var op: i64 = 0 331 while op < HMAC_B { outer_in[op] = opad_blk[op]; op = op + 1 } 332 var ohi: i64 = 0 333 while ohi < 48 { outer_in[HMAC_B + ohi] = inner_out[ohi]; ohi = ohi + 1 } 334 _sha512_one_shot(outer_in, HMAC_B + 48, inner_ctx, out_ptr) 335 return 0 336} 337 338// ===== HKDF proper ===== 339 340// HKDF-Extract(salt, IKM) = HMAC-SHA-384(salt, IKM) -> 48-byte PRK. 341// If salt_len == 0, use 48 zero bytes as salt per RFC 5869 §2.2. 342// 343// scratch_ptr must be >=2048 bytes (HMAC needs that much). PRK out 344// destination is 48 bytes. 345func nx_hkdf384_extract(salt_ptr: *u8, salt_len: i64, 346 ikm_ptr: *u8, ikm_len: i64, 347 scratch_ptr: *u8, prk_out: *u8) -> i64 { 348 if salt_len == 0 { 349 let zero_salt: *u8 = (scratch_ptr as i64 + 1856) as *u8 350 var z: i64 = 0 351 while z < 48 { zero_salt[z] = 0; z = z + 1 } 352 _hmac_sha512(zero_salt, 48, ikm_ptr, ikm_len, scratch_ptr, prk_out) 353 } else { 354 _hmac_sha512(salt_ptr, salt_len, ikm_ptr, ikm_len, scratch_ptr, prk_out) 355 } 356 return 0 357} 358 359// HKDF-Expand(PRK, info, L) -> first L bytes of T(1) || T(2) || ... 360// T(i) = HMAC-SHA-384(PRK, T(i-1) || info || i_byte) 361// T(0) = empty 362// L MUST be <= 255 * 48 = 12240 bytes. 363// 364// scratch_ptr layout (>=4096 bytes): 365// 0..2047 : HMAC scratch (used by _hmac_sha512 internally; SHA-384 IV) 366// 2048..2095 : T(i-1) buffer (48 B) 367// 2112..2159 : T(i) output buffer (48 B) 368// 2176..3199 : HMAC input buffer (T(i-1) || info || counter; <= 48 + 950 + 1) 369func nx_hkdf384_expand(prk_ptr: *u8, info_ptr: *u8, info_len: i64, 370 okm_len: i64, scratch_ptr: *u8, okm_out: *u8) -> i64 { 371 if okm_len > 12240 { return -1 } 372 let hmac_scratch: *u8 = scratch_ptr 373 let t_prev: *u8 = (scratch_ptr as i64 + 2048) as *u8 374 let t_cur: *u8 = (scratch_ptr as i64 + 2112) as *u8 375 let hmac_in: *u8 = (scratch_ptr as i64 + 2176) as *u8 376 377 let n: i64 = (okm_len + 47) / 48 // number of T blocks (HashLen=48) 378 var written: i64 = 0 379 var i: i64 = 1 380 var prev_len: i64 = 0 381 while i <= n { 382 var p: i64 = 0 383 var pp: i64 = 0 384 while pp < prev_len { hmac_in[p] = t_prev[pp]; p = p + 1; pp = pp + 1 } 385 var ii: i64 = 0 386 while ii < info_len { hmac_in[p] = info_ptr[ii]; p = p + 1; ii = ii + 1 } 387 hmac_in[p] = i & 0xFF 388 p = p + 1 389 390 _hmac_sha512(prk_ptr, 48, hmac_in, p, hmac_scratch, t_cur) 391 392 let need: i64 = okm_len - written 393 var take: i64 = 48 394 if need < 48 { take = need } 395 var ki: i64 = 0 396 while ki < take { 397 okm_out[written + ki] = t_cur[ki] 398 ki = ki + 1 399 } 400 written = written + take 401 402 var ci: i64 = 0 403 while ci < 48 { t_prev[ci] = t_cur[ci]; ci = ci + 1 } 404 prev_len = 48 405 i = i + 1 406 } 407 return 0 408}