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
use coset::iana;
use passkey_types::{
ctap2::{Aaguid, Ctap2Error, Flags},
webauthn,
};
use crate::{CredentialStore, UserValidationMethod};
mod get_assertion;
mod get_info;
mod make_credential;
/// A virtual authenticator with all the necessary state and information.
pub struct Authenticator<S, U> {
/// The authenticator's AAGUID
aaguid: Aaguid,
/// Provides credential storage capabilities
store: S,
/// Current supported algorithms by the authenticator
algs: Vec<iana::Algorithm>,
/// Provider of user verification factor.
user_validation: U,
}
impl<S, U> Authenticator<S, U>
where
S: CredentialStore,
U: UserValidationMethod,
{
/// Create an authenticator with a known aaguid, a backing storage and a User verification system.
pub fn new(aaguid: Aaguid, store: S, user: U) -> Self {
Self {
aaguid,
store,
// TODO: Change this to a method on the cryptographic backend
algs: vec![iana::Algorithm::ES256],
user_validation: user,
}
}
/// Access the [`CredentialStore`] to look into what is stored.
pub fn store(&self) -> &S {
&self.store
}
/// Exclusively access the [`CredentialStore`] to look into what is stored and modify it if needed.
pub fn store_mut(&mut self) -> &mut S {
&mut self.store
}
/// Access the authenticator's [`Aaguid`]
pub fn aaguid(&self) -> &Aaguid {
&self.aaguid
}
/// Return the current attachment type for this authenticator.
pub fn attachment_type(&self) -> webauthn::AuthenticatorAttachment {
// TODO: Make this variable depending on the transport.
webauthn::AuthenticatorAttachment::Platform
}
/// Validate `params` with the following steps
/// 1. For each element of `params`:
/// 1-2: Handled during deserialization
/// 3. If the element specifies an algorithm that is supported by the authenticator, and
/// no algorithm has yet been chosen by this loop, then let the algorithm specified by
/// the current element be the chosen algorithm.
/// 2. If the loop completes and no algorithm was chosen then return [`Ctap2Error::UnsupportedAlgorithm`].
/// Note: This loop chooses the first occurrence of an algorithm identifier supported by this
/// authenticator but always iterates over every element of `params` to validate them.
pub fn choose_algorithm(
&self,
params: &[webauthn::PublicKeyCredentialParameters],
) -> Result<iana::Algorithm, Ctap2Error> {
params
.iter()
.find(|param| self.algs.contains(¶m.alg))
.map(|param| param.alg)
.ok_or(Ctap2Error::UnsupportedAlgorithm)
}
/// 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):
/// 1. If the "uv" option was specified and set to true:
/// 1. If device doesn’t support user-identifiable gestures, return the
/// CTAP2_ERR_UNSUPPORTED_OPTION error.
/// 2. Collect a user-identifiable gesture. If gesture validation fails, return the
/// CTAP2_ERR_OPERATION_DENIED error.
/// 2. If the "up" option was specified and set to true, collect the user’s consent.
/// 1. If no consent is obtained and a timeout occurs, return the
/// CTAP2_ERR_OPERATION_DENIED error.
async fn check_user(
&self,
options: &passkey_types::ctap2::make_credential::Options,
) -> Result<Flags, Ctap2Error> {
if options.uv {
let Some(true) = self.user_validation.is_verification_enabled() else {
return Err(Ctap2Error::UnsupportedOption)
};
if self.user_validation.check_user_verification().await {
Ok(Flags::UP | Flags::UV)
} else {
Err(Ctap2Error::OperationDenied)
}
} else if options.up {
if self.user_validation.check_user_presence().await {
Ok(Flags::UP)
} else {
Err(Ctap2Error::OperationDenied)
}
} else {
Ok(Flags::empty())
}
}
}