1use anyhow::Result;
2use byteorder::{LittleEndian, WriteBytesExt};
3use p256::elliptic_curve::{
4 scalar::FromUintUnchecked,
5 sec1::{FromEncodedPoint, ToEncodedPoint},
6 Curve, Field,
7};
8use std::ops::Mul;
9
10use crate::util::cryptoutil;
11
12pub struct Context {
13 w0: p256::Scalar,
14 w1: p256::Scalar,
15 x_random: p256::Scalar,
16 pub x: p256::EncodedPoint,
17 pub y: p256::EncodedPoint,
18 pub ca: Option<Vec<u8>>,
19 pub decrypt_key: Option<Vec<u8>>,
20 pub encrypt_key: Option<Vec<u8>>,
21}
22
23pub struct Engine {
24 m: p256::AffinePoint,
25 n: p256::AffinePoint,
26}
27
28impl Engine {
29 pub(crate) fn p256_scalar_from_40_bytes(bytes: &[u8]) -> p256::Scalar {
30 let int = crypto_bigint::U320::from_be_slice(bytes);
31 let modulo = int.rem(&crypto_bigint::NonZero::from_uint(
32 crypto_bigint::U320::from(&p256::NistP256::ORDER),
33 ));
34 let u256 = crypto_bigint::U256::from(&modulo);
35 p256::Scalar::from_uint_unchecked(u256)
36 }
37
38 fn encoded_point_to_affine(e: &p256::EncodedPoint) -> Result<p256::AffinePoint> {
39 let res = p256::AffinePoint::from_encoded_point(e).into_option();
40 if let Some(r) = res {
41 Ok(r)
42 } else {
43 Err(anyhow::anyhow!("can't convert point to affine {:?}", e))
44 }
45 }
46 pub(crate) fn encoded_point_to_projective(e: &p256::EncodedPoint) -> Result<p256::ProjectivePoint> {
47 let res = p256::ProjectivePoint::from_encoded_point(e).into_option();
48 if let Some(r) = res {
49 Ok(r)
50 } else {
51 Err(anyhow::anyhow!(format!(
52 "can't convert point to projective {:?}",
53 e
54 )))
55 }
56 }
57
58 pub fn create_passcode_verifier(key: &[u8], salt: &[u8], iterations: u32) -> Vec<u8> {
59 let mut kdf = [0; 80];
60 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(key, salt, iterations, &mut kdf);
61 let w0 = Self::p256_scalar_from_40_bytes(&kdf[..40]);
62 let w1 = Self::p256_scalar_from_40_bytes(&kdf[40..]);
63 let l = p256::ProjectivePoint::GENERATOR.mul(w1);
64 let mut out = Vec::new();
65 out.extend_from_slice(w0.to_bytes().as_slice());
66 out.extend_from_slice(l.to_encoded_point(false).as_bytes());
67 out
68 }
69
70 pub fn start(&self, key: &[u8], salt: &[u8], iterations: u32) -> Result<Context> {
71 let mut kdf = [0; 80];
72 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(key, salt, iterations, &mut kdf);
73
74 let w0_scalar = Self::p256_scalar_from_40_bytes(&kdf.as_slice()[..40]);
75 let w1_scalar = Self::p256_scalar_from_40_bytes(&kdf[40..80]);
76
77 let x_random_scalar = p256::Scalar::random(rand::thread_rng());
78
79 let t_pp = p256::ProjectivePoint::GENERATOR.mul(x_random_scalar);
80
81 let p = self.m.mul(&w0_scalar);
82 let px2 = p.add(&t_pp);
83
84 let px2enc = px2.to_encoded_point(false);
85 Ok(Context {
86 w0: w0_scalar,
87 w1: w1_scalar,
88 x_random: x_random_scalar,
89 x: px2enc,
90 y: p256::EncodedPoint::identity(),
91 ca: None,
92 decrypt_key: None,
93 encrypt_key: None,
94 })
95 }
96
97 pub(crate) fn append_to_tt(buf: &mut Vec<u8>, data: &[u8]) -> Result<()> {
98 buf.write_u64::<LittleEndian>(data.len() as u64)?;
99 buf.extend_from_slice(data);
100 Ok(())
101 }
102
103 pub fn finish(&self, ctx: &mut Context, seed: &[u8], cb_received: &[u8]) -> Result<()> {
104 let wn = self.n.mul(ctx.w0);
105 let wn = wn.neg();
106 let zn = Self::encoded_point_to_projective(&ctx.y)?.add(&wn);
107 let z = zn.mul(ctx.x_random);
108 let v = zn.mul(ctx.w1);
109
110 let result = cryptoutil::sha256(seed);
111
112 let mut tt = Vec::with_capacity(1024);
113 Self::append_to_tt(&mut tt, &result)?;
114 Self::append_to_tt(&mut tt, &[])?;
115 Self::append_to_tt(&mut tt, &[])?;
116 Self::append_to_tt(&mut tt, self.m.to_encoded_point(false).as_bytes())?;
117 Self::append_to_tt(&mut tt, self.n.to_encoded_point(false).as_bytes())?;
118 Self::append_to_tt(&mut tt, ctx.x.as_bytes())?;
119 Self::append_to_tt(&mut tt, ctx.y.as_bytes())?;
120 Self::append_to_tt(&mut tt, z.to_encoded_point(false).as_bytes())?;
121 Self::append_to_tt(&mut tt, v.to_encoded_point(false).as_bytes())?;
122 Self::append_to_tt(&mut tt, ctx.w0.to_bytes().as_slice())?;
123
124 let result = cryptoutil::sha256(&tt);
125 let ka = &result[..16];
126 let ke = &result[16..32];
127
128 let okm = cryptoutil::hkdf_sha256(&[], ka, "ConfirmationKeys".as_bytes(), 32)?;
129
130 ctx.ca = Some(cryptoutil::hmac_sha256(ctx.y.as_bytes(), &okm[..16])?);
131 let cb = cryptoutil::hmac_sha256(ctx.x.as_bytes(), &okm[16..])?;
132 if cb != cb_received {
133 return Err(anyhow::anyhow!("cb value does not match expected value"));
134 }
135
136 let xcrypt = cryptoutil::hkdf_sha256(&[], ke, "SessionKeys".as_bytes(), 16 * 3)?;
137 ctx.decrypt_key = Some(xcrypt[16..32].to_vec());
138 ctx.encrypt_key = Some(xcrypt[..16].to_vec());
139
140 Ok(())
141 }
142
143 pub fn new() -> Result<Self> {
144 let mhex = "02886e2f97ace46e55ba9dd7242579f2993b64e16ef3dcab95afd497333d8fa12f";
145 let mbin = hex::decode(mhex)?;
146 let m = p256::EncodedPoint::from_bytes(mbin)?;
147 let m = Self::encoded_point_to_affine(&m)?;
148
149 let nhex = "03d8bbd6c639c62937b04d997f38c3770719c629d7014d49a24b4f98baa1292b49";
150 let nbin = hex::decode(nhex)?;
151 let n = p256::EncodedPoint::from_bytes(nbin)?;
152 let n = Self::encoded_point_to_affine(&n)?;
153 Ok(Self { m, n })
154 }
155}
156
157pub struct Verifier {
158 w0: p256::Scalar,
159 l: p256::ProjectivePoint,
160 y_random: p256::Scalar,
161 pub y: p256::EncodedPoint,
162 pub x: p256::EncodedPoint,
163 pub decrypt_key: Option<Vec<u8>>,
164 pub encrypt_key: Option<Vec<u8>>,
165 pub attestation_challenge: Option<Vec<u8>>,
166 pub cb: Option<Vec<u8>>,
167 ca_expected: Option<Vec<u8>>,
168}
169
170impl Verifier {
171 pub fn start(key: &[u8], salt: &[u8], iterations: u32, engine: &Engine) -> Result<Self> {
172 let mut kdf = [0; 80];
173 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(key, salt, iterations, &mut kdf);
174
175 let w0 = Engine::p256_scalar_from_40_bytes(&kdf[..40]);
176 let w1 = Engine::p256_scalar_from_40_bytes(&kdf[40..]);
177
178 let l = p256::ProjectivePoint::GENERATOR.mul(w1);
179
180 let y_random = p256::Scalar::random(rand::thread_rng());
181
182 let t_pp = p256::ProjectivePoint::GENERATOR.mul(y_random);
183 let wn = engine.n.mul(&w0);
184 let y_point = wn.add(&t_pp);
185 let y = y_point.to_encoded_point(false);
186
187 Ok(Self {
188 w0,
189 l,
190 y_random,
191 y,
192 x: p256::EncodedPoint::identity(),
193 decrypt_key: None,
194 encrypt_key: None,
195 cb: None,
196 ca_expected: None,
197 attestation_challenge: None,
198 })
199 }
200
201 pub fn start_from_verifier_data(verifier_data: &[u8], engine: &Engine) -> Result<Self> {
202 if verifier_data.len() < 97 {
203 anyhow::bail!("verifier data too short: {} bytes", verifier_data.len());
204 }
205 let w0 = p256::Scalar::from_uint_unchecked(
206 crypto_bigint::U256::from_be_slice(&verifier_data[..32]),
207 );
208 let l_bytes = &verifier_data[32..];
209 let l_point = p256::EncodedPoint::from_bytes(l_bytes)?;
210 let l = Engine::encoded_point_to_projective(&l_point)?;
211
212 let y_random = p256::Scalar::random(rand::thread_rng());
213 let t_pp = p256::ProjectivePoint::GENERATOR.mul(y_random);
214 let wn = engine.n.mul(&w0);
215 let y_point = wn.add(&t_pp);
216 let y = y_point.to_encoded_point(false);
217
218 Ok(Self {
219 w0,
220 l,
221 y_random,
222 y,
223 x: p256::EncodedPoint::identity(),
224 decrypt_key: None,
225 encrypt_key: None,
226 cb: None,
227 ca_expected: None,
228 attestation_challenge: None,
229 })
230 }
231
232 pub fn finish(&mut self, seed: &[u8], engine: &Engine) -> Result<()> {
233 let wm = engine.m.mul(self.w0);
234 let wm = wm.neg();
235 let zn = Engine::encoded_point_to_projective(&self.x)?.add(&wm);
236 let z = zn.mul(self.y_random);
237 let v = self.l.mul(self.y_random);
238
239 let result = cryptoutil::sha256(seed);
240
241 let mut tt = Vec::with_capacity(1024);
242 Engine::append_to_tt(&mut tt, &result)?;
243 Engine::append_to_tt(&mut tt, &[])?;
244 Engine::append_to_tt(&mut tt, &[])?;
245 Engine::append_to_tt(&mut tt, engine.m.to_encoded_point(false).as_bytes())?;
246 Engine::append_to_tt(&mut tt, engine.n.to_encoded_point(false).as_bytes())?;
247 Engine::append_to_tt(&mut tt, self.x.as_bytes())?;
248 Engine::append_to_tt(&mut tt, self.y.as_bytes())?;
249 Engine::append_to_tt(&mut tt, z.to_encoded_point(false).as_bytes())?;
250 Engine::append_to_tt(&mut tt, v.to_encoded_point(false).as_bytes())?;
251 Engine::append_to_tt(&mut tt, self.w0.to_bytes().as_slice())?;
252
253 let result = cryptoutil::sha256(&tt);
254 let ka = &result[..16];
255 let ke = &result[16..32];
256
257 let okm = cryptoutil::hkdf_sha256(&[], ka, "ConfirmationKeys".as_bytes(), 32)?;
258 let cb = cryptoutil::hmac_sha256(self.x.as_bytes(), &okm[16..])?;
259 let ca_expected = cryptoutil::hmac_sha256(self.y.as_bytes(), &okm[..16])?;
260
261 let xcrypt = cryptoutil::hkdf_sha256(&[], ke, "SessionKeys".as_bytes(), 16 * 3)?;
262 let decrypt_key = Some(xcrypt[..16].to_vec());
263 let encrypt_key = Some(xcrypt[16..32].to_vec());
264 self.attestation_challenge = Some(xcrypt[32..].to_vec());
265
266 self.decrypt_key = decrypt_key;
267 self.encrypt_key = encrypt_key;
268 self.cb = Some(cb);
269 self.ca_expected = Some(ca_expected);
270 Ok(())
271 }
272
273 pub fn verify_ca(&self, ca_received: &[u8]) -> Result<()> {
274 match &self.ca_expected {
275 Some(expected) if expected == ca_received => Ok(()),
276 Some(_) => Err(anyhow::anyhow!("ca verification failed")),
277 None => Err(anyhow::anyhow!("finish() must be called before verify_ca()")),
278 }
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn test_spake2p_prover_verifier_keys_match() -> Result<()> {
288 let engine = Engine::new()?;
289
290 let password = b"test_password_123";
291 let salt = b"test_salt";
292 let iterations = 1000;
293 let seed = b"test_seed";
294
295 let mut prover_ctx = engine.start(password, salt, iterations)?;
296 let prover_x = prover_ctx.x;
297
298 let mut verifier = Verifier::start(password, salt, iterations, &engine)?;
299 let verifier_y = verifier.y;
300
301 prover_ctx.y = verifier_y;
302 verifier.x = prover_x;
303
304 verifier.finish(seed, &engine)?;
305 engine.finish(&mut prover_ctx, seed, verifier.cb.as_ref().unwrap())?;
306
307 assert!(prover_ctx.decrypt_key.is_some());
308 assert!(prover_ctx.encrypt_key.is_some());
309 assert!(verifier.decrypt_key.is_some());
310 assert!(verifier.encrypt_key.is_some());
311
312 assert_eq!(
313 prover_ctx.decrypt_key.as_ref().unwrap(),
314 verifier.encrypt_key.as_ref().unwrap(),
315 "Prover decrypt key should match verifier encrypt key"
316 );
317
318 assert_eq!(
319 prover_ctx.encrypt_key.as_ref().unwrap(),
320 verifier.decrypt_key.as_ref().unwrap(),
321 "Prover encrypt key should match verifier decrypt key"
322 );
323
324 Ok(())
325 }
326}