passkey_authenticator/authenticator/
get_assertion.rs

1use p256::ecdsa::{SigningKey, signature::SignerMut};
2use passkey_types::{
3    Bytes,
4    ctap2::{
5        AuthenticatorData, Ctap2Error, Flags, StatusCode,
6        get_assertion::{Request, Response},
7    },
8    webauthn::PublicKeyCredentialUserEntity,
9};
10
11use crate::{
12    Authenticator, CredentialStore, UserValidationMethod,
13    passkey::{AsCredentialDescriptor, PasskeyAccessor},
14    private_key_from_cose_key,
15    user_validation::UiHint,
16};
17
18impl<S, U> Authenticator<S, U>
19where
20    S: CredentialStore + Sync,
21    U: UserValidationMethod<PasskeyItem = <S as CredentialStore>::PasskeyItem> + Sync,
22{
23    /// This method is used by a host to request cryptographic proof of user authentication as well
24    /// as user consent to a given transaction, using a previously generated credential that is
25    /// bound to the authenticator and relying party identifier.
26    pub async fn get_assertion(&mut self, input: Request) -> Result<Response, StatusCode> {
27        // 1. Locate all credentials that are eligible for retrieval under the specified criteria:
28        //     1. If an allowList is present and is non-empty, locate all denoted credentials
29        //        present on this authenticator and bound to the specified rpId.
30        //     2. If an allowList is not present, locate all credentials that are present on this
31        //        authenticator and bound to the specified rpId.
32        //     3. Let numberOfCredentials be the number of credentials found.
33        //        --> Seeing as we handle 1 credential per account for an RP, returning the number
34        //            of credentials leaks the number of accounts that is stored. This is not ideal,
35        //            therefore we will never populate this field.
36        let maybe_credential = self
37            .store()
38            .find_credentials(
39                input
40                    .allow_list
41                    .as_deref()
42                    .filter(|inner| !inner.is_empty()),
43                &input.rp_id,
44                // User handle is not available in assertion calls
45                None,
46            )
47            .await
48            .and_then(|c| c.into_iter().next().ok_or(Ctap2Error::NoCredentials.into()));
49
50        // 2. If pinAuth parameter is present and pinProtocol is 1, verify it by matching it against
51        //    first 16 bytes of HMAC-SHA-256 of clientDataHash parameter using
52        //    pinToken: HMAC-SHA-256(pinToken, clientDataHash).
53        //     1. If the verification succeeds, set the "uv" bit to 1 in the response.
54        //     2. If the verification fails, return CTAP2_ERR_PIN_AUTH_INVALID error.
55        // 3. If pinAuth parameter is present and the pinProtocol is not supported,
56        //    return CTAP2_ERR_PIN_AUTH_INVALID.
57        // 4. If pinAuth parameter is not present and clientPin has been set on the authenticator,
58        //    set the "uv" bit to 0 in the response.
59        if input.pin_auth.is_some() {
60            return Err(Ctap2Error::PinAuthInvalid.into());
61        }
62
63        // 5. If the options parameter is present, process all the options.
64        //     1. If the option is known but not supported, terminate this procedure and
65        //        return CTAP2_ERR_UNSUPPORTED_OPTION.
66        //     2. If the option is known but not valid for this command, terminate this procedure
67        //        and return CTAP2_ERR_INVALID_OPTION.
68        //     3. Ignore any options that are not understood.
69        // Note that because this specification defines normative behaviors for them, all
70        // authenticators MUST understand the "rk", "up", and "uv" options.
71
72        // 6. TODO, if the extensions parameter is present, process any extensions that this
73        //    authenticator supports. Authenticator extension outputs generated by the authenticator
74        //    extension processing are returned in the authenticator data.
75
76        // 7. Collect user consent if required. This step MUST happen before the following steps due
77        //    to privacy reasons (i.e., authenticator cannot disclose existence of a credential
78        //    until the user interacted with the device):
79        let hint = match &maybe_credential {
80            Ok(credential) => UiHint::RequestExistingCredential(credential),
81            Err(_) => UiHint::InformNoCredentialsFound,
82        };
83        let flags = self.check_user(hint, &input.options).await?;
84
85        // 8. If no credentials were located in step 1, return CTAP2_ERR_NO_CREDENTIALS.
86        let mut credential = maybe_credential?;
87
88        // 9. If more than one credential was located in step 1 and allowList is present and not
89        //    empty, select any applicable credential and proceed to step 12. Otherwise, order the
90        //    credentials by the time when they were created in reverse order. The first credential
91        //    is the most recent credential that was created.
92        // NB: This should be done within the `CredentialStore::find_any` implementation. Essentially
93        // if multiple credentials are found, use the most recently created one.
94
95        // 10. If authenticator does not have a display:
96        //     1. Remember the authenticatorGetAssertion parameters.
97        //     2. Create a credential counter(credentialCounter) and set it 1. This counter
98        //        signifies how many credentials are sent to the platform by the authenticator.
99        //     3. Start a timer. This is used during authenticatorGetNextAssertion command.
100        //        This step is optional if transport is done over NFC.
101        //     4. Update the response to include the first credential’s publicKeyCredentialUserEntity
102        //        information and numberOfCredentials. User identifiable information (name,
103        //        DisplayName, icon) inside publicKeyCredentialUserEntity MUST not be returned if
104        //        user verification is not done by the authenticator.
105
106        // 11. If authenticator has a display:
107        //     1. Display all these credentials to the user, using their friendly name along with
108        //        other stored account information.
109        //     2. Also, display the rpId of the requester (specified in the request) and ask the
110        //        user to select a credential.
111        //     3. If the user declines to select a credential or takes too long (as determined by
112        //        the authenticator), terminate this procedure and return the
113        //        CTAP2_ERR_OPERATION_DENIED error.
114
115        // [WebAuthn-9]. Increment the credential associated signature counter or the global signature
116        //               counter value, depending on which approach is implemented by the authenticator,
117        //               by some positive value. If the authenticator does not implement a signature
118        //               counter, let the signature counter value remain constant at zero.
119        if let Some(counter) = credential.counter() {
120            credential.set_counter(counter + 1);
121            self.store_mut().update_credential(&credential).await?;
122        }
123
124        let extensions =
125            self.get_extensions(&credential, input.extensions, flags.contains(Flags::UV))?;
126        // 12. Sign the clientDataHash along with authData with the selected credential.
127        //     Let signature be the assertion signature of the concatenation `authenticatorData` ||
128        //     `client_data_hash` using the privateKey of selectedCredential. A simple, undelimited
129        //      concatenation is safe to use here because the authenticator data describes its own
130        //      length. The hash of the serialized client data (which potentially has a variable
131        //      length) is always the last element.
132        let auth_data = AuthenticatorData::new(&input.rp_id, credential.counter())
133            .set_flags(flags)
134            .set_assertion_extensions(extensions.signed)?;
135
136        let mut signature_target = auth_data.to_vec();
137        signature_target.extend(input.client_data_hash);
138
139        let secret_key = private_key_from_cose_key(&credential.key())?;
140
141        let mut private_key = SigningKey::from(secret_key);
142
143        let signature: p256::ecdsa::Signature = private_key.sign(&signature_target);
144        let signature_bytes = signature.to_der().to_bytes().to_vec().into();
145
146        let user_handle = credential.user_handle().map(Bytes::from);
147
148        Ok(Response {
149            credential: Some(credential.as_credential_descriptor(None)),
150            auth_data,
151            signature: signature_bytes,
152            user: user_handle.map(|id| PublicKeyCredentialUserEntity {
153                id,
154                // TODO: make a Authenticator version of this struct similar to make_credential::PublicKeyCredentialRpEntity
155                // since these fields are optional at the authenticator boundary, but required at the client boundary.
156                display_name: "".into(),
157                name: "".into(),
158            }),
159            number_of_credentials: None,
160            user_selected: None,
161            large_blob_key: None,
162            unsigned_extension_outputs: extensions.unsigned,
163        })
164    }
165}
166
167#[cfg(test)]
168mod tests;