diff --git a/libwebauthn/examples/prf_test.rs b/libwebauthn/examples/prf_test.rs index f4e81cd..4ac40f9 100644 --- a/libwebauthn/examples/prf_test.rs +++ b/libwebauthn/examples/prf_test.rs @@ -13,8 +13,8 @@ use tokio::sync::broadcast::Receiver; use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::webauthn::{ - GetAssertionHmacOrPrfInput, GetAssertionRequest, GetAssertionRequestExtensions, PRFValue, - PrfInput, UserVerificationRequirement, + GetAssertionRequest, GetAssertionRequestExtensions, PRFValue, PrfInput, + UserVerificationRequirement, }; use libwebauthn::pin::PinRequestReason; use libwebauthn::proto::ctap2::{Ctap2PublicKeyCredentialDescriptor, Ctap2PublicKeyCredentialType}; @@ -126,16 +126,16 @@ pub async fn main() -> Result<(), Box> { }); let eval_by_credential = HashMap::new(); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( &mut channel, &credential, &challenge, - hmac_or_prf, + prf, "PRF output: ", ) .await; @@ -147,7 +147,7 @@ async fn run_success_test( channel: &mut HidChannel<'_>, credential: &Ctap2PublicKeyCredentialDescriptor, challenge: &[u8; 32], - hmac_or_prf: GetAssertionHmacOrPrfInput, + prf: PrfInput, printoutput: &str, ) { let get_assertion = GetAssertionRequest { @@ -156,7 +156,7 @@ async fn run_success_test( allow: vec![credential.clone()], user_verification: UserVerificationRequirement::Preferred, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(hmac_or_prf), + prf: Some(prf), ..Default::default() }), timeout: TIMEOUT, diff --git a/libwebauthn/examples/webauthn_extensions_hid.rs b/libwebauthn/examples/webauthn_extensions_hid.rs index 1feef5f..0c22168 100644 --- a/libwebauthn/examples/webauthn_extensions_hid.rs +++ b/libwebauthn/examples/webauthn_extensions_hid.rs @@ -10,9 +10,9 @@ use tokio::sync::broadcast::Receiver; use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::webauthn::{ - CredentialProtectionExtension, CredentialProtectionPolicy, GetAssertionHmacOrPrfInput, - GetAssertionRequest, GetAssertionRequestExtensions, HMACGetSecretInput, MakeCredentialRequest, - MakeCredentialsRequestExtensions, ResidentKeyRequirement, UserVerificationRequirement, + CredentialProtectionExtension, CredentialProtectionPolicy, GetAssertionRequest, + GetAssertionRequestExtensions, MakeCredentialRequest, MakeCredentialsRequestExtensions, + PRFValue, PrfInput, ResidentKeyRequirement, UserVerificationRequirement, }; use libwebauthn::pin::PinRequestReason; use libwebauthn::proto::ctap2::{ @@ -149,12 +149,13 @@ pub async fn main() -> Result<(), Box> { user_verification: UserVerificationRequirement::Discouraged, extensions: Some(GetAssertionRequestExtensions { cred_blob: true, - hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( - HMACGetSecretInput { - salt1: [1; 32], - salt2: None, - }, - )), + prf: Some(PrfInput { + eval: Some(PRFValue { + first: [1; 32], + second: None, + }), + eval_by_credential: std::collections::HashMap::new(), + }), ..Default::default() }), timeout: TIMEOUT, diff --git a/libwebauthn/examples/webauthn_prf_hid.rs b/libwebauthn/examples/webauthn_prf_hid.rs index 6ae845a..3e50fd8 100644 --- a/libwebauthn/examples/webauthn_prf_hid.rs +++ b/libwebauthn/examples/webauthn_prf_hid.rs @@ -12,9 +12,9 @@ use tokio::sync::broadcast::Receiver; use tracing_subscriber::{self, EnvFilter}; use libwebauthn::ops::webauthn::{ - GetAssertionHmacOrPrfInput, GetAssertionRequest, GetAssertionRequestExtensions, - MakeCredentialPrfInput, MakeCredentialRequest, MakeCredentialsRequestExtensions, PRFValue, - PrfInput, ResidentKeyRequirement, UserVerificationRequirement, + GetAssertionRequest, GetAssertionRequestExtensions, MakeCredentialPrfInput, + MakeCredentialRequest, MakeCredentialsRequestExtensions, PRFValue, PrfInput, + ResidentKeyRequirement, UserVerificationRequirement, }; use libwebauthn::pin::PinRequestReason; use libwebauthn::proto::ctap2::{ @@ -148,15 +148,15 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( &mut channel, &credential, &challenge, - hmac_or_prf, + prf, "eval_by_credential only", ) .await; @@ -175,15 +175,15 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( &mut channel, &credential, &challenge, - hmac_or_prf, + prf, "eval and eval_by_credential", ) .await; @@ -195,15 +195,15 @@ pub async fn main() -> Result<(), Box> { }); let eval_by_credential = HashMap::new(); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( &mut channel, &credential, &challenge, - hmac_or_prf, + prf, "eval only", ) .await; @@ -243,15 +243,15 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( &mut channel, &credential, &challenge, - hmac_or_prf, + prf, "eval and full list of eval_by_credential", ) .await; @@ -284,15 +284,15 @@ pub async fn main() -> Result<(), Box> { second: Some([8; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( &mut channel, &credential, &challenge, - hmac_or_prf, + prf, "eval and non-fitting list of eval_by_credential", ) .await; @@ -322,15 +322,15 @@ pub async fn main() -> Result<(), Box> { second: Some([8; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( &mut channel, &credential, &challenge, - hmac_or_prf, + prf, "No eval and non-fitting list of eval_by_credential (should have no extension output)", ) .await; @@ -349,15 +349,15 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_failed_test( &mut channel, Some(&credential), &challenge, - hmac_or_prf, + prf, "Wrongly encoded credential_id", WebAuthnError::Platform(PlatformError::SyntaxError), ) @@ -373,15 +373,15 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_failed_test( &mut channel, Some(&credential), &challenge, - hmac_or_prf, + prf, "Empty credential_id", WebAuthnError::Platform(PlatformError::SyntaxError), ) @@ -397,15 +397,15 @@ pub async fn main() -> Result<(), Box> { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_failed_test( &mut channel, None, &challenge, - hmac_or_prf, + prf, "Empty allow_list, set eval_by_credential", WebAuthnError::Platform(PlatformError::NotSupported), ) @@ -418,7 +418,7 @@ async fn run_success_test( channel: &mut HidChannel<'_>, credential: &Ctap2PublicKeyCredentialDescriptor, challenge: &[u8; 32], - hmac_or_prf: GetAssertionHmacOrPrfInput, + prf: PrfInput, printoutput: &str, ) { let get_assertion = GetAssertionRequest { @@ -427,7 +427,7 @@ async fn run_success_test( allow: vec![credential.clone()], user_verification: UserVerificationRequirement::Discouraged, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(hmac_or_prf), + prf: Some(prf), ..Default::default() }), timeout: TIMEOUT, @@ -444,7 +444,7 @@ async fn run_success_test( break Err(WebAuthnError::Ctap(ctap_error)); } Err(err) => break Err(err), - }; + } } .unwrap(); for (num, assertion) in response.assertions.iter().enumerate() { @@ -459,7 +459,7 @@ async fn run_failed_test( channel: &mut HidChannel<'_>, credential: Option<&Ctap2PublicKeyCredentialDescriptor>, challenge: &[u8; 32], - hmac_or_prf: GetAssertionHmacOrPrfInput, + prf: PrfInput, printoutput: &str, expected_error: WebAuthnError, ) { @@ -469,7 +469,7 @@ async fn run_failed_test( allow: credential.map(|x| vec![x.clone()]).unwrap_or_default(), user_verification: UserVerificationRequirement::Discouraged, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(hmac_or_prf), + prf: Some(prf), ..Default::default() }), timeout: TIMEOUT, @@ -486,7 +486,7 @@ async fn run_failed_test( break Err(WebAuthnError::Ctap(ctap_error)); } Err(err) => break Err(err), - }; + } }; assert_eq!(response, Err(expected_error), "{printoutput}:"); diff --git a/libwebauthn/src/ops/webauthn/get_assertion.rs b/libwebauthn/src/ops/webauthn/get_assertion.rs index af1a990..e8db340 100644 --- a/libwebauthn/src/ops/webauthn/get_assertion.rs +++ b/libwebauthn/src/ops/webauthn/get_assertion.rs @@ -81,18 +81,11 @@ impl FromInnerModel Result { - let hmac_or_prf = match inner.extensions.clone() { - Some(ext) => { - if let Some(prf) = ext.prf { - let prf_input = PrfInput::try_from(prf)?; - Some(GetAssertionHmacOrPrfInput::Prf(prf_input)) - } else if let Some(hmac) = ext.hmac_get_secret { - let hmac_input = HMACGetSecretInput::try_from(hmac)?; - Some(GetAssertionHmacOrPrfInput::HmacGetSecret(hmac_input)) - } else { - None - } - } + let prf = match inner.extensions.as_ref() { + Some(ext) => match &ext.prf { + Some(prf_json) => Some(PrfInput::try_from(prf_json.clone())?), + None => None, + }, None => None, }; @@ -106,7 +99,7 @@ impl FromInnerModel, + /// PRF extension input. At the CTAP level, this is converted to HMAC secret. + pub prf: Option, pub large_blob: Option, } @@ -570,11 +567,11 @@ mod tests { let req: GetAssertionRequest = GetAssertionRequest::from_json(&rpid, &req_json).unwrap(); if let Some(GetAssertionRequestExtensions { - hmac_or_prf: - Some(GetAssertionHmacOrPrfInput::Prf(PrfInput { + prf: + Some(PrfInput { eval: Some(ref prf_value), .. - })), + }), .. }) = &req.extensions { diff --git a/libwebauthn/src/proto/ctap2/model/get_assertion.rs b/libwebauthn/src/proto/ctap2/model/get_assertion.rs index 60996af..078e9ab 100644 --- a/libwebauthn/src/proto/ctap2/model/get_assertion.rs +++ b/libwebauthn/src/proto/ctap2/model/get_assertion.rs @@ -198,7 +198,7 @@ pub struct Ctap2GetAssertionRequestExtensions { #[serde(skip_serializing_if = "Option::is_none")] pub large_blob_key: Option, #[serde(skip)] - pub hmac_or_prf: Option, + pub(crate) hmac_or_prf: Option, } impl From for Ctap2GetAssertionRequestExtensions { @@ -206,7 +206,7 @@ impl From for Ctap2GetAssertionRequestExtensions Ctap2GetAssertionRequestExtensions { cred_blob: other.cred_blob, hmac_secret: None, // Gets calculated later - hmac_or_prf: other.hmac_or_prf, + hmac_or_prf: other.prf.map(GetAssertionHmacOrPrfInput::Prf), large_blob_key: if other.large_blob == Some(GetAssertionLargeBlobExtension::Read) { Some(true) } else { @@ -518,26 +518,27 @@ impl Ctap2GetAssertionResponseExtensions { } }); - let (hmac_get_secret, prf) = if let Some(decrypted) = decrypted_hmac { - match request.extensions.as_ref().and_then(|ext| ext.hmac_or_prf.as_ref()) { - None => (None, None), - Some(GetAssertionHmacOrPrfInput::HmacGetSecret(..)) => (Some(decrypted), None), - Some(GetAssertionHmacOrPrfInput::Prf(..)) => ( - None, - Some(GetAssertionPrfOutput { - results: Some(PRFValue { - first: decrypted.output1, - second: decrypted.output2, - }), + let prf = decrypted_hmac.and_then(|decrypted| { + // At WebAuthn level, we only support PRF (not raw HMAC). + // The PRF input was converted to HMAC internally. + request + .extensions + .as_ref() + .and_then(|ext| ext.prf.as_ref()) + .map(|_| GetAssertionPrfOutput { + results: Some(PRFValue { + first: decrypted.output1, + second: decrypted.output2, }), - ), - } - } else { - (None, None) - }; + }) + }); // LargeBlobs was requested - let large_blob = match request.extensions.as_ref().and_then(|ext| ext.large_blob.as_ref()) { + let large_blob = match request + .extensions + .as_ref() + .and_then(|ext| ext.large_blob.as_ref()) + { None => None, Some(GetAssertionLargeBlobExtension::Read) => { Some(GetAssertionLargeBlobExtensionOutput { @@ -552,7 +553,7 @@ impl Ctap2GetAssertionResponseExtensions { }; GetAssertionResponseUnsignedExtensions { - hmac_get_secret, + hmac_get_secret: None, large_blob, prf, } diff --git a/libwebauthn/src/tests/prf.rs b/libwebauthn/src/tests/prf.rs index 95666b4..b638864 100644 --- a/libwebauthn/src/tests/prf.rs +++ b/libwebauthn/src/tests/prf.rs @@ -2,9 +2,8 @@ use std::collections::HashMap; use std::time::Duration; use crate::ops::webauthn::{ - GetAssertionHmacOrPrfInput, GetAssertionRequest, GetAssertionRequestExtensions, - MakeCredentialPrfInput, MakeCredentialPrfOutput, MakeCredentialsRequestExtensions, PRFValue, - PrfInput, + GetAssertionRequest, GetAssertionRequestExtensions, MakeCredentialPrfInput, + MakeCredentialPrfOutput, MakeCredentialsRequestExtensions, PRFValue, PrfInput, }; use crate::pin::PinManagement; use crate::proto::ctap2::{Ctap2PinUvAuthProtocol, Ctap2PublicKeyCredentialDescriptor}; @@ -192,15 +191,15 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( channel, &credential, &challenge, - hmac_or_prf, + prf, true, false, "eval_by_credential only", @@ -221,15 +220,15 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( channel, &credential, &challenge, - hmac_or_prf, + prf, true, false, "eval and eval_by_credential", @@ -243,15 +242,15 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { }); let eval_by_credential = HashMap::new(); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( channel, &credential, &challenge, - hmac_or_prf, + prf, true, false, "eval only", @@ -293,15 +292,15 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: Some([7; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( channel, &credential, &challenge, - hmac_or_prf, + prf, true, true, "eval and full list of eval_by_credential", @@ -336,15 +335,15 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: Some([8; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( channel, &credential, &challenge, - hmac_or_prf, + prf, true, false, "eval and non-fitting list of eval_by_credential", @@ -376,15 +375,15 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: Some([8; 32]), }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_success_test( channel, &credential, &challenge, - hmac_or_prf, + prf, false, false, "No eval and non-fitting list of eval_by_credential (should have no extension output)", @@ -405,15 +404,15 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_failed_test( channel, Some(&credential), &challenge, - hmac_or_prf, + prf, "Wrongly encoded credential_id", WebAuthnError::Platform(PlatformError::SyntaxError), ) @@ -429,15 +428,15 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_failed_test( channel, Some(&credential), &challenge, - hmac_or_prf, + prf, "Empty credential_id", WebAuthnError::Platform(PlatformError::SyntaxError), ) @@ -453,15 +452,15 @@ async fn run_test_battery(channel: &mut HidChannel<'_>, using_pin: bool) { second: None, }, ); - let hmac_or_prf = GetAssertionHmacOrPrfInput::Prf(PrfInput { + let prf = PrfInput { eval, eval_by_credential, - }); + }; run_failed_test( channel, None, &challenge, - hmac_or_prf, + prf, "Empty allow_list, set eval_by_credential", WebAuthnError::Platform(PlatformError::NotSupported), ) @@ -476,7 +475,7 @@ async fn run_success_test( channel: &mut HidChannel<'_>, credential: &Ctap2PublicKeyCredentialDescriptor, challenge: &[u8; 32], - hmac_or_prf: GetAssertionHmacOrPrfInput, + prf: PrfInput, expect_extensions: bool, expect_prf_second: bool, printoutput: &str, @@ -487,7 +486,7 @@ async fn run_success_test( allow: vec![credential.clone()], user_verification: UserVerificationRequirement::Preferred, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(hmac_or_prf), + prf: Some(prf), ..Default::default() }), timeout: TIMEOUT, @@ -542,7 +541,7 @@ async fn run_failed_test( channel: &mut HidChannel<'_>, credential: Option<&Ctap2PublicKeyCredentialDescriptor>, challenge: &[u8; 32], - hmac_or_prf: GetAssertionHmacOrPrfInput, + prf: PrfInput, printoutput: &str, expected_error: WebAuthnError, ) { @@ -552,7 +551,7 @@ async fn run_failed_test( allow: credential.map(|x| vec![x.clone()]).unwrap_or_default(), user_verification: UserVerificationRequirement::Discouraged, extensions: Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(hmac_or_prf), + prf: Some(prf), ..Default::default() }), timeout: TIMEOUT, diff --git a/libwebauthn/src/webauthn/pin_uv_auth_token.rs b/libwebauthn/src/webauthn/pin_uv_auth_token.rs index 0072bf7..2c73bc7 100644 --- a/libwebauthn/src/webauthn/pin_uv_auth_token.rs +++ b/libwebauthn/src/webauthn/pin_uv_auth_token.rs @@ -433,8 +433,8 @@ mod test { use crate::{ ops::webauthn::{ - GetAssertionHmacOrPrfInput, GetAssertionRequest, GetAssertionRequestExtensions, - HMACGetSecretInput, UserVerificationRequirement, + GetAssertionRequest, GetAssertionRequestExtensions, PRFValue, PrfInput, + UserVerificationRequirement, }, pin::{pin_hash, PinUvAuthProtocol, PinUvAuthProtocolOne}, proto::{ @@ -610,12 +610,13 @@ mod test { info_extensions.as_deref(), UserVerificationRequirement::Discouraged, Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( - HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }, - )), + prf: Some(PrfInput { + eval: Some(PRFValue { + first: [0; 32], + second: None, + }), + eval_by_credential: HashMap::new(), + }), ..Default::default() }), Ok(UsedPinUvAuthToken::None), @@ -655,12 +656,13 @@ mod test { Some(&["hmac-secret"]), UserVerificationRequirement::Preferred, Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( - HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }, - )), + prf: Some(PrfInput { + eval: Some(PRFValue { + first: [0; 32], + second: None, + }), + eval_by_credential: HashMap::new(), + }), ..Default::default() }), Ok(UsedPinUvAuthToken::LegacyUV), @@ -726,12 +728,13 @@ mod test { for (info_options, uv_requirement) in testcases { let extensions = Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( - HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }, - )), + prf: Some(PrfInput { + eval: Some(PRFValue { + first: [0; 32], + second: None, + }), + eval_by_credential: HashMap::new(), + }), ..Default::default() }); @@ -795,12 +798,13 @@ mod test { for (info_options, uv_requirement) in testcases { let extensions = Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( - HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }, - )), + prf: Some(PrfInput { + eval: Some(PRFValue { + first: [0; 32], + second: None, + }), + eval_by_credential: HashMap::new(), + }), ..Default::default() }); @@ -911,12 +915,13 @@ mod test { for (info_options, uv_requirement) in testcases { let extensions = Some(GetAssertionRequestExtensions { - hmac_or_prf: Some(GetAssertionHmacOrPrfInput::HmacGetSecret( - HMACGetSecretInput { - salt1: [0; 32], - salt2: None, - }, - )), + prf: Some(PrfInput { + eval: Some(PRFValue { + first: [0; 32], + second: None, + }), + eval_by_credential: HashMap::new(), + }), ..Default::default() });