aucpace_conflux/
client.rs

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
35/// Implementation of the client side of the `AuCPace` protocol
36pub 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    /// Create a new client
54    pub const fn new(rng: CSPRNG) -> Self {
55        Self {
56            rng,
57            d: PhantomData,
58            h: PhantomData,
59        }
60    }
61
62    /// Create a new client in the SSID agreement phase
63    ///
64    /// # Return:
65    /// ([`next_step`](AuCPaceClientSsidEstablish<D, H, K1>), [`message`](ClientMessage::Nonce))
66    /// - [`next_step`](AuCPaceClientSsidEstablish): the client in the SSID establishment stage
67    /// - [`message`](ClientMessage::Nonce): the message to send to the server
68    ///
69    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    /// Create a new client in the pre-augmentation layer phase, provided an SSID
79    ///
80    /// # Argument:
81    /// - `ssid`: Some data to be hashed and act as the sub-session ID
82    ///
83    /// # Return:
84    /// - Ok([`next_step`](AuCPaceClientSsidEstablish)): the server in the SSID establishment stage
85    /// - Err([`Error::InsecureSsid`](Error::InsecureSsid)): the SSID provided was not long enough to be secure
86    ///
87    pub fn begin_prestablished_ssid<S>(&mut self, ssid: S) -> Result<AuCPaceClientPreAug<D, H, K1>>
88    where
89        S: AsRef<[u8]>,
90    {
91        // if the SSID isn't long enough return an error
92        if ssid.as_ref().len() < MIN_SSID_LEN {
93            return Err(Error::InsecureSsid);
94        }
95
96        // hash the SSID and begin the next step
97        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    /// Register a username/password
105    ///
106    /// # Arguments:
107    /// - `username` - the username to register with
108    /// - `password` - the password for the user
109    /// - `params` - the parameters of the PBKDF used
110    /// - `hasher` - the hasher to use for hashing the username and password.
111    ///
112    /// # Const Parameters
113    /// - `BUFSIZ` - the size of the buffer to use while hashing
114    ///   it should be enough to store the maximum length of a username + password + 1 for your use case
115    ///   e.g. if you have a username limit of 20 and password limit of 60, 81 would be the right value.
116    ///
117    /// # Return:
118    /// - Ok([`message`](ClientMessage::Registration)): the message to send to the server
119    /// - Err([`Error::PasswordHashing`](Error::PasswordHashing) | [`Error::HashEmpty`](Error::HashEmpty) | [`Error::HashSizeInvalid`](Error::HashSizeInvalid)):
120    ///   one of the three error variants that can result from the password hashing process
121    ///
122    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        // compute the verifier W
139        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        // attempt to convert the parameters to a ParamsString
152        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    /// Register a username/password in the strong variant of the protocol
163    ///
164    /// # Arguments:
165    /// - `username` - the username to register with
166    /// - `password` - the password for the user
167    /// - `params` - the parameters of the PBKDF used
168    /// - `hasher` - the hasher to use for hashing the username and password.
169    ///
170    /// # Const Parameters
171    /// - `BUFSIZ` - the size of the buffer to use while hashing
172    ///   it should be enough to store the maximum length of a username + password + 1 for your use case
173    ///   e.g. if you have a username limit of 20 and password limit of 60, 81 would be the right value.
174    ///
175    /// # Return:
176    /// - Ok([`message`](ClientMessage::Registration)): the message to send to the server
177    /// - Err([`Error::PasswordHashing`](Error::PasswordHashing) | [`Error::HashEmpty`](Error::HashEmpty) | [`Error::HashSizeInvalid`](Error::HashSizeInvalid)):
178    ///   one of the three error variants that can result from the password hashing process
179    ///
180    #[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        // generate a secret exponent and salt
192        let (q, salt_string) =
193            Self::generate_salt_strong(username, password.as_ref(), &mut self.rng)?;
194
195        // compute the verifier W
196        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        // attempt to convert the parameters to a ParamsString
208        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    /// Register a username/password
219    ///
220    /// Allocates space for user:pass string on the heap, instead of a constant size buffer.
221    ///
222    /// # Arguments:
223    /// - `username` - the username to register with
224    /// - `password` - the password for the user
225    /// - `params` - the parameters of the PBKDF used
226    /// - `hasher` - the hasher to use for hashing the username and password.
227    ///
228    /// # Return:
229    /// - Ok([`message`](ClientMessage::Registration)): the message to send to the server
230    /// - Err([`Error::PasswordHashing`](Error::PasswordHashing) | [`Error::HashEmpty`](Error::HashEmpty) | [`Error::HashSizeInvalid`](Error::HashSizeInvalid)):
231    ///   one of the three error variants that can result from the password hashing process
232    ///
233    #[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        // adapted from SaltString::generate, which we cannot use due to curve25519 versions of rand_core
245        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        // compute the verifier W
252        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        // attempt to convert the parameters to a ParamsString
259        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    /// Register a username/password in the strong variant of the protocol
270    ///
271    /// Allocates space for user:pass string on the heap, instead of a constant size buffer.
272    ///
273    /// # Arguments:
274    /// - `username` - the username to register with
275    /// - `password` - the password for the user
276    /// - `params` - the parameters of the PBKDF used
277    /// - `hasher` - the hasher to use for hashing the username and password.
278    ///
279    /// # Const Parameters
280    /// - `BUFSIZ` - the size of the buffer to use while hashing
281    ///   it should be enough to store the maximum length of a username + password + 1 for your use case
282    ///   e.g. if you have a username limit of 20 and password limit of 60, 81 would be the right value.
283    ///
284    /// # Return:
285    /// - Ok([`message`](ClientMessage::Registration)): the message to send to the server
286    /// - Err([`Error::PasswordHashing`](Error::PasswordHashing) | [`Error::HashEmpty`](Error::HashEmpty) | [`Error::HashSizeInvalid`](Error::HashSizeInvalid)):
287    ///   one of the three error variants that can result from the password hashing process
288    ///
289    #[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        // generate a secret exponent and salt
301        let (q, salt_string) =
302            Self::generate_salt_strong(username, password.as_ref(), &mut self.rng)?;
303
304        // compute the verifier W
305        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        // attempt to convert the parameters to a ParamsString
317        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    /// generate a secret exponent and a salt value for the strong variant of the protocol
328    #[cfg(feature = "strong_aucpace")]
329    fn generate_salt_strong(
330        user: &[u8],
331        pass: &[u8],
332        rng: &mut CSPRNG,
333    ) -> Result<(Scalar, SaltString)> {
334        // choose a random q
335        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        // compute z
343        let mut hasher: D = H1();
344        hasher.update(user);
345        hasher.update(pass);
346        let z = RistrettoPoint::from_hash(hasher);
347
348        // compute the salt value
349        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
358/// Client in the SSID agreement phase
359pub 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    /// Consume the server's nonce - `s` and progress to the augmentation layer
386    ///
387    /// # arguments:
388    /// - `server_nonce` - the nonce received from the server
389    ///
390    /// # return:
391    /// [`next_step`](AuCPaceClientPreAug): the client in the pre-augmentation stage
392    ///
393    #[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
400/// Client in the pre-augmentation phase
401pub 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    /// Consume the client's username and begin the augmentation layer
423    ///
424    /// # Arguments:
425    /// - `username` - a reference to the client's username
426    ///
427    /// # Return:
428    /// ([`next_step`](AuCPaceClientAugLayer), [`message`](ClientMessage::Username))
429    /// - [`next_step`](AuCPaceClientAugLayer): the client in the augmentation layer
430    /// - [`message`](ClientMessage::Username): the message to send to the server
431    ///
432    #[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    /// Consume the client's username and begin the augmentation layer.
445    /// This variant performs the strong version of the protocol.
446    ///
447    /// # Arguments:
448    /// - `username` - a reference to the client's username
449    ///
450    /// # Return:
451    /// ([`next_step`](StrongAuCPaceClientAugLayer), [`message`](ClientMessage::Username))
452    /// - [`next_step`](StrongAuCPaceClientAugLayer): the client in the augmentation layer
453    /// - [`message`](ClientMessage::Username): the message to send to the server
454    ///
455    #[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        // compute the blinding value and blind the hash of the username and password
469        // ensuring that it is non-zero as required by `invert`
470        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
496/// Client in the augmentation layer
497pub 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    /// Process the augmentation layer information from the server, hashes the user's password
523    /// together with their username, then computes `w` and `PRS`.
524    ///
525    /// # Arguments:
526    /// - `x_pub` - `x` from the protocol definition, used in generating the password related string (prs)
527    /// - `salt` - the salt value sent by the server
528    /// - `params` - the parameters used by the hasher
529    /// - `hasher` - the hasher to use when computing `w`
530    ///
531    /// # Const Parameters
532    /// - `BUFSIZ` - the size of the buffer to use while hashing
533    ///   it should be enough to store the maximum length of a username + password + 1 for your use case
534    ///   e.g. if you have a username limit of 20 and password limit of 60, 81 would be the right value.
535    ///
536    /// This version requires the alloc feature and allocates space for
537    /// the username and password on the heap using Vec.
538    ///
539    /// # Return:
540    /// - Ok([`next_step`](AuCPaceClientCPaceSubstep)): the client in the cpace substep
541    /// - Err([`Error::PasswordHashing`](Error::PasswordHashing) | [`Error::HashEmpty`](Error::HashEmpty) | [`Error::HashSizeInvalid`](Error::HashSizeInvalid)):
542    ///   one of the three error variants that can result from the password hashing process
543    ///
544    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        // check for the identity point
555        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    /// Process the augmentation layer information from the server, hashes the user's password
575    /// together with their username, then computes `w` and `PRS`.
576    ///
577    /// This version requires the alloc feature and allocates space for
578    /// the username:password string on the heap.
579    ///
580    /// # Arguments:
581    /// - `x_pub` - `x` from the protocol definition, used in generating the password related string (prs)
582    /// - `salt` - the salt value sent by the server
583    /// - `params` - the parameters used by the hasher
584    /// - `hasher` - the hasher to use when computing `w`
585    ///
586    /// # Return:
587    /// - Ok([`next_step`](AuCPaceClientCPaceSubstep)): the client in the cpace substep
588    /// - Err([`Error::PasswordHashing`](Error::PasswordHashing) | [`Error::HashEmpty`](Error::HashEmpty) | [`Error::HashSizeInvalid`](Error::HashSizeInvalid)):
589    ///   one of the three error variants that can result from the password hashing process
590    ///
591    #[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        // check for the identity point
603        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/// Client in the augmentation layer - strong version
618#[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    /// Process the strong augmentation layer information from the server, unblinds the salt value,
653    /// hashes the user's password together with their username, then computes `w` and `PRS`.
654    ///
655    /// # Arguments:
656    /// - `x_pub` - `x` from the protocol definition, used in generating the password related string (prs)
657    /// - `salt_point` - our blinded point `U`, multiplied by the server's secret exponent `q`
658    /// - `params` - the parameters used by the hasher
659    /// - `hasher` - the hasher to use when computing `w`
660    ///
661    /// # Const Parameters
662    /// - `BUFSIZ` - the size of the buffer to use while hashing
663    ///   it should be enough to store the maximum length of a username + password + 1 for your use case
664    ///   e.g. if you have a username limit of 20 and password limit of 60, 81 would be the right value.
665    ///
666    /// This version requires the alloc feature and allocates space for
667    /// the username and password on the heap using Vec.
668    ///
669    /// # Return:
670    /// - Ok([`next_step`](AuCPaceClientCPaceSubstep)): the client in the cpace substep
671    /// - Err([`Error::PasswordHashing`](Error::PasswordHashing) | [`Error::HashEmpty`](Error::HashEmpty) | [`Error::HashSizeInvalid`](Error::HashSizeInvalid)):
672    ///   one of the three error variants that can result from the password hashing process
673    ///
674    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        // check for the identity point
682        if x_pub.is_identity() {
683            return Err(Error::IllegalPointError);
684        }
685
686        // first recover the salt
687        let cofactor = Scalar::ONE;
688
689        // this is a tad funky, in the paper they write (1/(r * cj^2))*cj
690        // I have interpreted this as the multiplicative inverse of (r * cj^2)
691        // then multiplied by cj again.
692        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        // compute the PRS
697        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    /// Process the strong augmentation layer information from the server, unblinds the salt value,
711    /// hashes the user's password together with their username, then computes `w` and `PRS`.
712    ///
713    /// This version requires the alloc feature and allocates space for
714    /// the username:password string on the heap.
715    ///
716    /// # Arguments:
717    /// - `x_pub` - `x` from the protocol definition, used in generating the password related string (prs)
718    /// - `password` - the user's password
719    /// - `salt` - the salt value sent by the server
720    /// - `params` - the parameters used by the hasher
721    /// - `hasher` - the hasher to use when computing `w`
722    ///
723    /// # Return:
724    /// - Ok([`next_step`](AuCPaceClientCPaceSubstep)): the client in the cpace substep
725    /// - Err([`Error::PasswordHashing`](Error::PasswordHashing) | [`Error::HashEmpty`](Error::HashEmpty) | [`Error::HashSizeInvalid`](Error::HashSizeInvalid)):
726    ///   one of the three error variants that can result from the password hashing process
727    ///
728    #[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        // check for the identity point
737        if x_pub.is_identity() {
738            return Err(Error::IllegalPointError);
739        }
740
741        // first recover the salt
742        let cofactor = Scalar::ONE;
743
744        // this is a tad funky, in the paper they write (1/(r * cj^2))*cj
745        // I have interpreted this as the multiplicative inverse of (r * cj^2)
746        // then multiplied by cj again.
747        let exponent = (self.blinding_value * cofactor * cofactor).invert() * cofactor;
748
749        // check if the salt point is the neutral element
750        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        // compute the PRS
758        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/// Client in the `CPace` substep
773#[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    /// Generate a public key
792    /// moving the protocol onto the second half of the `CPace` substep - Receive Server Pubkey
793    ///
794    /// # Arguments:
795    /// - `channel_identifier` - `CI` from the protocol definition, in the context of TCP/IP this
796    ///     is usually some combination of the server and client's IP address and TCP port numbers.
797    ///     It's purpose is to prevent relay attacks.
798    /// - `rng` - the CSPRNG used when generating the public/private keypair
799    ///
800    /// # Return:
801    /// ([`next_step`](AuCPaceClientRecvServerKey), [`message`](ClientMessage::PublicKey))
802    /// - [`next_step`](AuCPaceClientRecvServerKey): the client waiting for the server's public key
803    /// - [`message`](ClientMessage::PublicKey): the message to send to the server
804    ///
805    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/// Client waiting to receive the server's public key
828#[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    /// Receive the server's public key
847    /// This completes the `CPace` substep and moves the client on to explicit mutual authentication.
848    ///
849    /// # Arguments:
850    /// - `server_pubkey` - the server's public key
851    ///
852    /// # Return:
853    /// ([`next_step`](AuCPaceClientExpMutAuth), [`message`](ClientMessage::Authenticator))
854    /// - [`next_step`](AuCPaceClientExpMutAuth): the client in the Explicit Mutual Authentication phase
855    /// - [`message`](ClientMessage::Authenticator): the message to send to the server
856    ///
857    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    /// Allow the user to exit the protocol early in the case of implicit authentication
877    /// Note: this should only be used in special circumstances and the
878    ///       explicit mutual authentication stage should be used in all other cases
879    ///
880    /// # Arguments:
881    /// - `server_pubkey` - the server's public key
882    ///
883    /// # Return:
884    /// `sk`: the session key reached by the `AuCPace` protocol
885    ///
886    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/// Client in the Explicit Mutual Authenticaton phase
902#[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    /// Receive the server's authenticator.
928    /// This completes the protocol and returns the derived key.
929    ///
930    /// # Arguments:
931    /// - `server_authenticator` - the server's authenticator
932    ///
933    /// # Return:
934    /// either:
935    /// - Ok(`sk`): the session key reached by the `AuCPace` protocol
936    /// - Err([`Error::MutualAuthFail`](Error::MutualAuthFail)): an error if the authenticator we computed doesn't match
937    ///     the server's authenticator, compared in constant time.
938    ///
939    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
959/// Hash a username and password with the given password hasher
960fn 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/// Hash a username and password with the given password hasher
995#[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    // hash "{username}:{password}"
1013    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/// An enum representing the different messages the client can send to the server
1024#[derive(Debug)]
1025#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1026pub enum ClientMessage<'a, const K1: usize> {
1027    /// SSID establishment message - the client's nonce: `t`
1028    Nonce(#[cfg_attr(feature = "serde", serde(with = "serde_byte_array"))] [u8; K1]),
1029
1030    /// Username - the client's username
1031    Username(&'a [u8]),
1032
1033    /// StrongUsername - the strong `AuCPace` username message
1034    /// also contains the blinded point `U`
1035    #[cfg(feature = "strong_aucpace")]
1036    StrongUsername {
1037        /// The client's username
1038        username: &'a [u8],
1039        /// The blinded point `U`
1040        blinded: RistrettoPoint,
1041    },
1042
1043    /// PublicKey - the client's public key: `Ya`
1044    PublicKey(RistrettoPoint),
1045
1046    /// Explicit Mutual Authentication - the client's authenticator: `Tb`
1047    Authenticator(#[cfg_attr(feature = "serde", serde(with = "serde_byte_array"))] [u8; 64]),
1048
1049    /// Registration - the username, verifier, salt and parameters needed for registering a user
1050    /// NOTE: if the UAD field is desired this should be handled separately and sent at the same time
1051    Registration {
1052        /// The username of whoever is registering
1053        username: &'a [u8],
1054
1055        /// The salt used when computing the verifier
1056        #[cfg_attr(feature = "serde", serde(with = "serde_saltstring"))]
1057        salt: SaltString,
1058
1059        /// The password hasher's parameters used when computing the verifier
1060        #[cfg_attr(feature = "serde", serde(with = "serde_paramsstring"))]
1061        params: ParamsString,
1062
1063        /// The verifier computed from the user's password
1064        verifier: RistrettoPoint,
1065    },
1066
1067    /// Registration Strong version - the username, verifier, secret exponent and parameters needed for registering a user
1068    /// NOTE: if the UAD field is desired this should be handled separately and sent at the same time
1069    #[cfg(feature = "strong_aucpace")]
1070    StrongRegistration {
1071        /// The username of whoever is registering
1072        username: &'a [u8],
1073
1074        /// The salt used when computing the verifier
1075        secret_exponent: Scalar,
1076
1077        /// The password hasher's parameters used when computing the verifier
1078        #[cfg_attr(feature = "serde", serde(with = "serde_paramsstring"))]
1079        params: ParamsString,
1080
1081        /// The verifier computed from the user's password
1082        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        // These are weak parameters, do not use them
1104        // they are used here to make the test run faster
1105        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}