passkey_authenticator/
u2f.rs

1//! Follows U2F 1.2 <https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html>
2
3use crate::{
4    Authenticator, CoseKeyPair, CredentialStore, UserValidationMethod, passkey::PasskeyAccessor,
5};
6use coset::iana;
7use p256::{
8    SecretKey,
9    ecdsa::{SigningKey, signature::Signer},
10};
11use passkey_types::{
12    Bytes, Passkey,
13    ctap2::{Flags, U2FError},
14    u2f::{
15        AuthenticationRequest, AuthenticationResponse, PublicKey, RegisterRequest, RegisterResponse,
16    },
17};
18mod sealed {
19    use crate::{Authenticator, CredentialStore, UserValidationMethod};
20
21    pub trait Sealed {}
22    impl<S: CredentialStore, U: UserValidationMethod> Sealed for Authenticator<S, U> {}
23}
24
25/// Provides the U2F Authenticator API
26#[async_trait::async_trait]
27pub trait U2fApi: sealed::Sealed {
28    /// from: RegisterRequest::register() (u2f/register.rs)
29    async fn register(
30        &mut self,
31        request: RegisterRequest,
32        handle: &[u8],
33    ) -> Result<RegisterResponse, U2FError>;
34
35    /// from AuthenticationRequest::authenticate() (u2f/authenticate.rs)
36    async fn authenticate(
37        &self,
38        request: AuthenticationRequest,
39        counter: u32,
40        user_presence: Flags,
41    ) -> Result<AuthenticationResponse, U2FError>;
42}
43
44#[async_trait::async_trait]
45impl<S: CredentialStore + Sync + Send, U: UserValidationMethod + Sync + Send> U2fApi
46    for Authenticator<S, U>
47{
48    /// Apply a register request and create a credential and respond with the public key of said credential.
49    async fn register(
50        &mut self,
51        request: RegisterRequest,
52        handle: &[u8],
53    ) -> Result<RegisterResponse, U2FError> {
54        // Create Keypair on P256 curve
55        let private_key = {
56            let mut rng = rand::thread_rng();
57            SecretKey::random(&mut rng)
58        };
59
60        // SAFETY: Can only fail if key is malformed
61        let CoseKeyPair { public: _, private } =
62            CoseKeyPair::from_secret_key(&private_key, iana::Algorithm::ES256);
63        let signing_key = SigningKey::from(private_key);
64        let public_key = signing_key.verifying_key();
65        let pub_key_encoded = public_key.to_encoded_point(false);
66
67        // SAFETY: These unwraps are safe due to the encoding not having any compression (false above)
68        // this makes sure that both x and y points are present in the encoded and are of 32 bytes
69        // in size.
70        let public_key = PublicKey {
71            x: pub_key_encoded.x().unwrap().as_slice().try_into().unwrap(),
72            y: pub_key_encoded.y().unwrap().as_slice().try_into().unwrap(),
73        };
74
75        // create signature, see [`RegisterResponse::signature`]'s documentation for more information
76        let signature_target = [0x00] // 1. reserved byte
77            .into_iter()
78            .chain(request.application) // 2. application parameter
79            .chain(request.challenge) // 3. challenge parameter
80            .chain(handle.iter().copied()) // 4. Key handle
81            .chain(public_key.encode()) // 5. public key
82            .collect::<Vec<u8>>();
83        let signature_singleton: p256::ecdsa::Signature = signing_key.sign(&signature_target);
84        let signature = signature_singleton.to_vec();
85
86        let attestation_certificate = Vec::new();
87
88        let response = RegisterResponse {
89            public_key,
90            key_handle: handle.into(),
91            attestation_certificate,
92            signature,
93        };
94
95        let (passkey, user, rp) =
96            Passkey::wrap_u2f_registration_request(&request, &response, handle, &private);
97
98        // U2F registration does not use rk, uv, or up
99        let options = passkey_types::ctap2::get_assertion::Options {
100            rk: false,
101            uv: false,
102            up: false,
103        };
104        let result = self
105            .store_mut()
106            .save_credential(passkey, user, rp, options)
107            .await;
108
109        match result {
110            Ok(_) => Ok(response),
111            _ => Err(U2FError::Other),
112        }
113    }
114
115    /// Apply an authentication request with the appropriate response
116    async fn authenticate(
117        &self,
118        request: AuthenticationRequest,
119        counter: u32,
120        user_presence: Flags,
121    ) -> Result<AuthenticationResponse, U2FError> {
122        // Turn the Authentication Request into a PublicKeyCredentialDescriptor and
123        // an rp_id in order to find the secret key in our store
124
125        let pk_descriptor = passkey_types::webauthn::PublicKeyCredentialDescriptor {
126            ty: passkey_types::webauthn::PublicKeyCredentialType::PublicKey,
127            id: request.key_handle.into(),
128            transports: None,
129        };
130        let id_bytes: Bytes = request.application.to_vec().into();
131        let maybe_credential = self
132            .store()
133            .find_credentials(
134                Some(&[pk_descriptor]),
135                String::from(id_bytes).as_str(),
136                None,
137            )
138            .await
139            .map_err(|_| U2FError::Other);
140
141        let credential = maybe_credential?
142            .into_iter()
143            .next()
144            .ok_or(U2FError::Other)?;
145
146        let secret_key =
147            super::private_key_from_cose_key(&credential.key()).map_err(|_| U2FError::Other)?;
148        let signing_key = SigningKey::from(secret_key);
149
150        // The following signature_target is specified in the U2F Raw Message Formats spec:
151        // https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-raw-message-formats-v1.2-ps-20170411.html#authentication-response-message-success
152        // [A signature] is [an] ECDSA signature (on P-256) over the following byte string:
153        let signature_target = request
154            .application // 1. The application parameter [32 bytes] from the authentication request message.
155            .into_iter()
156            .chain(std::iter::once(user_presence.into())) // 2. The ... user presence byte [1 byte].
157            .chain(counter.to_be_bytes()) // 3. The ... counter [4 bytes].
158            .chain(request.challenge) // 4. The challenge parameter [32 bytes] from the authentication request message.
159            .collect::<Vec<u8>>();
160
161        let signature: p256::ecdsa::Signature = signing_key.sign(&signature_target);
162        let signature_bytes = signature.to_der().as_bytes().to_vec();
163
164        Ok(AuthenticationResponse {
165            user_presence,
166            counter,
167            signature: signature_bytes,
168        })
169    }
170}
171
172#[cfg(test)]
173mod tests;