Skip to main content

defuse_webauthn/
mock.rs

1use std::{
2    marker::PhantomData,
3    sync::{
4        Arc,
5        atomic::{AtomicU32, Ordering},
6    },
7};
8
9use defuse_crypto::{Curve, Signer};
10use hex_literal::hex;
11use impl_tools::autoimpl;
12
13use crate::{Algorithm, ClientDataType, CollectedClientData, UserVerification, WebauthnAssertion};
14
15/// Mock origin
16const ORIGIN: &str = "http://localhost";
17
18/// SHA-256 hash of [`ORIGIN`]'s Relaying Party ID
19///
20/// See <https://w3c.github.io/webauthn/#rp-id>
21const RP_ID_HASH: [u8; 32] =
22    hex!("49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d9763");
23
24/// Mock signer for [`Webauthn`](crate::Webauthn) signature schema
25#[autoimpl(Debug, Clone where S: trait)]
26pub struct MockWebauthnSigner<A: Algorithm, UV: UserVerification, S: Signer<A::Curve>> {
27    sign_count: Arc<AtomicU32>,
28    signer: S,
29    _algorithm: PhantomData<fn() -> A>,
30    _user_verification: PhantomData<fn() -> UV>,
31}
32
33impl<A, UV, S> MockWebauthnSigner<A, UV, S>
34where
35    A: Algorithm,
36    UV: UserVerification,
37    S: Signer<A::Curve>,
38{
39    #[inline]
40    pub fn new(signer: S) -> Self {
41        Self {
42            sign_count: Arc::new(AtomicU32::new(0)),
43            signer,
44            _algorithm: PhantomData,
45            _user_verification: PhantomData,
46        }
47    }
48
49    #[inline]
50    pub const fn signer(&self) -> &S {
51        &self.signer
52    }
53
54    pub async fn sign(
55        &self,
56        challenge: impl Into<Vec<u8>>,
57    ) -> Result<(WebauthnAssertion, <A::Curve as Curve>::Signature), S::Error> {
58        let assertion = WebauthnAssertion {
59            // https://w3c.github.io/webauthn/#table-authData
60            authenticator_data: [
61                // rpIdHash
62                RP_ID_HASH.as_slice(),
63                // flags
64                &[{
65                    let mut flags = 0b00000001u8; // UP (User Present)
66                    if UV::REQUIRED {
67                        flags |= 0b00000100u8; // set UV (User Verified)
68                    }
69                    flags
70                }],
71                // signCount
72                &self.sign_count.fetch_add(1, Ordering::SeqCst).to_be_bytes(),
73            ]
74            .concat(),
75
76            // https://w3c.github.io/webauthn/#dictdef-collectedclientdata
77            client_data_json: serde_json::to_string(&CollectedClientData {
78                typ: ClientDataType::Get,
79                challenge: challenge.into(),
80                origin: ORIGIN.to_string(),
81            })
82            .expect("JSON: failed to serialize"),
83        };
84
85        let data = A::maybe_prehash(assertion.effective_msg())
86            .as_ref()
87            .to_vec();
88
89        let signature = self.signer.sign(&data).await?;
90
91        Ok((assertion, signature))
92    }
93}