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