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}