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,
        })
    }
}