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}