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