Skip to main content

bitwarden_auth/login/models/
user_decryption_options_response.rs

1use bitwarden_core::key_management::{MasterPasswordError, MasterPasswordUnlockData};
2use serde::{Deserialize, Serialize};
3
4use crate::login::{
5    api::response::UserDecryptionOptionsApiResponse,
6    models::{
7        KeyConnectorUserDecryptionOption, TrustedDeviceUserDecryptionOption,
8        WebAuthnPrfUserDecryptionOption,
9    },
10};
11
12/// SDK domain model for user decryption options.
13/// Provides the various methods available to unlock a user's vault.
14#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
15#[serde(rename_all = "camelCase")]
16#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
17#[bitwarden_ffi::wasm_record]
18pub struct UserDecryptionOptionsResponse {
19    /// Master password unlock option. None if user doesn't have a master password.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub master_password_unlock: Option<MasterPasswordUnlockData>,
22
23    /// Trusted Device decryption option.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub trusted_device_option: Option<TrustedDeviceUserDecryptionOption>,
26
27    /// Key Connector decryption option.
28    /// Mutually exclusive with Trusted Device option.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub key_connector_option: Option<KeyConnectorUserDecryptionOption>,
31
32    /// WebAuthn PRF decryption option.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub webauthn_prf_option: Option<WebAuthnPrfUserDecryptionOption>,
35}
36
37impl TryFrom<UserDecryptionOptionsApiResponse> for UserDecryptionOptionsResponse {
38    type Error = MasterPasswordError;
39
40    fn try_from(api: UserDecryptionOptionsApiResponse) -> Result<Self, Self::Error> {
41        Ok(Self {
42            master_password_unlock: match api.master_password_unlock {
43                Some(ref mp) => Some(MasterPasswordUnlockData::try_from(mp)?),
44                None => None,
45            },
46            trusted_device_option: api.trusted_device_option.map(|tde| tde.into()),
47            key_connector_option: api.key_connector_option.map(|kc| kc.into()),
48            webauthn_prf_option: api.webauthn_prf_option.map(|wa| wa.into()),
49        })
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use bitwarden_api_api::models::{
56        KdfType, MasterPasswordUnlockKdfResponseModel, MasterPasswordUnlockResponseModel,
57    };
58    use bitwarden_crypto::Kdf;
59
60    use super::*;
61    use crate::login::api::response::{
62        KeyConnectorUserDecryptionOptionApiResponse, TrustedDeviceUserDecryptionOptionApiResponse,
63        WebAuthnPrfUserDecryptionOptionApiResponse,
64    };
65
66    const MASTER_KEY_ENCRYPTED_USER_KEY: &str = "2.Q/2PhzcC7GdeiMHhWguYAQ==|GpqzVdr0go0ug5cZh1n+uixeBC3oC90CIe0hd/HWA/pTRDZ8ane4fmsEIcuc8eMKUt55Y2q/fbNzsYu41YTZzzsJUSeqVjT8/iTQtgnNdpo=|dwI+uyvZ1h/iZ03VQ+/wrGEFYVewBUUl/syYgjsNMbE=";
67
68    #[test]
69    fn test_user_decryption_options_conversion_with_master_password() {
70        let api = UserDecryptionOptionsApiResponse {
71            master_password_unlock: Some(MasterPasswordUnlockResponseModel {
72                kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
73                    kdf_type: KdfType::PBKDF2_SHA256,
74                    iterations: 600000,
75                    memory: None,
76                    parallelism: None,
77                }),
78                master_key_encrypted_user_key: Some(MASTER_KEY_ENCRYPTED_USER_KEY.to_string()),
79                salt: Some("[email protected]".to_string()),
80                contained_key_id: None,
81            }),
82            trusted_device_option: None,
83            key_connector_option: None,
84            webauthn_prf_option: None,
85        };
86
87        let domain: UserDecryptionOptionsResponse = api.try_into().unwrap();
88
89        assert!(domain.master_password_unlock.is_some());
90        let mp_unlock = domain.master_password_unlock.unwrap();
91        assert_eq!(mp_unlock.salt, "[email protected]");
92        match mp_unlock.kdf {
93            Kdf::PBKDF2 { iterations } => {
94                assert_eq!(iterations.get(), 600000);
95            }
96            _ => panic!("Expected PBKDF2 KDF"),
97        }
98        assert!(domain.trusted_device_option.is_none());
99        assert!(domain.key_connector_option.is_none());
100        assert!(domain.webauthn_prf_option.is_none());
101    }
102
103    #[test]
104    fn test_user_decryption_options_conversion_with_all_options() {
105        // Test data constants
106        const SALT: &str = "[email protected]";
107        const KDF_ITERATIONS: u32 = 600000;
108        const TDE_ENCRYPTED_PRIVATE_KEY: &str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
109        const TDE_ENCRYPTED_USER_KEY: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw==";
110        const KEY_CONNECTOR_URL: &str = "https://key-connector.bitwarden.com";
111        const WEBAUTHN_ENCRYPTED_PRIVATE_KEY: &str = "2.fkvl0+sL1lwtiOn1eewsvQ==|dT0TynLl8YERZ8x7dxC+DQ==|cWhiRSYHOi/AA2LiV/JBJWbO9C7pbUpOM6TMAcV47hE=";
112        const WEBAUTHN_ENCRYPTED_USER_KEY: &str = "4.DMD1D5r6BsDDd7C/FE1eZbMCKrmryvAsCKj6+bO54gJNUxisOI7SDcpPLRXf+JdhqY15pT+wimQ5cD9C+6OQ6s71LFQHewXPU29l9Pa1JxGeiKqp37KLYf+1IS6UB2K3ANN35C52ZUHh2TlzIS5RuntxnpCw7APbcfpcnmIdLPJBtuj/xbFd6eBwnI3GSe5qdS6/Ixdd0dgsZcpz3gHJBKmIlSo0YN60SweDq3kTJwox9xSqdCueIDg5U4khc7RhjYx8b33HXaNJj3DwgIH8iLj+lqpDekogr630OhHG3XRpvl4QzYO45bmHb8wAh67Dj70nsZcVg6bAEFHdSFohww==";
113
114        let api = UserDecryptionOptionsApiResponse {
115            master_password_unlock: Some(MasterPasswordUnlockResponseModel {
116                kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
117                    kdf_type: KdfType::PBKDF2_SHA256,
118                    iterations: KDF_ITERATIONS as i32,
119                    memory: None,
120                    parallelism: None,
121                }),
122                master_key_encrypted_user_key: Some(MASTER_KEY_ENCRYPTED_USER_KEY.to_string()),
123                salt: Some(SALT.to_string()),
124                contained_key_id: None,
125            }),
126            // Note: the trusted device option && the key connector option are mutually exclusive
127            // from the server, but this test is just verifying that the conversion logic works
128            // for all option types.
129            trusted_device_option: Some(TrustedDeviceUserDecryptionOptionApiResponse {
130                has_admin_approval: true,
131                has_login_approving_device: false,
132                has_manage_reset_password_permission: false,
133                is_tde_offboarding: false,
134                encrypted_private_key: Some(TDE_ENCRYPTED_PRIVATE_KEY.parse().unwrap()),
135                encrypted_user_key: Some(TDE_ENCRYPTED_USER_KEY.parse().unwrap()),
136            }),
137            key_connector_option: Some(KeyConnectorUserDecryptionOptionApiResponse {
138                key_connector_url: KEY_CONNECTOR_URL.to_string(),
139            }),
140            webauthn_prf_option: Some(WebAuthnPrfUserDecryptionOptionApiResponse {
141                encrypted_private_key: WEBAUTHN_ENCRYPTED_PRIVATE_KEY.parse().unwrap(),
142                encrypted_user_key: WEBAUTHN_ENCRYPTED_USER_KEY.parse().unwrap(),
143                credential_id: None,
144                transports: None,
145            }),
146        };
147
148        let domain: UserDecryptionOptionsResponse = api.try_into().unwrap();
149
150        // Verify master password unlock
151        assert!(domain.master_password_unlock.is_some());
152        let mp_unlock = domain.master_password_unlock.unwrap();
153        assert_eq!(mp_unlock.salt, SALT);
154        match mp_unlock.kdf {
155            Kdf::PBKDF2 { iterations } => {
156                assert_eq!(iterations.get(), KDF_ITERATIONS);
157            }
158            _ => panic!("Expected PBKDF2 KDF"),
159        }
160
161        // Verify trusted device option
162        assert!(domain.trusted_device_option.is_some());
163        let tde = domain.trusted_device_option.unwrap();
164        assert!(tde.has_admin_approval);
165        assert!(!tde.has_login_approving_device);
166        assert!(!tde.has_manage_reset_password_permission);
167        assert!(!tde.is_tde_offboarding);
168        assert_eq!(
169            tde.encrypted_private_key,
170            Some(TDE_ENCRYPTED_PRIVATE_KEY.parse().unwrap())
171        );
172        assert_eq!(
173            tde.encrypted_user_key,
174            Some(TDE_ENCRYPTED_USER_KEY.parse().unwrap())
175        );
176
177        // Verify key connector option
178        assert!(domain.key_connector_option.is_some());
179        let kc = domain.key_connector_option.unwrap();
180        assert_eq!(kc.key_connector_url, KEY_CONNECTOR_URL);
181
182        // Verify webauthn prf option
183        assert!(domain.webauthn_prf_option.is_some());
184        let webauthn = domain.webauthn_prf_option.unwrap();
185        assert_eq!(
186            webauthn.encrypted_private_key,
187            WEBAUTHN_ENCRYPTED_PRIVATE_KEY.parse().unwrap()
188        );
189        assert_eq!(
190            webauthn.encrypted_user_key,
191            WEBAUTHN_ENCRYPTED_USER_KEY.parse().unwrap()
192        );
193        assert_eq!(webauthn.credential_id, None);
194        assert_eq!(webauthn.transports, None);
195    }
196
197    #[test]
198    fn test_user_decryption_options_with_trusted_device_only() {
199        const TDE_ENCRYPTED_PRIVATE_KEY: &str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
200        const TDE_ENCRYPTED_USER_KEY: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw==";
201
202        let api = UserDecryptionOptionsApiResponse {
203            master_password_unlock: None,
204            trusted_device_option: Some(TrustedDeviceUserDecryptionOptionApiResponse {
205                has_admin_approval: false,
206                has_login_approving_device: true,
207                has_manage_reset_password_permission: false,
208                is_tde_offboarding: false,
209                encrypted_private_key: Some(TDE_ENCRYPTED_PRIVATE_KEY.parse().unwrap()),
210                encrypted_user_key: Some(TDE_ENCRYPTED_USER_KEY.parse().unwrap()),
211            }),
212            key_connector_option: None,
213            webauthn_prf_option: None,
214        };
215
216        let domain: UserDecryptionOptionsResponse = api.try_into().unwrap();
217
218        assert!(domain.master_password_unlock.is_none());
219        assert!(domain.trusted_device_option.is_some());
220        assert!(domain.key_connector_option.is_none());
221        assert!(domain.webauthn_prf_option.is_none());
222
223        let tde = domain.trusted_device_option.unwrap();
224        assert!(!tde.has_admin_approval);
225        assert!(tde.has_login_approving_device);
226        assert_eq!(
227            tde.encrypted_private_key,
228            Some(TDE_ENCRYPTED_PRIVATE_KEY.parse().unwrap())
229        );
230        assert_eq!(
231            tde.encrypted_user_key,
232            Some(TDE_ENCRYPTED_USER_KEY.parse().unwrap())
233        );
234    }
235
236    #[test]
237    fn test_user_decryption_options_with_key_connector_only() {
238        const KEY_CONNECTOR_URL: &str = "https://key-connector.example.com";
239
240        let api = UserDecryptionOptionsApiResponse {
241            master_password_unlock: None,
242            trusted_device_option: None,
243            key_connector_option: Some(KeyConnectorUserDecryptionOptionApiResponse {
244                key_connector_url: KEY_CONNECTOR_URL.to_string(),
245            }),
246            webauthn_prf_option: None,
247        };
248
249        let domain: UserDecryptionOptionsResponse = api.try_into().unwrap();
250
251        assert!(domain.master_password_unlock.is_none());
252        assert!(domain.trusted_device_option.is_none());
253        assert!(domain.key_connector_option.is_some());
254        assert!(domain.webauthn_prf_option.is_none());
255
256        let kc = domain.key_connector_option.unwrap();
257        assert_eq!(kc.key_connector_url, KEY_CONNECTOR_URL);
258    }
259
260    #[test]
261    fn test_user_decryption_options_with_webauthn_prf_only() {
262        const WEBAUTHN_ENCRYPTED_PRIVATE_KEY: &str = "2.fkvl0+sL1lwtiOn1eewsvQ==|dT0TynLl8YERZ8x7dxC+DQ==|cWhiRSYHOi/AA2LiV/JBJWbO9C7pbUpOM6TMAcV47hE=";
263        const WEBAUTHN_ENCRYPTED_USER_KEY: &str = "4.DMD1D5r6BsDDd7C/FE1eZbMCKrmryvAsCKj6+bO54gJNUxisOI7SDcpPLRXf+JdhqY15pT+wimQ5cD9C+6OQ6s71LFQHewXPU29l9Pa1JxGeiKqp37KLYf+1IS6UB2K3ANN35C52ZUHh2TlzIS5RuntxnpCw7APbcfpcnmIdLPJBtuj/xbFd6eBwnI3GSe5qdS6/Ixdd0dgsZcpz3gHJBKmIlSo0YN60SweDq3kTJwox9xSqdCueIDg5U4khc7RhjYx8b33HXaNJj3DwgIH8iLj+lqpDekogr630OhHG3XRpvl4QzYO45bmHb8wAh67Dj70nsZcVg6bAEFHdSFohww==";
264
265        let api = UserDecryptionOptionsApiResponse {
266            master_password_unlock: None,
267            trusted_device_option: None,
268            key_connector_option: None,
269            webauthn_prf_option: Some(WebAuthnPrfUserDecryptionOptionApiResponse {
270                encrypted_private_key: WEBAUTHN_ENCRYPTED_PRIVATE_KEY.parse().unwrap(),
271                encrypted_user_key: WEBAUTHN_ENCRYPTED_USER_KEY.parse().unwrap(),
272                credential_id: None,
273                transports: None,
274            }),
275        };
276
277        let domain: UserDecryptionOptionsResponse = api.try_into().unwrap();
278
279        assert!(domain.master_password_unlock.is_none());
280        assert!(domain.trusted_device_option.is_none());
281        assert!(domain.key_connector_option.is_none());
282        assert!(domain.webauthn_prf_option.is_some());
283
284        let webauthn = domain.webauthn_prf_option.unwrap();
285        assert_eq!(
286            webauthn.encrypted_private_key,
287            WEBAUTHN_ENCRYPTED_PRIVATE_KEY.parse().unwrap()
288        );
289        assert_eq!(
290            webauthn.encrypted_user_key,
291            WEBAUTHN_ENCRYPTED_USER_KEY.parse().unwrap()
292        );
293        assert_eq!(webauthn.credential_id, None);
294        assert_eq!(webauthn.transports, None);
295    }
296
297    #[test]
298    fn test_user_decryption_options_with_no_options() {
299        let api = UserDecryptionOptionsApiResponse {
300            master_password_unlock: None,
301            trusted_device_option: None,
302            key_connector_option: None,
303            webauthn_prf_option: None,
304        };
305
306        let domain: UserDecryptionOptionsResponse = api.try_into().unwrap();
307
308        assert!(domain.master_password_unlock.is_none());
309        assert!(domain.trusted_device_option.is_none());
310        assert!(domain.key_connector_option.is_none());
311        assert!(domain.webauthn_prf_option.is_none());
312    }
313
314    #[test]
315    fn test_user_decryption_options_with_master_password_and_trusted_device() {
316        const TDE_ENCRYPTED_PRIVATE_KEY: &str = "2.pMS6/icTQABtulw52pq2lg==|XXbxKxDTh+mWiN1HjH2N1w==|Q6PkuT+KX/axrgN9ubD5Ajk2YNwxQkgs3WJM0S0wtG8=";
317        const TDE_ENCRYPTED_USER_KEY: &str = "4.ZheRb3PCfAunyFdQYPfyrFqpuvmln9H9w5nDjt88i5A7ug1XE0LJdQHCIYJl0YOZ1gCOGkhFu/CRY2StiLmT3iRKrrVBbC1+qRMjNNyDvRcFi91LWsmRXhONVSPjywzrJJXglsztDqGkLO93dKXNhuKpcmtBLsvgkphk/aFvxbaOvJ/FHdK/iV0dMGNhc/9tbys8laTdwBlI5xIChpRcrfH+XpSFM88+Bu03uK67N9G6eU1UmET+pISJwJvMuIDMqH+qkT7OOzgL3t6I0H2LDj+CnsumnQmDsvQzDiNfTR0IgjpoE9YH2LvPXVP2wVUkiTwXD9cG/E7XeoiduHyHjw==";
318
319        let api = UserDecryptionOptionsApiResponse {
320            master_password_unlock: Some(MasterPasswordUnlockResponseModel {
321                kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
322                    kdf_type: KdfType::PBKDF2_SHA256,
323                    iterations: 600000,
324                    memory: None,
325                    parallelism: None,
326                }),
327                master_key_encrypted_user_key: Some(MASTER_KEY_ENCRYPTED_USER_KEY.to_string()),
328                salt: Some("[email protected]".to_string()),
329                contained_key_id: None,
330            }),
331            trusted_device_option: Some(TrustedDeviceUserDecryptionOptionApiResponse {
332                has_admin_approval: true,
333                has_login_approving_device: false,
334                has_manage_reset_password_permission: true,
335                is_tde_offboarding: false,
336                encrypted_private_key: Some(TDE_ENCRYPTED_PRIVATE_KEY.parse().unwrap()),
337                encrypted_user_key: Some(TDE_ENCRYPTED_USER_KEY.parse().unwrap()),
338            }),
339            key_connector_option: None,
340            webauthn_prf_option: None,
341        };
342
343        let domain: UserDecryptionOptionsResponse = api.try_into().unwrap();
344
345        assert!(domain.master_password_unlock.is_some());
346        assert!(domain.trusted_device_option.is_some());
347        assert!(domain.key_connector_option.is_none());
348        assert!(domain.webauthn_prf_option.is_none());
349    }
350
351    #[test]
352    fn test_user_decryption_options_with_master_password_and_key_connector() {
353        const KEY_CONNECTOR_URL: &str = "https://key-connector.example.com";
354
355        let api = UserDecryptionOptionsApiResponse {
356            master_password_unlock: Some(MasterPasswordUnlockResponseModel {
357                kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
358                    kdf_type: KdfType::PBKDF2_SHA256,
359                    iterations: 600000,
360                    memory: None,
361                    parallelism: None,
362                }),
363                master_key_encrypted_user_key: Some(MASTER_KEY_ENCRYPTED_USER_KEY.to_string()),
364                salt: Some("[email protected]".to_string()),
365                contained_key_id: None,
366            }),
367            trusted_device_option: None,
368            key_connector_option: Some(KeyConnectorUserDecryptionOptionApiResponse {
369                key_connector_url: KEY_CONNECTOR_URL.to_string(),
370            }),
371            webauthn_prf_option: None,
372        };
373
374        let domain: UserDecryptionOptionsResponse = api.try_into().unwrap();
375
376        assert!(domain.master_password_unlock.is_some());
377        assert!(domain.trusted_device_option.is_none());
378        assert!(domain.key_connector_option.is_some());
379        assert!(domain.webauthn_prf_option.is_none());
380    }
381
382    #[test]
383    fn test_user_decryption_options_with_master_password_and_webauthn_prf() {
384        const WEBAUTHN_ENCRYPTED_PRIVATE_KEY: &str = "2.fkvl0+sL1lwtiOn1eewsvQ==|dT0TynLl8YERZ8x7dxC+DQ==|cWhiRSYHOi/AA2LiV/JBJWbO9C7pbUpOM6TMAcV47hE=";
385        const WEBAUTHN_ENCRYPTED_USER_KEY: &str = "4.DMD1D5r6BsDDd7C/FE1eZbMCKrmryvAsCKj6+bO54gJNUxisOI7SDcpPLRXf+JdhqY15pT+wimQ5cD9C+6OQ6s71LFQHewXPU29l9Pa1JxGeiKqp37KLYf+1IS6UB2K3ANN35C52ZUHh2TlzIS5RuntxnpCw7APbcfpcnmIdLPJBtuj/xbFd6eBwnI3GSe5qdS6/Ixdd0dgsZcpz3gHJBKmIlSo0YN60SweDq3kTJwox9xSqdCueIDg5U4khc7RhjYx8b33HXaNJj3DwgIH8iLj+lqpDekogr630OhHG3XRpvl4QzYO45bmHb8wAh67Dj70nsZcVg6bAEFHdSFohww==";
386
387        let api = UserDecryptionOptionsApiResponse {
388            master_password_unlock: Some(MasterPasswordUnlockResponseModel {
389                kdf: Box::new(MasterPasswordUnlockKdfResponseModel {
390                    kdf_type: KdfType::PBKDF2_SHA256,
391                    iterations: 600000,
392                    memory: None,
393                    parallelism: None,
394                }),
395                master_key_encrypted_user_key: Some(MASTER_KEY_ENCRYPTED_USER_KEY.to_string()),
396                salt: Some("[email protected]".to_string()),
397                contained_key_id: None,
398            }),
399            trusted_device_option: None,
400            key_connector_option: None,
401            webauthn_prf_option: Some(WebAuthnPrfUserDecryptionOptionApiResponse {
402                encrypted_private_key: WEBAUTHN_ENCRYPTED_PRIVATE_KEY.parse().unwrap(),
403                encrypted_user_key: WEBAUTHN_ENCRYPTED_USER_KEY.parse().unwrap(),
404                credential_id: None,
405                transports: None,
406            }),
407        };
408
409        let domain: UserDecryptionOptionsResponse = api.try_into().unwrap();
410
411        assert!(domain.master_password_unlock.is_some());
412        assert!(domain.trusted_device_option.is_none());
413        assert!(domain.key_connector_option.is_none());
414        assert!(domain.webauthn_prf_option.is_some());
415    }
416}