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