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;