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 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>,
/// Current supported transports that this authenticator can use to communicate.
///
/// Default values are [`AuthenticatorTransport::Internal`] and [`AuthenticatorTransport::Hybrid`].
transports: Vec<webauthn::AuthenticatorTransport>,
/// Provider of user verification factor.
user_validation: U,
/// The display name given when a [`webauthn::CredentialPropertiesOutput`] is requested
display_name: Option<String>,
}
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],
transports: vec![
webauthn::AuthenticatorTransport::Internal,
webauthn::AuthenticatorTransport::Hybrid,
],
user_validation: user,
display_name: None,
}
}
/// Set the authenticator's display name which will be returned if [`webauthn::CredentialPropertiesOutput`] is requested.
pub fn set_display_name(&mut self, name: String) {
self.display_name = Some(name);
}
/// Get a reference to the authenticators display name to return in [`webauthn::CredentialPropertiesOutput`].
pub fn display_name(&self) -> Option<&String> {
self.display_name.as_ref()
}
/// 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)
}
/// Builder method for overwriting the authenticator's supported transports.
pub fn transports(self, transports: Vec<webauthn::AuthenticatorTransport>) -> Self {
Self { transports, ..self }
}
/// 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())
}
}
}