passkey_types/ctap2/
get_assertion.rs

1//! <https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetAssertion>
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    Bytes,
6    ctap2::AuthenticatorData,
7    utils::serde::{ignore_unknown, ignore_unknown_opt_vec},
8    webauthn::{PublicKeyCredentialDescriptor, PublicKeyCredentialUserEntity},
9};
10
11pub use crate::ctap2::make_credential::Options;
12
13#[cfg(doc)]
14use {
15    crate::webauthn::{CollectedClientData, PublicKeyCredentialRequestOptions},
16    ciborium::value::Value,
17};
18
19use super::extensions::{AuthenticatorPrfGetOutputs, AuthenticatorPrfInputs, HmacGetSecretInput};
20
21serde_workaround! {
22    /// While similar in structure to [`PublicKeyCredentialRequestOptions`],
23    /// it is not completely identical, namely the presence of the `options` key.
24    #[derive(Debug)]
25    pub struct Request {
26        /// Relying Party Identifier
27        #[serde(rename = 0x01)]
28        pub rp_id: String,
29
30        /// Hash of the serialized client data collected by the host.
31        /// See [`CollectedClientData`]
32        #[serde(rename = 0x02)]
33        pub client_data_hash: Bytes,
34
35        /// A sequence of PublicKeyCredentialDescriptor structures, each denoting a credential. If
36        /// this parameter is present and has 1 or more entries, the authenticator MUST only
37        /// generate an assertion using one of the denoted credentials.
38        #[serde(
39            rename = 0x03;
40            default,
41            skip_serializing_if = Option::is_none,
42            deserialize_with = ignore_unknown_opt_vec
43        )]
44        pub allow_list: Option<Vec<PublicKeyCredentialDescriptor>>,
45
46        /// Parameters to influence authenticator operation. These parameters might be authenticator
47        /// specific.
48        #[serde(
49            rename = 0x04;
50            default,
51            skip_serializing_if = Option::is_none,
52            deserialize_with = ignore_unknown
53        )]
54        pub extensions: Option<ExtensionInputs>,
55
56        /// Parameters to influence authenticator operation, see [`Options`] for more details.
57        #[serde(rename = 0x05; default)]
58        pub options: Options,
59
60        /// First 16 bytes of HMAC-SHA-256 of clientDataHash using pinToken which platform got from
61        /// the authenticator: HMAC-SHA-256(pinToken, clientDataHash). (NOT YET SUPPORTED)
62        #[serde(rename = 0x06; default, skip_serializing_if = Option::is_none)]
63        pub pin_auth: Option<Bytes>,
64
65        /// PIN protocol version chosen by the client
66        #[serde(rename = 0x07; default, skip_serializing_if = Option::is_none)]
67        pub pin_protocol: Option<u8>,
68    }
69}
70
71/// All supported Authenticator extensions inputs during credential assertion
72#[derive(Debug, Serialize, Deserialize, Default)]
73pub struct ExtensionInputs {
74    /// The input salts for fetching and deriving a symmetric secret.
75    ///
76    /// <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-hmac-secret-extension>
77    #[serde(
78        rename = "hmac-secret",
79        default,
80        skip_serializing_if = "Option::is_none",
81        deserialize_with = "ignore_unknown"
82    )]
83    pub hmac_secret: Option<HmacGetSecretInput>,
84
85    /// The direct input from a on-system client for the prf extension.
86    ///
87    /// The output from a request using the `prf` extension will not be signed
88    /// and will be un-encrypted.
89    /// This input should already be hashed by the client.
90    #[serde(
91        default,
92        skip_serializing_if = "Option::is_none",
93        deserialize_with = "ignore_unknown"
94    )]
95    pub prf: Option<AuthenticatorPrfInputs>,
96}
97
98impl ExtensionInputs {
99    /// Validates that there is at least one extension field that is `Some`.
100    /// If all fields are `None` then this returns `None` as well.
101    pub fn zip_contents(self) -> Option<Self> {
102        let Self { hmac_secret, prf } = &self;
103
104        let has_hmac_secret = hmac_secret.is_some();
105        let has_prf = prf.is_some();
106
107        (has_hmac_secret || has_prf).then_some(self)
108    }
109}
110
111serde_workaround! {
112    /// Type returned from `Authenticator::get_assertion` on success.
113    #[derive(Debug)]
114    pub struct Response {
115        /// PublicKeyCredentialDescriptor structure containing the credential identifier whose
116        /// private key was used to generate the assertion. May be omitted if the allowList has
117        /// exactly one Credential.
118        #[serde(rename = 0x01; default, skip_serializing_if = Option::is_none)]
119        pub credential: Option<PublicKeyCredentialDescriptor>,
120
121        /// The signed-over contextual bindings made by the authenticator
122        #[serde(rename = 0x02)]
123        pub auth_data: AuthenticatorData,
124
125        /// The assertion signature produced by the authenticator
126        #[serde(rename = 0x03)]
127        pub signature: Bytes,
128
129        /// [`PublicKeyCredentialUserEntity`] structure containing the user account information.
130        /// User identifiable information (name, DisplayName, icon) MUST not be returned if user
131        /// verification is not done by the authenticator.
132        ///
133        /// ## U2F Devices:
134        /// For U2F devices, this parameter is not returned as this user information is not present
135        /// for U2F credentials.
136        ///
137        /// ## FIDO Devices - server resident credentials:
138        /// For server resident credentials on FIDO devices, this parameter is optional as server
139        /// resident credentials behave same as U2F credentials where they are discovered given the
140        /// user information on the RP. Authenticators optionally MAY store user information inside
141        /// the credential ID.
142        ///
143        /// ## FIDO devices - device resident credentials:
144        /// For device resident keys on FIDO devices, at least user "id" is mandatory.
145        ///
146        /// For single account per RP case, authenticator returns "id" field to the platform which
147        /// will be returned to the WebAuthn layer.
148        ///
149        /// For multiple accounts per RP case, where the authenticator does not have a display,
150        /// authenticator returns "id" as well as other fields to the platform. Platform will use
151        /// this information to show the account selection UX to the user and for the user selected
152        /// account, it will ONLY return "id" back to the WebAuthn layer and discard other user details.
153        #[serde(rename = 0x04; default, skip_serializing_if = Option::is_none)]
154        pub user: Option<PublicKeyCredentialUserEntity>,
155
156        /// Total number of account credentials for the RP. This member is required when more than
157        /// one account for the RP and the authenticator does not have a display. Omitted when
158        /// returned for the authenticatorGetNextAssertion method.
159        ///
160        /// It seems unlikely that more than 256 credentials would be needed for any given RP. Please
161        /// file an enhancement request if this limit impacts your application.
162        #[serde(rename = 0x05; default, skip_serializing_if = Option::is_none)]
163        pub number_of_credentials: Option<u8>,
164
165        /// Indicates that a credential was selected by the user via interaction directly with the authenticator,
166        /// and thus the platform does not need to confirm the credential.
167        /// Optional; defaults to false.
168        /// MUST NOT be present in response to a request where an [`Request::allow_list`] was given,
169        /// where [`Self::number_of_credentials`] is greater than one,
170        ///  nor in response to an `authenticatorGetNextAssertion` request.
171        #[serde(rename = 0x06; default, skip_serializing_if = Option::is_none)]
172        pub user_selected: Option<bool>,
173
174        /// The contents of the associated `largeBlobKey` if present for the asserted credential,
175        /// and if [largeBlobKey[] was true in the extensions input.
176        ///
177        /// This extension is currently un-supported by this library.
178        #[serde(rename = 0x07; default, skip_serializing_if = Option::is_none)]
179        pub large_blob_key: Option<Bytes>,
180
181        /// A map, keyed by extension identifiers, to unsigned outputs of extensions, if any.
182        /// Authenticators SHOULD omit this field if no processed extensions define unsigned outputs.
183        /// Clients MUST treat an empty map the same as an omitted field.
184        #[serde(rename = 0x08; default, skip_serializing_if = Option::is_none)]
185        pub unsigned_extension_outputs: Option<UnsignedExtensionOutputs>,
186    }
187}
188
189/// All supported Authenticator extensions outputs during credential assertion
190///
191/// This is to be serialized to [`Value`] in [`AuthenticatorData::extensions`]
192#[derive(Debug, Serialize, Deserialize)]
193pub struct SignedExtensionOutputs {
194    /// Outputs the symmetric secrets after successful processing. The output MUST be encrypted.
195    ///
196    /// <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#sctn-hmac-secret-extension>
197    #[serde(
198        rename = "hmac-secret",
199        default,
200        skip_serializing_if = "Option::is_none"
201    )]
202    pub hmac_secret: Option<Bytes>,
203}
204
205impl SignedExtensionOutputs {
206    /// Validates that there is at least one extension field that is `Some`.
207    /// If all fields are `None` then this returns `None` as well.
208    pub fn zip_contents(self) -> Option<Self> {
209        let Self { hmac_secret } = &self;
210        hmac_secret.is_some().then_some(self)
211    }
212}
213
214/// A map, keyed by extension identifiers, to unsigned outputs of extensions, if any.
215/// Authenticators SHOULD omit this field if no processed extensions define unsigned outputs.
216/// Clients MUST treat an empty map the same as an omitted field.
217#[derive(Debug, Serialize, Deserialize, Default)]
218#[serde(rename_all = "camelCase")]
219pub struct UnsignedExtensionOutputs {
220    /// This output is supported in the Webauthn specification and will be used when the authenticator
221    /// and the client are in memory or communicating through an internal channel.
222    ///
223    /// If you are using transports where this needs to pass through a wire, use hmac-secret instead.
224    pub prf: Option<AuthenticatorPrfGetOutputs>,
225}
226
227impl UnsignedExtensionOutputs {
228    /// Validates that there is at least one extension field that is `Some`.
229    /// If all fields are `None` then this returns `None` as well.
230    pub fn zip_contents(self) -> Option<Self> {
231        let Self { prf } = &self;
232        prf.is_some().then_some(self)
233    }
234}