1use bitwarden_api_api::models::{
2 AuthenticatorAttestationRawResponse, CredentialCreateOptions, PublicKeyCredentialType,
3 ResponseData, SecretVerificationRequestModel, UserVerificationRequirement,
4 WebAuthnCredentialCreateOptionsResponseModel, WebAuthnLoginCredentialCreateRequestModel,
5};
6use bitwarden_core::{
7 Client, key_management::SymmetricKeySlotId, mobile::KdfClient,
8 platform::SecretVerificationRequest,
9};
10use bitwarden_crypto::{HashPurpose, Kdf, RotateableKeySet};
11use chrono::{DateTime, Utc};
12use coset::{CborSerializable, CoseKey};
13use passkey::{
14 authenticator::{
15 DiscoverabilitySupport, StoreInfo, UiHint, UserCheck, extensions::HmacSecretConfig,
16 },
17 types::{
18 CredentialExtensions, Passkey, StoredHmacSecret,
19 crypto::sha256,
20 ctap2::{
21 self, Ctap2Code, Ctap2Error, StatusCode, VendorError,
22 extensions::{AuthenticatorPrfInputs, AuthenticatorPrfValues},
23 make_credential::Options,
24 },
25 },
26};
27use reqwest::Url;
28
29use crate::{
30 GetAssertionRequest, MakeCredentialResult, PublicKeyCredentialRpEntity,
31 PublicKeyCredentialUserEntity,
32 types::{
33 GetAssertionExtensionsOutput, PublicKeyCredentialDescriptor, PublicKeyCredentialParameters,
34 UV, WebAuthnEntityError,
35 },
36};
37
38pub struct DeviceAuthKeyAuthenticator<'a> {
40 pub client: &'a Client,
42
43 pub store: &'a mut dyn DeviceAuthKeyStore,
45}
46
47impl DeviceAuthKeyAuthenticator<'_> {
48 pub async fn create_device_auth_key(
52 &mut self,
53 client_name: String,
54 web_vault_url: String,
55 email: String,
64 secret_verification_request: SecretVerificationRequest,
65 kdf_params: Kdf,
66 ) -> Result<(), DeviceAuthKeyError> {
67 let config = self.client.internal.get_api_configurations();
69 let api_client = &config.api_client;
70
71 let secret_verification_request_model = build_secret_verification_request(
73 &secret_verification_request,
74 email,
75 kdf_params,
76 &self.client.kdf(),
77 )
78 .await?;
79 let options_response = api_client
80 .web_authn_api()
81 .attestation_options(Some(secret_verification_request_model))
82 .await
83 .map_err(|err| {
84 tracing::error!(%err, "Failed to retrieve attestation options");
85 DeviceAuthKeyError::RetrieveRegistrationOptionsFailure
86 })?;
87 let WebAuthnCredentialCreateOptionsResponseModel { options, token, .. } = options_response;
88
89 let (default_rp_id, origin) = {
91 let url =
92 Url::parse(&web_vault_url).map_err(|_| DeviceAuthKeyError::InvalidWebVaultUrl)?;
93 let Some(default_rp_id) = url.host().map(|host| host.to_string()) else {
94 return Err(DeviceAuthKeyError::InvalidWebVaultUrl);
95 };
96 let origin = url.origin().ascii_serialization();
97 (default_rp_id, origin)
98 };
99 let (request, client_data_json) = convert_creation_options(options.as_ref(), default_rp_id, origin).map_err(|err| {
100 tracing::error!(%err, ?options, "Received invalid WebAuthn attestation options from server");
101 DeviceAuthKeyError::RetrieveRegistrationOptionsFailure
102 })?;
103
104 let rp_id = request.rp.id.clone();
106 let user_handle = request.user.id.to_vec();
107 let user_name = request.user.name.clone();
108 let user_display_name = request.user.display_name.clone();
109
110 let store = DeviceAuthKeyStoreInternal { store: self.store };
112 let ui = DeviceAuthKeyUiInternal {};
113 let mut authenticator =
114 passkey::authenticator::Authenticator::new(super::AAGUID, store, ui)
115 .hmac_secret(HmacSecretConfig::new_with_uv_only().enable_on_make_credential());
116 let response = authenticator
117 .make_credential(request)
118 .await
119 .map_err(|status_code| {
120 tracing::error!(?status_code, "Failed to make FIDO credential");
121 if let StatusCode::Ctap2(Ctap2Code::Known(Ctap2Error::CredentialExcluded)) =
122 status_code
123 {
124 DeviceAuthKeyError::CredentialExcluded
125 } else {
126 DeviceAuthKeyError::AuthenticatorFailure
127 }
128 })?;
129
130 let result: MakeCredentialResult = response
132 .try_into()
133 .map_err(|_| DeviceAuthKeyError::AuthenticatorFailure)?;
134
135 let prf_result = result
137 .extensions
138 .prf
139 .and_then(|prf| prf.results)
140 .ok_or_else(|| {
141 tracing::error!("No PRF output received from authenticator response");
142 DeviceAuthKeyError::PrfFailure
143 })?
144 .first;
145 let prf_key =
146 bitwarden_crypto::derive_symmetric_key_from_prf(&prf_result).map_err(|err| {
147 tracing::error!(?err, "Failed to derive symmetric key from PRF output");
148 DeviceAuthKeyError::PrfFailure
149 })?;
150 let key_set = {
151 let ctx = self.client.internal.get_key_store().context();
152 RotateableKeySet::new(&ctx, &prf_key, SymmetricKeySlotId::User).map_err(|err| {
153 tracing::error!(%err, "Failed to gen/Conerate rotateable key set from PRF output");
154 DeviceAuthKeyError::PrfFailure
155 })?
156 };
157
158 let credential_id = result.credential_id.clone();
160 let create_request = WebAuthnLoginCredentialCreateRequestModel {
161 device_response: Box::new(AuthenticatorAttestationRawResponse {
162 id: Some(result.credential_id.clone()),
163 raw_id: Some(result.credential_id),
164 r#type: Some(PublicKeyCredentialType::PublicKey),
165 response: Some(Box::new(ResponseData {
166 attestation_object: Some(result.attestation_object),
167 client_data_json: Some(client_data_json.into_bytes()),
168 })),
169 extensions: None,
170 }),
171 name: client_name,
172 token,
173 supports_prf: true,
174 encrypted_user_key: Some(key_set.encapsulated_downstream_key.to_string()),
175 encrypted_public_key: Some(key_set.encrypted_encapsulation_key.to_string()),
176 encrypted_private_key: Some(key_set.encrypted_decapsulation_key.to_string()),
177 };
178 let server_response = api_client
179 .web_authn_api()
180 .post(Some(create_request))
181 .await
182 .map_err(|err| {
183 tracing::error!(%err, "Failed to submit passkey and PRF key set to server");
184 DeviceAuthKeyError::SubmitRegistrationFailure
185 })?;
186 let record_identifier = server_response
187 .id
188 .ok_or(DeviceAuthKeyError::SubmitRegistrationFailure)?;
189
190 let metadata = DeviceAuthKeyMetadata {
192 record_identifier,
193 creation_date: chrono::offset::Utc::now(),
194 credential_id,
195 rp_id,
196 user_handle,
197 user_name,
198 user_display_name,
199 };
200 self.store.create_metadata(metadata).await.map_err(|err| {
201 tracing::error!(%err, "Failed to save device auth key metadata");
202 err
203 })?;
204 Ok(())
205 }
206
207 pub async fn assert_device_auth_key(
211 &mut self,
212 request: GetAssertionRequest,
213 ) -> Result<DeviceAuthKeyGetAssertionResult, DeviceAuthKeyError> {
214 let request = ctap2::get_assertion::Request {
216 rp_id: request.rp_id,
217 client_data_hash: request.client_data_hash.into(),
218 allow_list: request
219 .allow_list
220 .map(|l| {
221 l.into_iter()
222 .map(TryInto::try_into)
223 .collect::<Result<Vec<_>, _>>()
224 .map_err(|_| DeviceAuthKeyError::InvalidPublicKeyCredentialDescriptor)
225 })
226 .transpose()?,
227 extensions: request
228 .extensions
229 .map(passkey::types::ctap2::get_assertion::ExtensionInputs::from),
230 options: passkey::types::ctap2::make_credential::Options {
231 rk: request.options.rk,
232 up: true,
233 uv: match request.options.uv {
234 UV::Discouraged => false,
235 UV::Preferred => true,
236 UV::Required => true,
237 },
238 },
239 pin_auth: None,
240 pin_protocol: None,
241 };
242
243 let requested_cred_id = if let Some([cred]) = request.allow_list.as_deref() {
245 Some(cred.id.to_vec())
246 } else {
247 None
248 };
249
250 let store = DeviceAuthKeyStoreInternal { store: self.store };
252 let ui = DeviceAuthKeyUiInternal {};
253 let mut authenticator =
254 passkey::authenticator::Authenticator::new(super::AAGUID, store, ui)
255 .hmac_secret(HmacSecretConfig::new_with_uv_only().enable_on_make_credential());
256 let response = authenticator
257 .get_assertion(request)
258 .await
259 .map_err(|status_code| {
260 tracing::error!(?status_code, "Authenticator failed to assert credential");
261 DeviceAuthKeyError::AuthenticatorFailure
262 })?;
263
264 let authenticator_data = response.auth_data.to_vec();
266 let credential_id = response
272 .credential
273 .map(|cred| cred.id.to_vec())
274 .or(requested_cred_id)
275 .ok_or(DeviceAuthKeyError::MissingCredentialId)?;
276 let extensions: GetAssertionExtensionsOutput = response.unsigned_extension_outputs.into();
277 let user_handle = response
278 .user
279 .map(|u| u.id.to_vec())
280 .ok_or(DeviceAuthKeyError::MissingUserHandle)?;
281 Ok(DeviceAuthKeyGetAssertionResult {
282 credential_id,
283 authenticator_data,
284 signature: response.signature.to_vec(),
285 user_handle,
286 extensions,
287 })
288 }
289
290 pub async fn unregister_device_auth_key(
292 &mut self,
293 email: String,
294 secret_verification_request: SecretVerificationRequest,
295 kdf_params: Kdf,
296 ) -> Result<(), DeviceAuthKeyError> {
297 let metadata = self
299 .store
300 .get_metadata()
301 .await?
302 .ok_or(DeviceAuthKeyError::MissingDeviceAuthKey)?;
303
304 self.store.delete_record_and_metadata().await?;
305
306 let record_id = metadata
307 .record_identifier
308 .parse::<uuid::Uuid>()
309 .map_err(|err| {
310 tracing::error!(%err, "Failed to parse record identifier as UUID");
311 DeviceAuthKeyError::InvalidRecordIdentifier
312 })?;
313
314 let config = self.client.internal.get_api_configurations();
316 let api_client = &config.api_client;
317 let secret_verification_request_model = build_secret_verification_request(
318 &secret_verification_request,
319 email,
320 kdf_params,
321 &self.client.kdf(),
322 )
323 .await?;
324 api_client
325 .web_authn_api()
326 .delete(record_id, Some(secret_verification_request_model))
327 .await
328 .map_err(|err| {
329 tracing::error!(%err, "Failed to unregister device auth key from server");
330 DeviceAuthKeyError::UnregisterFailure
331 })?;
332
333 Ok(())
334 }
335}
336
337async fn build_secret_verification_request(
338 input: &SecretVerificationRequest,
339 email: String,
340 kdf_params: Kdf,
341 kdf_client: &KdfClient,
342) -> Result<SecretVerificationRequestModel, DeviceAuthKeyError> {
343 let master_password_hash = if let Some(master_password) = &input.master_password {
344 Some(
345 kdf_client
346 .hash_password(
347 email,
348 master_password.to_string(),
349 kdf_params,
350 HashPurpose::ServerAuthorization,
351 )
352 .await
353 .map_err(|_| DeviceAuthKeyError::MasterPasswordHash)?
354 .to_string(),
355 )
356 } else {
357 None
358 };
359
360 Ok(SecretVerificationRequestModel {
361 master_password_hash,
362 otp: input.otp.clone(),
363 auth_request_access_code: None,
364 secret: None,
365 })
366}
367
368fn convert_creation_options(
372 options: &CredentialCreateOptions,
373 default_rp_id: String,
374 origin: String,
375) -> Result<(passkey::types::ctap2::make_credential::Request, String), WebAuthnEntityError> {
376 let mut missing_fields = Vec::with_capacity(0);
377 if options.rp.is_none() {
378 missing_fields.push("rp".to_string());
379 }
380 if options.user.is_none() {
381 missing_fields.push("user".to_string());
382 }
383 if options.challenge.is_none() {
384 missing_fields.push("challenge".to_string());
385 }
386 if options.pub_key_cred_params.is_none() {
387 missing_fields.push("pubKeyCredParams".to_string());
388 }
389 if !missing_fields.is_empty() {
390 return Err(WebAuthnEntityError::MissingRequiredFields(missing_fields));
391 }
392
393 let CredentialCreateOptions {
394 rp: Some(rp),
395 user: Some(user),
396 challenge: Some(challenge),
397 pub_key_cred_params: Some(pub_key_cred_params),
398 authenticator_selection,
399 exclude_credentials,
400 extensions,
401 ..
402 } = options
403 else {
404 unreachable!("Missing required fields on options");
406 };
407
408 let challenge_b64 = bitwarden_encoding::B64Url::from(challenge.as_ref())
409 .to_string()
410 .trim_end_matches('=')
411 .to_string();
412 let client_data_json = format!(
413 r#"{{"type":"webauthn.create","challenge":"{}","origin":"{}","crossOrigin":false}}"#,
414 challenge_b64, origin
415 );
416 let client_data_hash = passkey::types::crypto::sha256(client_data_json.as_bytes()).to_vec();
417
418 let mut rp = rp.clone();
420 rp.id.get_or_insert(default_rp_id);
421 let rp = TryInto::<PublicKeyCredentialRpEntity>::try_into(rp.as_ref())?.into();
422
423 let user_entity = TryInto::<PublicKeyCredentialUserEntity>::try_into(user.as_ref())?.into();
424 let pub_key_cred_params = pub_key_cred_params
425 .iter()
426 .map(|p| {
427 PublicKeyCredentialParameters::try_from(p).and_then(|ours| {
428 passkey::types::webauthn::PublicKeyCredentialParameters::try_from(ours)
429 })
430 })
431 .collect::<Result<Vec<passkey::types::webauthn::PublicKeyCredentialParameters>, _>>()?;
432 let exclude_list = exclude_credentials
433 .as_ref()
434 .map(|l| {
435 l.iter()
436 .map(|c| {
437 let descriptor = PublicKeyCredentialDescriptor::try_from(c);
438
439 descriptor.and_then(|c| c.try_into().map_err(WebAuthnEntityError::from))
440 })
441 .collect()
442 })
443 .transpose()?;
444 let authenticator_options = authenticator_selection
445 .as_ref()
446 .map(|o| Options {
447 rk: o.require_resident_key.unwrap_or_default(),
448 uv: !matches!(
449 o.user_verification,
450 Some(UserVerificationRequirement::Discouraged)
451 ),
452 up: true,
453 })
454 .unwrap_or_else(|| Options {
455 rk: false,
456 uv: true,
457 up: true,
458 });
459
460 let prf_input = AuthenticatorPrfInputs {
462 eval: Some(AuthenticatorPrfValues {
463 first: sha256("passwordless-login".as_bytes()),
464 second: None,
465 }),
466 eval_by_credential: None,
467 };
468
469 let request = passkey::types::ctap2::make_credential::Request {
470 client_data_hash: client_data_hash.into(),
471 rp,
472 user: user_entity,
473 pub_key_cred_params,
474 exclude_list,
475 options: authenticator_options,
476 extensions: extensions
477 .as_ref()
478 .map(|_| ctap2::make_credential::ExtensionInputs {
479 hmac_secret: None,
480 hmac_secret_mc: None,
481 prf: Some(prf_input),
482 }),
483 pin_auth: None,
484 pin_protocol: None,
485 };
486 Ok((request, client_data_json))
487}
488
489#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
498pub struct DeviceAuthKeyGetAssertionResult {
499 pub credential_id: Vec<u8>,
503
504 pub authenticator_data: Vec<u8>,
506
507 pub signature: Vec<u8>,
509
510 pub user_handle: Vec<u8>,
512
513 pub extensions: GetAssertionExtensionsOutput,
516}
517
518#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
522pub struct DeviceAuthKeyRecord {
523 pub credential_id: Vec<u8>,
525
526 pub key: Vec<u8>,
528
529 pub rp_id: String,
531
532 pub user_id: Vec<u8>,
534
535 pub counter: Option<u32>,
537
538 pub hmac_secret: Vec<u8>,
540}
541
542impl TryFrom<Passkey> for DeviceAuthKeyRecord {
543 type Error = DeviceAuthKeyError;
544 fn try_from(value: Passkey) -> Result<Self, Self::Error> {
545 let credential_id = value.credential_id.to_vec();
546 let key = value.key.to_vec().map_err(|err| {
547 tracing::error!(%err, "Failed to serialize COSE key to bytes.");
548 DeviceAuthKeyError::InvalidCoseKey
549 })?;
550 let user_id = value
551 .user_handle
552 .ok_or(DeviceAuthKeyError::MissingUserHandle)?
553 .to_vec();
554 let hmac_secret = value
555 .extensions
556 .hmac_secret
557 .as_ref()
558 .ok_or(DeviceAuthKeyError::MissingHmacSecret)?
559 .cred_with_uv
560 .clone();
561 Ok(DeviceAuthKeyRecord {
562 credential_id,
563 key,
564 rp_id: value.rp_id,
565 user_id,
566 counter: value.counter,
567 hmac_secret,
568 })
569 }
570}
571
572impl TryFrom<DeviceAuthKeyRecord> for Passkey {
573 type Error = DeviceAuthKeyError;
574 fn try_from(value: DeviceAuthKeyRecord) -> Result<Self, Self::Error> {
575 Ok(Passkey {
576 credential_id: value.credential_id.into(),
577 key: CoseKey::from_slice(&value.key).map_err(|err| {
578 tracing::error!(%err, "Failed to deserialize COSE key from bytes");
579 DeviceAuthKeyError::InvalidCoseKey
580 })?,
581 rp_id: value.rp_id,
582 user_handle: Some(value.user_id.into()),
583 counter: value.counter,
584 extensions: CredentialExtensions {
585 hmac_secret: Some(StoredHmacSecret {
586 cred_with_uv: value.hmac_secret,
587 cred_without_uv: None,
588 }),
589 },
590 })
591 }
592}
593
594#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
598pub struct DeviceAuthKeyMetadata {
599 pub record_identifier: String,
602
603 pub creation_date: DateTime<Utc>,
605
606 pub credential_id: Vec<u8>,
608
609 pub rp_id: String,
611
612 pub user_name: String,
617
618 pub user_handle: Vec<u8>,
622
623 pub user_display_name: String,
628}
629
630#[derive(Debug, thiserror::Error)]
632#[cfg_attr(feature = "uniffi", derive(uniffi::Error), uniffi(flat_error))]
633pub enum DeviceAuthKeyError {
634 #[error("The authenticator failed to produce a valid response")]
636 AuthenticatorFailure,
637
638 #[error("Failed to convert between Rust types")]
640 Conversion,
641
642 #[error("The existing device auth key is already registered on the server.")]
644 CredentialExcluded,
645
646 #[error("The record identifier is not a valid UUID")]
648 InvalidRecordIdentifier,
649
650 #[error("Invalid Web Vault URL specified")]
652 InvalidWebVaultUrl,
653
654 #[error("No device auth key exists on this device")]
656 MissingDeviceAuthKey,
657
658 #[error("Failed to unregister device auth key from server")]
660 UnregisterFailure,
661
662 #[error("Failed to de-/serialize COSE key data")]
664 InvalidCoseKey,
665
666 #[error("An invalid public key credential descriptor was passed in the allow list")]
668 InvalidPublicKeyCredentialDescriptor,
669
670 #[error("A master password hash could not be generated for the given master password")]
672 MasterPasswordHash,
673
674 #[error(
676 "No credential ID was returned in the response nor was a single credential ID passed in the request"
677 )]
678 MissingCredentialId,
679
680 #[error("No HMAC secret was returned with the credential")]
682 MissingHmacSecret,
683
684 #[error("User handle was not returned in the response")]
686 MissingUserHandle,
687
688 #[error("Feature is not yet implemented")]
690 NotImplemented,
691
692 #[error("Failed to retrieve the registration options from the server")]
694 RetrieveRegistrationOptionsFailure,
695
696 #[error("Failed to generate rotateable key set from PRF output")]
698 PrfFailure,
699
700 #[error("Failed to submit registration request to the server")]
702 SubmitRegistrationFailure,
703
704 #[error("User cancelled the operation")]
706 UserCancelled,
707
708 #[error("An unknown error occurred")]
710 Unknown {
711 reason: String,
713 },
714}
715
716#[cfg(feature = "uniffi")]
720impl From<uniffi::UnexpectedUniFFICallbackError> for DeviceAuthKeyError {
721 fn from(e: uniffi::UnexpectedUniFFICallbackError) -> Self {
722 Self::Unknown { reason: e.reason }
723 }
724}
725
726#[async_trait::async_trait]
728pub trait DeviceAuthKeyStore: Send + Sync {
729 async fn create_record(
734 &mut self,
735 record: DeviceAuthKeyRecord,
736 ) -> Result<(), DeviceAuthKeyError>;
737
738 async fn create_metadata(
742 &mut self,
743 metadata: DeviceAuthKeyMetadata,
744 ) -> Result<(), DeviceAuthKeyError>;
745
746 async fn get_metadata(&self) -> Result<Option<DeviceAuthKeyMetadata>, DeviceAuthKeyError>;
748
749 async fn get_record(&self) -> Result<Option<DeviceAuthKeyRecord>, DeviceAuthKeyError>;
751
752 async fn delete_record_and_metadata(&mut self) -> Result<(), DeviceAuthKeyError>;
754}
755
756struct DeviceAuthKeyStoreInternal<'a> {
757 store: &'a mut dyn DeviceAuthKeyStore,
758}
759
760#[async_trait::async_trait]
761impl passkey::authenticator::CredentialStore for DeviceAuthKeyStoreInternal<'_> {
762 type PasskeyItem = DeviceAuthKeyRecord;
763
764 async fn find_credentials(
765 &self,
766 _ids: Option<&[passkey::types::webauthn::PublicKeyCredentialDescriptor]>,
767 _rp_id: &str,
768 _user_handle: Option<&[u8]>,
769 ) -> Result<Vec<Self::PasskeyItem>, StatusCode> {
770 match self.store.get_record().await {
771 Ok(Some(key)) => Ok(vec![key]),
772 Ok(None) => return Ok(vec![]),
773 Err(_) => Err(VendorError::try_from(0xf0)
774 .expect("valid vendor error")
775 .into()),
776 }
777 }
778
779 async fn save_credential(
780 &mut self,
781 cred: Passkey,
782 _user: passkey::types::ctap2::make_credential::PublicKeyCredentialUserEntity,
783 _rp: passkey::types::ctap2::make_credential::PublicKeyCredentialRpEntity,
784 _options: passkey::types::ctap2::get_assertion::Options,
785 ) -> Result<(), StatusCode> {
786 let record = cred
787 .try_into()
788 .map_err(|_| VendorError::try_from(0xf0).expect("valid vendor error"))?;
789
790 self.store.create_record(record).await.map_err(|_| {
791 StatusCode::from(VendorError::try_from(0xf0).expect("valid vendor error"))
792 })?;
793 Ok(())
794 }
795
796 async fn update_credential(&mut self, _cred: Passkey) -> Result<(), StatusCode> {
797 tracing::warn!("called update_credential() on device auth key, which is not supported");
799 Err(StatusCode::Ctap2(
800 VendorError::try_from(0xF3)
801 .expect("valid vendor error")
802 .into(),
803 ))
804 }
805
806 async fn get_info(&self) -> StoreInfo {
807 StoreInfo {
808 discoverability: DiscoverabilitySupport::Full,
809 }
810 }
811}
812
813struct DeviceAuthKeyUiInternal {}
814
815#[async_trait::async_trait]
816impl passkey::authenticator::UserValidationMethod for DeviceAuthKeyUiInternal {
817 type PasskeyItem = DeviceAuthKeyRecord;
818
819 async fn check_user<'a>(
820 &self,
821 _hint: UiHint<'a, Self::PasskeyItem>,
822 _presence: bool,
823 _verification: bool,
824 ) -> Result<UserCheck, Ctap2Error> {
825 Ok(UserCheck {
829 presence: true,
830 verification: true,
831 })
832 }
833
834 fn is_presence_enabled(&self) -> bool {
835 true
836 }
837
838 fn is_verification_enabled(&self) -> Option<bool> {
839 Some(true)
840 }
841}