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(&param.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())
        }
    }
}