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