Skip to main content

defuse_webauthn/
p256.rs

1use defuse_digest::{Digest, sha2::Sha256};
2
3use crate::Algorithm;
4
5/// [COSE ES256 (-7) algorithm](https://www.iana.org/assignments/cose/cose.xhtml#algorithms):
6/// P256 (a.k.a secp256r1) over SHA-256
7pub struct P256;
8
9impl Algorithm for P256 {
10    type Curve = defuse_crypto::p256::P256;
11
12    fn maybe_prehash(msg: impl AsRef<[u8]>) -> impl AsRef<[u8]> {
13        // prehash via SHA-256
14        Sha256::digest(msg)
15    }
16}
17
18#[cfg(test)]
19mod tests {
20    use defuse_crypto::{
21        Signer,
22        p256::{P256CompressedPublicKey, P256Signature, p256::ecdsa::SigningKey},
23    };
24    use defuse_digest::common::Generate;
25    use hex_literal::hex;
26    use rand::rng;
27    use rstest::rstest;
28
29    use crate::{
30        IgnoreUserVerification, RequireUserVerification, Webauthn, WebauthnAssertion,
31        mock::MockWebauthnSigner,
32    };
33
34    use super::*;
35
36    #[rstest]
37    #[case(
38        hex!("03b87da10683d04e6ec4e2f1775556a63cbb01be843058eb737fe02f9e22663093"),
39        hex!("06f269191431372337a0c606a15822e349bd0d5ec317704f97bef1a4ed6f5e1d"),
40        WebauthnAssertion {
41            authenticator_data: hex!("49960de5880e8c687434170f6476605b8fe4aeb9a28632c7995cf3ba831d97631d00000000").to_vec(),
42            client_data_json: r#"{"type":"webauthn.get","challenge":"BvJpGRQxNyM3oMYGoVgi40m9DV7DF3BPl77xpO1vXh0","origin":"http://localhost:5173","crossOrigin":false}"#.to_string(),
43        },
44        hex!("002527c17a17d709ab62ffab033e0a26d901f5bee6686a0d605032828e490a7c47a9f4161e6a17688ae42a66adb67c9c22c86153afb08103b1e9eccd5314c271"),
45    )]
46    fn verify_ok(
47        #[case] public_key: impl Into<P256CompressedPublicKey>,
48        #[case] challenge: impl AsRef<[u8]>,
49        #[case] payload: WebauthnAssertion,
50        #[case] signature: impl Into<P256Signature>,
51    ) {
52        assert!(
53            Webauthn::<P256, RequireUserVerification>::verify(
54                &public_key.into().try_into().unwrap(),
55                challenge,
56                &payload,
57                &signature.into().try_into().unwrap(),
58            ),
59            "signature is invalid",
60        );
61    }
62
63    #[rstest]
64    #[case("")]
65    #[case("test")]
66    #[case(hex!("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"))]
67    #[tokio::test]
68    async fn sign_verify_ok(#[case] challenge: impl AsRef<[u8]>) {
69        let challenge = challenge.as_ref();
70
71        let signer = SigningKey::generate_from_rng(&mut rng());
72        let mock = MockWebauthnSigner::<P256, IgnoreUserVerification, _>::new(signer);
73        let (assertion, signature) = mock.sign(challenge).await.unwrap();
74
75        assert!(
76            Webauthn::<P256, IgnoreUserVerification>::verify(
77                &mock.signer().public_key(),
78                challenge,
79                &assertion,
80                &signature
81            ),
82            "signer produced invalid signature"
83        );
84    }
85}