code wiki / hub / nx_opaque_envelope.nx

nx_opaque_envelope.nx source

↩ module page · 242 lines · 10165 B

1// nx_opaque_envelope.nx -- V-MODAUTH-2a: RFC 9807 §4 client credential Envelope (Store / Recover). 2// 3// The Envelope is what the server stores INSTEAD of a password hash. It contains 4// NO encrypted secret at all -- just a nonce + MAC. The client's AKE private key is 5// RE-DERIVED from the randomized password (OPRF output -> KSF stretch -> Extract) 6// at every login; the MAC proves the derivation used the right password against 7// the right server. Server compromise leaks nothing offline-crackable without the 8// separate oprf_seed, and even then each guess costs a full OPRF + KSF evaluation. 9// 10// Deterministic-core convention (per the VOPRF KAT precedent): the *_det functions 11// take ALL randomness (envelope_nonce) as inputs so the RFC C.1.5/C.1.6 vectors can 12// drive them byte-exact; the non-det wrapper draws CSPRNG. KATs gate the _det core; 13// production calls the wrapper. 14// 15// COMPOSES: hub/nx_opaque_core (Expand/DeriveKeyPair/ct_eq), nx_hmac, nx_hkdf, nx_csprng 16// COMPOSED BY: hub/nx_opaque_pake (FinalizeRegistrationRequest / RecoverCredentials) 17// SPEC: RFC 9807 §4.1.2 Store, §4.1.3 Recover, §4 CleartextCredentials 18// license_tier: ORIGINAL 19 20import "nx_syscalls.nx" 21import "nx_csprng.nx" 22import "nx_hmac.nx" 23import "nx_hkdf.nx" 24import "hub/nx_opaque_core.nx" 25 26const NX_OPQ_ENVELOPE_BYTES: i64 = 64 // nonce(32) || auth_tag(32) 27 28// ===== CleartextCredentials MAC input per RFC 9807 §4.1.2 ================================================= 29// 30// concat(envelope_nonce, server_public_key, 31// I2OSP(len(server_identity),2), server_identity, 32// I2OSP(len(client_identity),2), client_identity) 33// identities default to the respective public keys when caller passes 0-ptr. 34// Returns total length written, or negative verdict. 35 36func _opqe_build_mac_input( 37 envelope_nonce_32: *u8, 38 server_pub_33: *u8, 39 client_pub_33: *u8, 40 server_id: *u8, server_id_n: i64, 41 client_id: *u8, client_id_n: i64, 42 out: *u8, out_cap: i64 43) -> i64 { 44 var sid: *u8 = server_id 45 var sid_n: i64 = server_id_n 46 if (server_id as i64) == 0 { sid = server_pub_33; sid_n = NX_OPQ_NPK } 47 var cid: *u8 = client_id 48 var cid_n: i64 = client_id_n 49 if (client_id as i64) == 0 { cid = client_pub_33; cid_n = NX_OPQ_NPK } 50 if sid_n > NX_OPQ_MAX_ID_LEN { return 0 - NX_OPQ_BAD_INPUT } 51 if cid_n > NX_OPQ_MAX_ID_LEN { return 0 - NX_OPQ_BAD_INPUT } 52 53 let total: i64 = NX_OPQ_NN + NX_OPQ_NPK + 2 + sid_n + 2 + cid_n 54 if total > out_cap { return 0 - NX_OPQ_BUF_OVERFLOW } 55 var pos: i64 = 0 56 var i: i64 = 0 57 while i < NX_OPQ_NN { out[pos + i] = envelope_nonce_32[i]; i = i + 1 } 58 pos = pos + NX_OPQ_NN 59 var j: i64 = 0 60 while j < NX_OPQ_NPK { out[pos + j] = server_pub_33[j]; j = j + 1 } 61 pos = pos + NX_OPQ_NPK 62 out[pos] = ((sid_n >> 8) & 0xFF) as u8 63 out[pos + 1] = (sid_n & 0xFF) as u8 64 pos = pos + 2 65 var k: i64 = 0 66 while k < sid_n { out[pos + k] = sid[k]; k = k + 1 } 67 pos = pos + sid_n 68 out[pos] = ((cid_n >> 8) & 0xFF) as u8 69 out[pos + 1] = (cid_n & 0xFF) as u8 70 pos = pos + 2 71 var m: i64 = 0 72 while m < cid_n { out[pos + m] = cid[m]; m = m + 1 } 73 pos = pos + cid_n 74 return pos 75} 76 77// Shared derivation: from randomized_password + envelope_nonce derive 78// auth_key, export_key, and the client AKE keypair (seed -> DeriveDiffieHellmanKeyPair). 79func _opqe_derive_from_rwd( 80 rwd_32: *u8, 81 envelope_nonce_32: *u8, 82 out_auth_key_32: *u8, 83 out_export_key_32: *u8, 84 out_client_sk_32: *u8, 85 out_client_pk_33: *u8 86) -> i64 { 87 let lbl: *u8 = sys_mmap(NX_OPQ_NN + 16) 88 var i: i64 = 0 89 while i < NX_OPQ_NN { lbl[i] = envelope_nonce_32[i]; i = i + 1 } 90 91 // auth_key = Expand(rwd, nonce || "AuthKey", Nh) 92 let ak: *u8 = "AuthKey" as *u8 93 var a: i64 = 0 94 while a < 7 { lbl[NX_OPQ_NN + a] = ak[a]; a = a + 1 } 95 if hkdf_expand(rwd_32, lbl, NX_OPQ_NN + 7, NX_OPQ_NH, out_auth_key_32) != 0 { 96 return 0 - NX_OPQ_DERIVE_FAILED 97 } 98 99 // export_key = Expand(rwd, nonce || "ExportKey", Nh) 100 let ek: *u8 = "ExportKey" as *u8 101 var e: i64 = 0 102 while e < 9 { lbl[NX_OPQ_NN + e] = ek[e]; e = e + 1 } 103 if hkdf_expand(rwd_32, lbl, NX_OPQ_NN + 9, NX_OPQ_NH, out_export_key_32) != 0 { 104 return 0 - NX_OPQ_DERIVE_FAILED 105 } 106 107 // seed = Expand(rwd, nonce || "PrivateKey", Nseed); DeriveDiffieHellmanKeyPair(seed) 108 let pk: *u8 = "PrivateKey" as *u8 109 var p: i64 = 0 110 while p < 10 { lbl[NX_OPQ_NN + p] = pk[p]; p = p + 1 } 111 let seed: *u8 = sys_mmap(NX_OPQ_NSEED) 112 if hkdf_expand(rwd_32, lbl, NX_OPQ_NN + 10, NX_OPQ_NSEED, seed) != 0 { 113 return 0 - NX_OPQ_DERIVE_FAILED 114 } 115 let ddh: *u8 = "OPAQUE-DeriveDiffieHellmanKeyPair" as *u8 116 return nx_opq_derive_keypair(seed, ddh, 33, out_client_sk_32, out_client_pk_33) 117} 118 119// masking_key = Expand(rwd, "MaskingKey", Nh) (nonce-independent per §4.1.2) 120func nx_opq_masking_key(rwd_32: *u8, out_masking_key_32: *u8) -> i64 { 121 let mk: *u8 = "MaskingKey" as *u8 122 if hkdf_expand(rwd_32, mk, 10, NX_OPQ_NH, out_masking_key_32) != 0 { 123 return 0 - NX_OPQ_DERIVE_FAILED 124 } 125 return NX_OPQ_OK 126} 127 128// ===== Store per RFC 9807 §4.1.2 (deterministic core) ================================================= 129 130func nx_opq_envelope_store_det( 131 rwd_32: *u8, 132 server_pub_33: *u8, 133 server_id: *u8, server_id_n: i64, 134 client_id: *u8, client_id_n: i64, 135 envelope_nonce_32: *u8, 136 out_envelope_64: *u8, 137 out_client_pub_33: *u8, 138 out_masking_key_32: *u8, 139 out_export_key_32: *u8 140) -> i64 { 141 if (rwd_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 142 if (server_pub_33 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 143 if (envelope_nonce_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 144 if (out_envelope_64 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 145 if (out_client_pub_33 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 146 if (out_masking_key_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 147 if (out_export_key_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 148 149 let rc_mk: i64 = nx_opq_masking_key(rwd_32, out_masking_key_32) 150 if rc_mk != NX_OPQ_OK { return rc_mk } 151 152 let auth_key: *u8 = sys_mmap(NX_OPQ_NH) 153 let client_sk: *u8 = sys_mmap(NX_OPQ_NSK) 154 let rc_d: i64 = _opqe_derive_from_rwd(rwd_32, envelope_nonce_32, 155 auth_key, out_export_key_32, 156 client_sk, out_client_pub_33) 157 if rc_d != NX_OPQ_OK { return rc_d } 158 159 let mac_in: *u8 = sys_mmap(512) 160 let mac_n: i64 = _opqe_build_mac_input(envelope_nonce_32, server_pub_33, 161 out_client_pub_33, 162 server_id, server_id_n, 163 client_id, client_id_n, 164 mac_in, 512) 165 if mac_n < 0 { return mac_n } 166 167 var i: i64 = 0 168 while i < NX_OPQ_NN { out_envelope_64[i] = envelope_nonce_32[i]; i = i + 1 } 169 hmac_sha256(auth_key, NX_OPQ_NH, mac_in, mac_n, 170 (out_envelope_64 as i64 + NX_OPQ_NN) as *u8) 171 return NX_OPQ_OK 172} 173 174// Production wrapper: draws the envelope nonce from CSPRNG. 175func nx_opq_envelope_store( 176 rwd_32: *u8, 177 server_pub_33: *u8, 178 server_id: *u8, server_id_n: i64, 179 client_id: *u8, client_id_n: i64, 180 out_envelope_64: *u8, 181 out_client_pub_33: *u8, 182 out_masking_key_32: *u8, 183 out_export_key_32: *u8 184) -> i64 { 185 let nonce: *u8 = sys_mmap(NX_OPQ_NN) 186 if nx_csprng_fill(nonce, NX_OPQ_NN) != 0 { return 0 - NX_OPQ_CSPRNG_FAILED } 187 return nx_opq_envelope_store_det(rwd_32, server_pub_33, 188 server_id, server_id_n, 189 client_id, client_id_n, 190 nonce, out_envelope_64, out_client_pub_33, 191 out_masking_key_32, out_export_key_32) 192} 193 194// ===== Recover per RFC 9807 §4.1.3 ================================================= 195// 196// Wrong password => derived auth_key differs => auth_tag mismatch => ENVELOPE_AUTH_FAIL. 197// On failure all derived outputs are zeroed (RFC: intermediary values MUST be deleted). 198 199func nx_opq_envelope_recover( 200 rwd_32: *u8, 201 server_pub_33: *u8, 202 envelope_64: *u8, 203 server_id: *u8, server_id_n: i64, 204 client_id: *u8, client_id_n: i64, 205 out_client_sk_32: *u8, 206 out_client_pub_33: *u8, 207 out_export_key_32: *u8 208) -> i64 { 209 if (rwd_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 210 if (server_pub_33 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 211 if (envelope_64 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 212 if (out_client_sk_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 213 if (out_client_pub_33 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 214 if (out_export_key_32 as i64) == 0 { return 0 - NX_OPQ_BAD_INPUT } 215 216 let auth_key: *u8 = sys_mmap(NX_OPQ_NH) 217 let rc_d: i64 = _opqe_derive_from_rwd(rwd_32, envelope_64, 218 auth_key, out_export_key_32, 219 out_client_sk_32, out_client_pub_33) 220 if rc_d != NX_OPQ_OK { return rc_d } 221 222 let mac_in: *u8 = sys_mmap(512) 223 let mac_n: i64 = _opqe_build_mac_input(envelope_64, server_pub_33, 224 out_client_pub_33, 225 server_id, server_id_n, 226 client_id, client_id_n, 227 mac_in, 512) 228 if mac_n < 0 { return mac_n } 229 230 let expected: *u8 = sys_mmap(NX_OPQ_NM) 231 hmac_sha256(auth_key, NX_OPQ_NH, mac_in, mac_n, expected) 232 if nx_opq_ct_eq((envelope_64 as i64 + NX_OPQ_NN) as *u8, expected, NX_OPQ_NM) != 1 { 233 var z: i64 = 0 234 while z < NX_OPQ_NSK { out_client_sk_32[z] = 0 as u8; z = z + 1 } 235 var z2: i64 = 0 236 while z2 < NX_OPQ_NH { out_export_key_32[z2] = 0 as u8; z2 = z2 + 1 } 237 var z3: i64 = 0 238 while z3 < NX_OPQ_NPK { out_client_pub_33[z3] = 0 as u8; z3 = z3 + 1 } 239 return 0 - NX_OPQ_ENVELOPE_AUTH_FAIL 240 } 241 return NX_OPQ_OK 242}