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