1use crate::{
2 errors::{Error, Result},
3 utils::{
4 H0, compute_authenticator_messages, compute_first_session_key, compute_session_key,
5 compute_ssid, generate_keypair, generate_nonce, scalar_from_hash,
6 },
7};
8
9use crate::constants::MIN_SSID_LEN;
10use core::marker::PhantomData;
11use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
12use curve25519_dalek::traits::IsIdentity;
13use curve25519_dalek::{
14 digest::consts::U64,
15 digest::{Digest, Output},
16 ristretto::RistrettoPoint,
17 scalar::Scalar,
18};
19use password_hash::{ParamsString, PasswordHash, PasswordHasher, Salt, SaltString};
20use rand_core::{TryCryptoRng, TryRngCore};
21use subtle::ConstantTimeEq;
22
23#[cfg(feature = "strong_aucpace")]
24use crate::utils::H1;
25
26#[cfg(feature = "alloc")]
27extern crate alloc;
28
29#[cfg(feature = "serde")]
30use crate::utils::{serde_paramsstring, serde_saltstring};
31
32#[cfg(feature = "serde")]
33use serde::{Deserialize, Serialize};
34
35pub struct AuCPaceClient<D, H, CSPRNG, const K1: usize>
37where
38 D: Digest<OutputSize = U64> + Default,
39 H: PasswordHasher,
40 CSPRNG: TryRngCore + TryCryptoRng,
41{
42 rng: CSPRNG,
43 d: PhantomData<D>,
44 h: PhantomData<H>,
45}
46
47impl<D, H, CSPRNG, const K1: usize> AuCPaceClient<D, H, CSPRNG, K1>
48where
49 D: Digest<OutputSize = U64> + Default,
50 H: PasswordHasher,
51 CSPRNG: TryRngCore + TryCryptoRng,
52{
53 pub const fn new(rng: CSPRNG) -> Self {
55 Self {
56 rng,
57 d: PhantomData,
58 h: PhantomData,
59 }
60 }
61
62 pub fn begin(
70 &mut self,
71 ) -> Result<(AuCPaceClientSsidEstablish<D, H, K1>, ClientMessage<'_, K1>)> {
72 let next_step = AuCPaceClientSsidEstablish::new(&mut self.rng)?;
73 let message = ClientMessage::Nonce(next_step.nonce);
74
75 Ok((next_step, message))
76 }
77
78 pub fn begin_prestablished_ssid<S>(&mut self, ssid: S) -> Result<AuCPaceClientPreAug<D, H, K1>>
88 where
89 S: AsRef<[u8]>,
90 {
91 if ssid.as_ref().len() < MIN_SSID_LEN {
93 return Err(Error::InsecureSsid);
94 }
95
96 let mut hasher: D = H0();
98 hasher.update(ssid);
99 let ssid_hash = hasher.finalize();
100 let next_step = AuCPaceClientPreAug::new(ssid_hash);
101 Ok(next_step)
102 }
103
104 pub fn register<'a, P, const BUFSIZ: usize>(
123 &mut self,
124 username: &'a [u8],
125 password: P,
126 params: H::Params,
127 hasher: H,
128 ) -> Result<ClientMessage<'a, K1>>
129 where
130 P: AsRef<[u8]>,
131 {
132 let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH];
133 self.rng
134 .try_fill_bytes(&mut bytes)
135 .map_err(|_| Error::Rng)?;
136 let salt_string = SaltString::encode_b64(&bytes).expect("Salt length invariant broken.");
137
138 let pw_hash = hash_password::<&[u8], P, &SaltString, H, BUFSIZ>(
140 username,
141 password,
142 &salt_string,
143 params.clone(),
144 &hasher,
145 )?;
146
147 let cofactor = Scalar::ONE;
148 let w = scalar_from_hash(&pw_hash)?;
149 let verifier = RISTRETTO_BASEPOINT_POINT * (w * cofactor);
150
151 let params_string = params.try_into().map_err(Error::PasswordHashing)?;
153
154 Ok(ClientMessage::Registration {
155 username,
156 salt: salt_string,
157 params: params_string,
158 verifier,
159 })
160 }
161
162 #[cfg(feature = "strong_aucpace")]
181 pub fn register_strong<'a, P, const BUFSIZ: usize>(
182 &mut self,
183 username: &'a [u8],
184 password: P,
185 params: H::Params,
186 hasher: H,
187 ) -> Result<ClientMessage<'a, K1>>
188 where
189 P: AsRef<[u8]>,
190 {
191 let (q, salt_string) =
193 Self::generate_salt_strong(username, password.as_ref(), &mut self.rng)?;
194
195 let pw_hash = hash_password::<&[u8], P, &SaltString, H, BUFSIZ>(
197 username,
198 password,
199 &salt_string,
200 params.clone(),
201 &hasher,
202 )?;
203 let cofactor = Scalar::ONE;
204 let w = scalar_from_hash(&pw_hash)?;
205 let verifier = RISTRETTO_BASEPOINT_POINT * (w * cofactor);
206
207 let params_string = params.try_into().map_err(Error::PasswordHashing)?;
209
210 Ok(ClientMessage::StrongRegistration {
211 username,
212 secret_exponent: q,
213 params: params_string,
214 verifier,
215 })
216 }
217
218 #[cfg(feature = "alloc")]
234 pub fn register_alloc<'a, P>(
235 &mut self,
236 username: &'a [u8],
237 password: P,
238 params: H::Params,
239 hasher: H,
240 ) -> Result<ClientMessage<'a, K1>>
241 where
242 P: AsRef<[u8]>,
243 {
244 let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH];
246 self.rng
247 .try_fill_bytes(&mut bytes)
248 .map_err(|_| Error::Rng)?;
249 let salt_string = SaltString::encode_b64(&bytes).expect("Salt length invariant broken.");
250
251 let pw_hash =
253 hash_password_alloc(username, password, &salt_string, params.clone(), &hasher)?;
254 let cofactor = Scalar::ONE;
255 let w = scalar_from_hash(&pw_hash)?;
256 let verifier = RISTRETTO_BASEPOINT_POINT * (w * cofactor);
257
258 let params_string = params.try_into().map_err(Error::PasswordHashing)?;
260
261 Ok(ClientMessage::Registration {
262 username,
263 salt: salt_string,
264 params: params_string,
265 verifier,
266 })
267 }
268
269 #[cfg(all(feature = "strong_aucpace", feature = "alloc"))]
290 pub fn register_alloc_strong<'a, P>(
291 &mut self,
292 username: &'a [u8],
293 password: P,
294 params: H::Params,
295 hasher: H,
296 ) -> Result<ClientMessage<'a, K1>>
297 where
298 P: AsRef<[u8]>,
299 {
300 let (q, salt_string) =
302 Self::generate_salt_strong(username, password.as_ref(), &mut self.rng)?;
303
304 let pw_hash = hash_password_alloc(
306 username,
307 password,
308 salt_string.as_salt(),
309 params.clone(),
310 &hasher,
311 )?;
312 let cofactor = Scalar::ONE;
313 let w = scalar_from_hash(&pw_hash)?;
314 let verifier = RISTRETTO_BASEPOINT_POINT * (w * cofactor);
315
316 let params_string = params.try_into().map_err(Error::PasswordHashing)?;
318
319 Ok(ClientMessage::StrongRegistration {
320 username,
321 secret_exponent: q,
322 params: params_string,
323 verifier,
324 })
325 }
326
327 #[cfg(feature = "strong_aucpace")]
329 fn generate_salt_strong(
330 user: &[u8],
331 pass: &[u8],
332 rng: &mut CSPRNG,
333 ) -> Result<(Scalar, SaltString)> {
334 let mut rand_bytes = [0u8; 64];
336 rng.try_fill_bytes(&mut rand_bytes)
337 .map_err(|_| Error::Rng)?;
338 let mut hasher_q: D = H1();
339 hasher_q.update(&rand_bytes);
340 let q = Scalar::from_hash(hasher_q);
341
342 let mut hasher: D = H1();
344 hasher.update(user);
345 hasher.update(pass);
346 let z = RistrettoPoint::from_hash(hasher);
347
348 let cofactor = Scalar::ONE;
350 let salt_point = z * (q * cofactor);
351 let salt = salt_point.compress().to_bytes();
352 let salt_string = SaltString::encode_b64(&salt).map_err(Error::PasswordHashing)?;
353
354 Ok((q, salt_string))
355 }
356}
357
358pub struct AuCPaceClientSsidEstablish<D, H, const K1: usize>
360where
361 D: Digest<OutputSize = U64> + Default,
362 H: PasswordHasher,
363{
364 nonce: [u8; K1],
365 d: PhantomData<D>,
366 h: PhantomData<H>,
367}
368
369impl<D, H, const K1: usize> AuCPaceClientSsidEstablish<D, H, K1>
370where
371 D: Digest<OutputSize = U64> + Default,
372 H: PasswordHasher,
373{
374 fn new<CSPRNG>(rng: &mut CSPRNG) -> Result<Self>
375 where
376 CSPRNG: TryRngCore + TryCryptoRng,
377 {
378 Ok(Self {
379 nonce: generate_nonce(rng)?,
380 d: PhantomData,
381 h: PhantomData,
382 })
383 }
384
385 #[must_use]
394 pub fn agree_ssid(self, server_nonce: [u8; K1]) -> AuCPaceClientPreAug<D, H, K1> {
395 let ssid = compute_ssid::<D, K1>(server_nonce, self.nonce);
396 AuCPaceClientPreAug::new(ssid)
397 }
398}
399
400pub struct AuCPaceClientPreAug<D, H, const K1: usize>
402where
403 D: Digest<OutputSize = U64> + Default,
404 H: PasswordHasher,
405{
406 ssid: Output<D>,
407 h: PhantomData<H>,
408}
409
410impl<D, H, const K1: usize> AuCPaceClientPreAug<D, H, K1>
411where
412 D: Digest<OutputSize = U64> + Default,
413 H: PasswordHasher,
414{
415 const fn new(ssid: Output<D>) -> Self {
416 Self {
417 ssid,
418 h: PhantomData,
419 }
420 }
421
422 #[must_use]
433 pub const fn start_augmentation<'a>(
434 self,
435 username: &'a [u8],
436 password: &'a [u8],
437 ) -> (AuCPaceClientAugLayer<'a, D, H, K1>, ClientMessage<'a, K1>) {
438 let next_step = AuCPaceClientAugLayer::new(self.ssid, username, password);
439 let message = ClientMessage::Username(username);
440
441 (next_step, message)
442 }
443
444 #[cfg(feature = "strong_aucpace")]
456 pub fn start_augmentation_strong<'a, CSPRNG>(
457 self,
458 username: &'a [u8],
459 password: &'a [u8],
460 rng: &mut CSPRNG,
461 ) -> Result<(
462 StrongAuCPaceClientAugLayer<'a, D, H, K1>,
463 ClientMessage<'a, K1>,
464 )>
465 where
466 CSPRNG: TryRngCore + TryCryptoRng,
467 {
468 let blinding_value = loop {
471 let mut rand_bytes = [0u8; 64];
472 rng.try_fill_bytes(&mut rand_bytes)
473 .map_err(|_| Error::Rng)?;
474 let mut hasher_bv: D = H1();
475 hasher_bv.update(&rand_bytes);
476 let val = Scalar::from_hash(hasher_bv);
477 if val != Scalar::ZERO {
478 break val;
479 }
480 };
481 let mut hasher: D = H1();
482 hasher.update(username);
483 hasher.update(password);
484 let z = RistrettoPoint::from_hash(hasher);
485 let cofactor = Scalar::ONE;
486 let blinded = z * (blinding_value * cofactor);
487
488 let next_step =
489 StrongAuCPaceClientAugLayer::new(self.ssid, username, password, blinding_value);
490 let message = ClientMessage::StrongUsername { username, blinded };
491
492 Ok((next_step, message))
493 }
494}
495
496pub struct AuCPaceClientAugLayer<'a, D, H, const K1: usize>
498where
499 D: Digest<OutputSize = U64> + Default,
500 H: PasswordHasher,
501{
502 ssid: Output<D>,
503 username: &'a [u8],
504 password: &'a [u8],
505 h: PhantomData<H>,
506}
507
508impl<'a, D, H, const K1: usize> AuCPaceClientAugLayer<'a, D, H, K1>
509where
510 D: Digest<OutputSize = U64> + Default,
511 H: PasswordHasher,
512{
513 const fn new(ssid: Output<D>, username: &'a [u8], password: &'a [u8]) -> Self {
514 Self {
515 ssid,
516 username,
517 password,
518 h: PhantomData,
519 }
520 }
521
522 pub fn generate_cpace<'salt, S, const BUFSIZ: usize>(
545 self,
546 x_pub: RistrettoPoint,
547 salt: S,
548 params: H::Params,
549 hasher: H,
550 ) -> Result<AuCPaceClientCPaceSubstep<D, K1>>
551 where
552 S: Into<Salt<'salt>>,
553 {
554 if x_pub.is_identity() {
556 return Err(Error::IllegalPointError);
557 }
558
559 let cofactor = Scalar::ONE;
560 let pw_hash = hash_password::<&[u8], &[u8], S, H, BUFSIZ>(
561 self.username,
562 self.password,
563 salt,
564 params,
565 &hasher,
566 )?;
567 let w = scalar_from_hash(&pw_hash)?;
568
569 let prs = (x_pub * (w * cofactor)).compress().to_bytes();
570
571 Ok(AuCPaceClientCPaceSubstep::new(self.ssid, prs))
572 }
573
574 #[cfg(feature = "alloc")]
592 pub fn generate_cpace_alloc<'salt, S>(
593 self,
594 x_pub: RistrettoPoint,
595 salt: S,
596 params: H::Params,
597 hasher: H,
598 ) -> Result<AuCPaceClientCPaceSubstep<D, K1>>
599 where
600 S: Into<Salt<'a>>,
601 {
602 if x_pub.is_identity() {
604 return Err(Error::IllegalPointError);
605 }
606
607 let cofactor = Scalar::ONE;
608 let pw_hash = hash_password_alloc(self.username, self.password, salt, params, &hasher)?;
609 let w = scalar_from_hash(&pw_hash)?;
610
611 let prs = (x_pub * (w * cofactor)).compress().to_bytes();
612
613 Ok(AuCPaceClientCPaceSubstep::new(self.ssid, prs))
614 }
615}
616
617#[cfg(feature = "strong_aucpace")]
619pub struct StrongAuCPaceClientAugLayer<'a, D, H, const K1: usize>
620where
621 D: Digest<OutputSize = U64> + Default,
622 H: PasswordHasher,
623{
624 ssid: Output<D>,
625 username: &'a [u8],
626 password: &'a [u8],
627 blinding_value: Scalar,
628 h: PhantomData<H>,
629}
630
631#[cfg(feature = "strong_aucpace")]
632impl<'a, D, H, const K1: usize> StrongAuCPaceClientAugLayer<'a, D, H, K1>
633where
634 D: Digest<OutputSize = U64> + Default,
635 H: PasswordHasher,
636{
637 const fn new(
638 ssid: Output<D>,
639 username: &'a [u8],
640 password: &'a [u8],
641 blinding_value: Scalar,
642 ) -> Self {
643 Self {
644 ssid,
645 username,
646 password,
647 blinding_value,
648 h: PhantomData,
649 }
650 }
651
652 pub fn generate_cpace<const BUFSIZ: usize>(
675 self,
676 x_pub: RistrettoPoint,
677 blinded_salt: RistrettoPoint,
678 params: H::Params,
679 hasher: H,
680 ) -> Result<AuCPaceClientCPaceSubstep<D, K1>> {
681 if x_pub.is_identity() {
683 return Err(Error::IllegalPointError);
684 }
685
686 let cofactor = Scalar::ONE;
688
689 let exponent = (self.blinding_value * cofactor * cofactor).invert() * cofactor;
693 let salt = (blinded_salt * exponent).compress().to_bytes();
694 let salt_string = SaltString::encode_b64(&salt).map_err(Error::PasswordHashing)?;
695
696 let pw_hash = hash_password::<&[u8], &[u8], &SaltString, H, BUFSIZ>(
698 self.username,
699 self.password,
700 &salt_string,
701 params,
702 &hasher,
703 )?;
704 let w = scalar_from_hash(&pw_hash)?;
705 let prs = (x_pub * (w * cofactor)).compress().to_bytes();
706
707 Ok(AuCPaceClientCPaceSubstep::new(self.ssid, prs))
708 }
709
710 #[cfg(feature = "alloc")]
729 pub fn generate_cpace_alloc(
730 self,
731 x_pub: RistrettoPoint,
732 blinded_salt: RistrettoPoint,
733 params: H::Params,
734 hasher: H,
735 ) -> Result<AuCPaceClientCPaceSubstep<D, K1>> {
736 if x_pub.is_identity() {
738 return Err(Error::IllegalPointError);
739 }
740
741 let cofactor = Scalar::ONE;
743
744 let exponent = (self.blinding_value * cofactor * cofactor).invert() * cofactor;
748
749 let salt_point = blinded_salt * exponent;
751 if salt_point.is_identity() {
752 return Err(Error::IllegalPointError);
753 }
754 let salt = salt_point.compress().to_bytes();
755 let salt_string = SaltString::encode_b64(&salt).map_err(Error::PasswordHashing)?;
756
757 let pw_hash = hash_password_alloc(
759 self.username,
760 self.password,
761 salt_string.as_salt(),
762 params,
763 &hasher,
764 )?;
765 let w = scalar_from_hash(&pw_hash)?;
766 let prs = (x_pub * (w * cofactor)).compress().to_bytes();
767
768 Ok(AuCPaceClientCPaceSubstep::new(self.ssid, prs))
769 }
770}
771
772#[derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
774pub struct AuCPaceClientCPaceSubstep<D, const K1: usize>
775where
776 D: Digest<OutputSize = U64> + Default,
777{
778 #[zeroize(skip)]
779 ssid: Output<D>,
780 prs: [u8; 32],
781}
782
783impl<D, const K1: usize> AuCPaceClientCPaceSubstep<D, K1>
784where
785 D: Digest<OutputSize = U64> + Default,
786{
787 const fn new(ssid: Output<D>, prs: [u8; 32]) -> Self {
788 Self { ssid, prs }
789 }
790
791 pub fn generate_public_key<CI, CSPRNG>(
806 self,
807 channel_identifier: CI,
808 rng: &mut CSPRNG,
809 ) -> Result<(
810 AuCPaceClientRecvServerKey<D, K1>,
811 ClientMessage<'static, K1>,
812 )>
813 where
814 CI: AsRef<[u8]>,
815 CSPRNG: TryRngCore + TryCryptoRng,
816 {
817 let (priv_key, pub_key) =
818 generate_keypair::<D, CSPRNG, CI>(rng, self.ssid, self.prs, channel_identifier)?;
819
820 let next_step = AuCPaceClientRecvServerKey::new(self.ssid, priv_key);
821 let message = ClientMessage::PublicKey(pub_key);
822
823 Ok((next_step, message))
824 }
825}
826
827#[derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
829pub struct AuCPaceClientRecvServerKey<D, const K1: usize>
830where
831 D: Digest<OutputSize = U64> + Default,
832{
833 #[zeroize(skip)]
834 ssid: Output<D>,
835 priv_key: Scalar,
836}
837
838impl<D, const K1: usize> AuCPaceClientRecvServerKey<D, K1>
839where
840 D: Digest<OutputSize = U64> + Default,
841{
842 const fn new(ssid: Output<D>, priv_key: Scalar) -> Self {
843 Self { ssid, priv_key }
844 }
845
846 pub fn receive_server_pubkey(
858 self,
859 server_pubkey: RistrettoPoint,
860 ) -> Result<(AuCPaceClientExpMutAuth<D, K1>, ClientMessage<'static, K1>)> {
861 if server_pubkey.is_identity() {
862 return Err(Error::IllegalPointError);
863 }
864
865 let sk1 = compute_first_session_key::<D>(self.ssid, self.priv_key, server_pubkey);
866 let (ta, tb) = compute_authenticator_messages::<D>(self.ssid, sk1);
867 let next_step = AuCPaceClientExpMutAuth::new(self.ssid, sk1, ta);
868 let tb_arr = tb
869 .as_slice()
870 .try_into()
871 .map_err(|_| Error::HashSizeInvalid)?;
872 let message = ClientMessage::Authenticator(tb_arr);
873 Ok((next_step, message))
874 }
875
876 pub fn implicit_auth(
887 self,
888 server_pubkey: RistrettoPoint,
889 ) -> Result<secret_utils::wrappers::SecretKey> {
890 if server_pubkey.is_identity() {
891 return Err(Error::IllegalPointError);
892 }
893
894 let sk1 = compute_first_session_key::<D>(self.ssid, self.priv_key, server_pubkey);
895 Ok(secret_utils::wrappers::SecretKey::from(
896 compute_session_key::<D>(self.ssid, sk1).as_slice().to_vec(),
897 ))
898 }
899}
900
901#[derive(zeroize::Zeroize, zeroize::ZeroizeOnDrop)]
903pub struct AuCPaceClientExpMutAuth<D, const K1: usize>
904where
905 D: Digest<OutputSize = U64> + Default,
906{
907 #[zeroize(skip)]
908 ssid: Output<D>,
909 #[zeroize(skip)]
910 sk1: Output<D>,
911 #[zeroize(skip)]
912 server_authenticator: Output<D>,
913}
914
915impl<D, const K1: usize> AuCPaceClientExpMutAuth<D, K1>
916where
917 D: Digest<OutputSize = U64> + Default,
918{
919 const fn new(ssid: Output<D>, sk1: Output<D>, server_authenticator: Output<D>) -> Self {
920 Self {
921 ssid,
922 sk1,
923 server_authenticator,
924 }
925 }
926
927 pub fn receive_server_authenticator(
940 self,
941 server_authenticator: [u8; 64],
942 ) -> Result<secret_utils::wrappers::SecretKey> {
943 if self
944 .server_authenticator
945 .ct_eq(&server_authenticator)
946 .into()
947 {
948 Ok(secret_utils::wrappers::SecretKey::from(
949 compute_session_key::<D>(self.ssid, self.sk1)
950 .as_slice()
951 .to_vec(),
952 ))
953 } else {
954 Err(Error::MutualAuthFail)
955 }
956 }
957}
958
959fn hash_password<'a, U, P, S, H, const BUFSIZ: usize>(
961 username: U,
962 password: P,
963 salt: S,
964 params: H::Params,
965 hasher: &H,
966) -> Result<PasswordHash<'a>>
967where
968 H: PasswordHasher,
969 U: AsRef<[u8]>,
970 P: AsRef<[u8]>,
971 S: Into<Salt<'a>>,
972{
973 let user = username.as_ref();
974 let pass = password.as_ref();
975 let u = user.len();
976 let p = pass.len();
977
978 if u + p + 1 > BUFSIZ {
979 return Err(Error::UsernameOrPasswordTooLong);
980 }
981
982 let mut buf = [0u8; BUFSIZ];
983 buf[0..u].copy_from_slice(user);
984 buf[u] = b':';
985 buf[(u + 1)..=(u + p)].copy_from_slice(pass);
986
987 let hash = hasher
988 .hash_password_customized(&buf[0..=(u + p)], None, None, params, salt)
989 .map_err(Error::PasswordHashing);
990
991 hash
992}
993
994#[cfg(feature = "alloc")]
996fn hash_password_alloc<'a, U, P, S, H>(
997 username: U,
998 password: P,
999 salt: S,
1000 params: H::Params,
1001 hasher: &H,
1002) -> Result<PasswordHash<'a>>
1003where
1004 H: PasswordHasher,
1005 U: AsRef<[u8]>,
1006 P: AsRef<[u8]>,
1007 S: Into<Salt<'a>>,
1008{
1009 let user = username.as_ref();
1010 let pass = password.as_ref();
1011
1012 let mut v = alloc::vec::Vec::with_capacity(user.len() + pass.len() + 1);
1014 v.extend_from_slice(user);
1015 v.push(b':');
1016 v.extend_from_slice(pass);
1017
1018 hasher
1019 .hash_password_customized(v.as_slice(), None, None, params, salt)
1020 .map_err(Error::PasswordHashing)
1021}
1022
1023#[derive(Debug)]
1025#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1026pub enum ClientMessage<'a, const K1: usize> {
1027 Nonce(#[cfg_attr(feature = "serde", serde(with = "serde_byte_array"))] [u8; K1]),
1029
1030 Username(&'a [u8]),
1032
1033 #[cfg(feature = "strong_aucpace")]
1036 StrongUsername {
1037 username: &'a [u8],
1039 blinded: RistrettoPoint,
1041 },
1042
1043 PublicKey(RistrettoPoint),
1045
1046 Authenticator(#[cfg_attr(feature = "serde", serde(with = "serde_byte_array"))] [u8; 64]),
1048
1049 Registration {
1052 username: &'a [u8],
1054
1055 #[cfg_attr(feature = "serde", serde(with = "serde_saltstring"))]
1057 salt: SaltString,
1058
1059 #[cfg_attr(feature = "serde", serde(with = "serde_paramsstring"))]
1061 params: ParamsString,
1062
1063 verifier: RistrettoPoint,
1065 },
1066
1067 #[cfg(feature = "strong_aucpace")]
1070 StrongRegistration {
1071 username: &'a [u8],
1073
1074 secret_exponent: Scalar,
1076
1077 #[cfg_attr(feature = "serde", serde(with = "serde_paramsstring"))]
1079 params: ParamsString,
1080
1081 verifier: RistrettoPoint,
1083 },
1084}
1085
1086#[cfg(test)]
1087mod tests {
1088 #[allow(unused)]
1089 use super::*;
1090
1091 #[test]
1092 #[cfg(all(feature = "alloc", feature = "getrandom", feature = "scrypt"))]
1093 fn test_hash_password_no_std_and_alloc_agree() {
1094 use rand::rngs::OsRng;
1095 use rand_core::TryRngCore;
1096 use scrypt::{Params, Scrypt};
1097
1098 let username = "worf@starship.enterprise";
1099 let password = "data_x_worf_4ever_<3";
1100 let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH];
1101 OsRng.try_fill_bytes(&mut bytes).unwrap();
1102 let salt = SaltString::encode_b64(&bytes).expect("Salt length invariant broken.");
1103 let params: Params = Default::default();
1106
1107 let no_std_res = hash_password::<&str, &str, &SaltString, Scrypt, 100>(
1108 username, password, &salt, params, &Scrypt,
1109 )
1110 .unwrap();
1111 let alloc_res = hash_password_alloc(username, password, &salt, params, &Scrypt).unwrap();
1112
1113 assert_eq!(alloc_res, no_std_res);
1114 }
1115
1116 #[test]
1117 #[cfg(all(feature = "getrandom", feature = "sha2"))]
1118 fn test_client_doesnt_accept_insecure_ssid() {
1119 use crate::Client;
1120 use rand::rngs::OsRng;
1121
1122 let mut client = Client::new(OsRng);
1123 let res = client.begin_prestablished_ssid("bad ssid");
1124 assert!(matches!(res, Err(Error::InsecureSsid)));
1125 }
1126
1127 #[test]
1128 #[cfg(all(feature = "sha2", feature = "scrypt"))]
1129 fn test_client_doesnt_accept_invalid_x_pub() {
1130 use crate::utils::H0;
1131 use curve25519_dalek::traits::Identity;
1132 let ssid = H0::<sha2::Sha512>().finalize();
1133 let aug_client: AuCPaceClientAugLayer<'_, sha2::Sha512, scrypt::Scrypt, 16> =
1134 AuCPaceClientAugLayer::new(
1135 ssid,
1136 b"bob",
1137 b"bob's very secure password that nobody knows about, honest",
1138 );
1139 let res = aug_client.generate_cpace::<'_, &SaltString, 100>(
1140 RistrettoPoint::identity(),
1141 &SaltString::encode_b64(b"saltyboi").unwrap(),
1142 scrypt::Params::recommended(),
1143 scrypt::Scrypt,
1144 );
1145
1146 if let Err(e) = res {
1147 assert_eq!(e, Error::IllegalPointError);
1148 } else {
1149 panic!("Client accepted illegal point.");
1150 }
1151 }
1152
1153 #[test]
1154 #[cfg(all(feature = "sha2", feature = "scrypt", feature = "alloc"))]
1155 fn test_alloc_client_doesnt_accept_invalid_x_pub() {
1156 use crate::utils::H0;
1157 use curve25519_dalek::traits::Identity;
1158 let ssid = H0::<sha2::Sha512>().finalize();
1159 let aug_client: AuCPaceClientAugLayer<'_, sha2::Sha512, scrypt::Scrypt, 16> =
1160 AuCPaceClientAugLayer::new(
1161 ssid,
1162 b"bob",
1163 b"bob's very secure password that nobody knows about, honest",
1164 );
1165 let res = aug_client.generate_cpace_alloc(
1166 RistrettoPoint::identity(),
1167 &SaltString::encode_b64(b"saltyboi").unwrap(),
1168 scrypt::Params::recommended(),
1169 scrypt::Scrypt,
1170 );
1171
1172 if let Err(e) = res {
1173 assert_eq!(e, Error::IllegalPointError);
1174 } else {
1175 panic!("Client accepted illegal point.");
1176 }
1177 }
1178
1179 #[test]
1180 #[cfg(all(feature = "sha2", feature = "scrypt", feature = "strong_aucpace"))]
1181 fn test_strong_client_doesnt_accept_invalid_x_pub() {
1182 use crate::utils::H0;
1183 use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
1184 use curve25519_dalek::traits::Identity;
1185
1186 let ssid = H0::<sha2::Sha512>().finalize();
1187 let aug_client: StrongAuCPaceClientAugLayer<'_, sha2::Sha512, scrypt::Scrypt, 16> =
1188 StrongAuCPaceClientAugLayer::new(
1189 ssid,
1190 b"bob",
1191 b"bob's very secure password that nobody knows about, honest",
1192 Scalar::from(69u32),
1193 );
1194 let res = aug_client.generate_cpace::<100>(
1195 RistrettoPoint::identity(),
1196 RISTRETTO_BASEPOINT_POINT,
1197 scrypt::Params::recommended(),
1198 scrypt::Scrypt,
1199 );
1200
1201 if let Err(e) = res {
1202 assert_eq!(e, Error::IllegalPointError);
1203 } else {
1204 panic!("Client accepted illegal point.");
1205 }
1206 }
1207
1208 #[test]
1209 #[cfg(all(
1210 feature = "sha2",
1211 feature = "scrypt",
1212 feature = "alloc",
1213 feature = "strong_aucpace"
1214 ))]
1215 fn test_strong_alloc_client_doesnt_accept_invalid_x_pub() {
1216 use crate::utils::H0;
1217 use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
1218 use curve25519_dalek::traits::Identity;
1219
1220 let ssid = H0::<sha2::Sha512>().finalize();
1221 let aug_client: StrongAuCPaceClientAugLayer<'_, sha2::Sha512, scrypt::Scrypt, 16> =
1222 StrongAuCPaceClientAugLayer::new(
1223 ssid,
1224 b"bob",
1225 b"bob's very secure password that nobody knows about, honest",
1226 Scalar::from(69u32),
1227 );
1228 let res = aug_client.generate_cpace_alloc(
1229 RistrettoPoint::identity(),
1230 RISTRETTO_BASEPOINT_POINT,
1231 scrypt::Params::recommended(),
1232 scrypt::Scrypt,
1233 );
1234
1235 if let Err(e) = res {
1236 assert_eq!(e, Error::IllegalPointError);
1237 } else {
1238 panic!("Client accepted illegal point.");
1239 }
1240 }
1241
1242 #[test]
1243 #[cfg(all(feature = "sha2", feature = "scrypt"))]
1244 fn test_client_doesnt_accept_invalid_pubkey() {
1245 use crate::utils::H0;
1246 use curve25519_dalek::traits::Identity;
1247 let ssid = H0::<sha2::Sha512>().finalize();
1248 let aug_client: AuCPaceClientRecvServerKey<sha2::Sha512, 16> =
1249 AuCPaceClientRecvServerKey::new(ssid, Scalar::from(420u32));
1250 let res = aug_client.receive_server_pubkey(RistrettoPoint::identity());
1251
1252 if let Err(e) = res {
1253 assert_eq!(e, Error::IllegalPointError);
1254 } else {
1255 panic!("Client accepted illegal point.");
1256 }
1257 }
1258
1259 #[test]
1260 #[cfg(all(feature = "sha2", feature = "scrypt"))]
1261 fn test_client_doesnt_accept_invalid_pubkey_implicit_auth() {
1262 use crate::utils::H0;
1263 use curve25519_dalek::traits::Identity;
1264 let ssid = H0::<sha2::Sha512>().finalize();
1265 let aug_client: AuCPaceClientRecvServerKey<sha2::Sha512, 16> =
1266 AuCPaceClientRecvServerKey::new(ssid, Scalar::from(420u32));
1267 let res = aug_client.implicit_auth(RistrettoPoint::identity());
1268
1269 if let Err(e) = res {
1270 assert_eq!(e, Error::IllegalPointError);
1271 } else {
1272 panic!("Client accepted illegal point.");
1273 }
1274 }
1275
1276 #[test]
1277 #[cfg(all(
1278 feature = "sha2",
1279 feature = "scrypt",
1280 feature = "alloc",
1281 feature = "strong_aucpace"
1282 ))]
1283 fn test_strong_alloc_client_doesnt_accept_invalid_salt() {
1284 use crate::utils::H0;
1285 use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
1286 use curve25519_dalek::traits::Identity;
1287
1288 let ssid = H0::<sha2::Sha512>().finalize();
1289 let aug_client: StrongAuCPaceClientAugLayer<'_, sha2::Sha512, scrypt::Scrypt, 16> =
1290 StrongAuCPaceClientAugLayer::new(
1291 ssid,
1292 b"bob",
1293 b"bob's very secure password that nobody knows about, honest",
1294 Scalar::from(69u32),
1295 );
1296 let res = aug_client.generate_cpace_alloc(
1297 RISTRETTO_BASEPOINT_POINT,
1298 RistrettoPoint::identity(),
1299 scrypt::Params::recommended(),
1300 scrypt::Scrypt,
1301 );
1302
1303 if let Err(e) = res {
1304 assert_eq!(e, Error::IllegalPointError);
1305 } else {
1306 panic!("Client accepted illegal point.");
1307 }
1308 }
1309}