From ea080feac8d7db7e53a1332b047476f9bbb16a9f Mon Sep 17 00:00:00 2001 From: Firstyear Date: Sat, 24 Jul 2021 14:58:38 +1000 Subject: [PATCH] Update webauthn-rs to alpha.9 (#532) --- Cargo.lock | 78 ++++++++++---------- kanidm_client/Cargo.toml | 4 +- kanidm_proto/Cargo.toml | 2 +- kanidmd/Cargo.toml | 4 +- kanidmd/src/lib/be/dbvalue.rs | 4 +- kanidmd/src/lib/credential/mod.rs | 3 + kanidmd/src/lib/credential/webauthn.rs | 8 +-- kanidmd/src/lib/idm/authsession.rs | 98 +++++++++++++++----------- kanidmd/src/lib/idm/mfareg.rs | 8 +-- kanidmd_web_ui/Cargo.toml | 2 +- 10 files changed, 114 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db6c13174..064a2e873 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,9 +84,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61" +checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486" [[package]] name = "anymap" @@ -574,9 +574,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.68" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787" +checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2" [[package]] name = "cfg-if" @@ -895,9 +895,9 @@ dependencies = [ [[package]] name = "crypto-mac" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6" +checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" dependencies = [ "generic-array 0.14.4", "subtle", @@ -1156,9 +1156,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77b705829d1e87f762c2df6da140b26af5839e1033aa84aa5f56bb688e4e1bdb" +checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" dependencies = [ "instant", ] @@ -1561,7 +1561,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" dependencies = [ - "crypto-mac 0.10.0", + "crypto-mac 0.10.1", "digest 0.9.0", ] @@ -1642,9 +1642,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.9" +version = "0.14.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83" +checksum = "0b61cf2d1aebcf6e6352c97b81dc2244ca29194be1b276f5d8ad5c6330fffb11" dependencies = [ "bytes", "futures-channel", @@ -1723,9 +1723,9 @@ checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" [[package]] name = "instant" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d" dependencies = [ "cfg-if 1.0.0", ] @@ -2006,9 +2006,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.97" +version = "0.2.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6" +checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790" [[package]] name = "libm" @@ -2425,18 +2425,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4" +checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f" +checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" dependencies = [ "proc-macro2", "quote", @@ -3308,9 +3308,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "sval" @@ -3320,9 +3320,9 @@ checksum = "45f6ee7c7b87caf59549e9fe45d6a69c75c8019e79e212a835c5da0e92f0ba08" [[package]] name = "syn" -version = "1.0.73" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" +checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c" dependencies = [ "proc-macro2", "quote", @@ -3331,9 +3331,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa" dependencies = [ "proc-macro2", "quote", @@ -3516,9 +3516,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.2.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "848a1e1181b9f6753b5e96a092749e29b11d19ede67dfbbd6c7dc7e0f49b5338" dependencies = [ "tinyvec_macros", ] @@ -3531,9 +3531,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985" +checksum = "c2602b8af3767c285202012822834005f596c811042315fa7e9f5b12b2a43207" dependencies = [ "autocfg", "bytes", @@ -3551,9 +3551,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37" +checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" dependencies = [ "proc-macro2", "quote", @@ -3690,9 +3690,9 @@ checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "universal-hash" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ "generic-array 0.14.4", "subtle", @@ -3911,9 +3911,9 @@ dependencies = [ [[package]] name = "webauthn-authenticator-rs" -version = "0.3.0-alpha.9" +version = "0.3.0-alpha.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66256dbac82aed09737aa10d05e6f47f646d63501238e258569e4120bfa2e8b8" +checksum = "3998c77a06a258ac1408ac7fbeb8f6be6d8dcf073ea4b2ae2d647db675c3c7e9" dependencies = [ "authenticator", "log", @@ -3927,9 +3927,9 @@ dependencies = [ [[package]] name = "webauthn-rs" -version = "0.3.0-alpha.7" +version = "0.3.0-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "510bc117ab035a8e6e5f99a56f953e9e610b0a38c19314fc6ddb01bb7c9690cd" +checksum = "4bbb2b77105c3b25ef0187146d80824648da0645f650c4d2080e3815d6cbbb87" dependencies = [ "base64 0.13.0", "js-sys", @@ -4059,9 +4059,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.3.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "377db0846015f7ae377174787dd452e1c5f5a9050bc6f954911d01f116daa0cd" dependencies = [ "zeroize_derive", ] diff --git a/kanidm_client/Cargo.toml b/kanidm_client/Cargo.toml index e098ca069..0de69a463 100644 --- a/kanidm_client/Cargo.toml +++ b/kanidm_client/Cargo.toml @@ -20,7 +20,7 @@ serde_derive = "1.0" toml = "0.5" uuid = { version = "0.8", features = ["serde", "v4"] } url = { version = "2", features = ["serde"] } -webauthn-rs = "0.3.0-alpha.7" +webauthn-rs = "0.3.0-alpha.9" tokio = { version = "1", features = ["rt", "net", "time", "macros", "sync", "signal"] } [dev-dependencies] @@ -29,6 +29,6 @@ kanidm = { path = "../kanidmd" } futures = "0.3" async-std = "1.6" -webauthn-authenticator-rs = "0.3.0-alpha.9" +webauthn-authenticator-rs = "0.3.0-alpha.10" oauth2_ext = { package = "oauth2", version = "4.0", default-features = false } base64 = "0.13" diff --git a/kanidm_proto/Cargo.toml b/kanidm_proto/Cargo.toml index 73ba46047..fa7895c42 100644 --- a/kanidm_proto/Cargo.toml +++ b/kanidm_proto/Cargo.toml @@ -15,7 +15,7 @@ serde_derive = "1.0" uuid = { version = "0.8", features = ["serde", "wasm-bindgen"] } # zxcvbn = { version = "2.0", features = ["ser"] } base32 = "0.4" -webauthn-rs = { version = "0.3.0-alpha.7", default-features = false, features = ["wasm"] } +webauthn-rs = { version = "0.3.0-alpha.9", default-features = false, features = ["wasm"] } time = { version = "0.2", features = ["serde", "std"] } url = { version = "2", features = ["serde"] } diff --git a/kanidmd/Cargo.toml b/kanidmd/Cargo.toml index bfdf6b008..9679b024f 100644 --- a/kanidmd/Cargo.toml +++ b/kanidmd/Cargo.toml @@ -79,7 +79,7 @@ idlset = { version = "^0.2" } # idlset = { version = "^0.2", path = "../../idlset" } ldap3_server = "0.1" -webauthn-rs = "0.3.0-alpha.7" +webauthn-rs = "0.3.0-alpha.9" libc = "0.2" users = "0.11" @@ -98,7 +98,7 @@ simd_support = [ "concread/simd_support" ] [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } # For testing webauthn -webauthn-authenticator-rs = "0.3.0-alpha.9" +webauthn-authenticator-rs = "0.3.0-alpha.10" [dev-dependencies.cargo-husky] version = "1" diff --git a/kanidmd/src/lib/be/dbvalue.rs b/kanidmd/src/lib/be/dbvalue.rs index 9c070bac8..e59218842 100644 --- a/kanidmd/src/lib/be/dbvalue.rs +++ b/kanidmd/src/lib/be/dbvalue.rs @@ -1,7 +1,7 @@ use std::{collections::HashSet, time::Duration}; use url::Url; use uuid::Uuid; -use webauthn_rs::proto::COSEKey; +use webauthn_rs::proto::{COSEKey, UserVerificationPolicy}; #[derive(Serialize, Deserialize, Debug)] pub struct DbCidV1 { @@ -69,6 +69,8 @@ pub struct DbWebauthnV1 { pub counter: u32, #[serde(rename = "v")] pub verified: bool, + #[serde(rename = "p", default)] + pub registration_policy: UserVerificationPolicy, } #[derive(Serialize, Deserialize)] diff --git a/kanidmd/src/lib/credential/mod.rs b/kanidmd/src/lib/credential/mod.rs index e0ccdd4f0..5c50eb579 100644 --- a/kanidmd/src/lib/credential/mod.rs +++ b/kanidmd/src/lib/credential/mod.rs @@ -352,6 +352,7 @@ impl TryFrom for Credential { cred: wc.cred, counter: wc.counter, verified: wc.verified, + registration_policy: wc.registration_policy, }, ) }) @@ -647,6 +648,7 @@ impl Credential { cred: v.cred.clone(), counter: v.counter, verified: v.verified, + registration_policy: v.registration_policy, }) .collect(), ), @@ -666,6 +668,7 @@ impl Credential { cred: v.cred.clone(), counter: v.counter, verified: v.verified, + registration_policy: v.registration_policy, }) .collect(), ), diff --git a/kanidmd/src/lib/credential/webauthn.rs b/kanidmd/src/lib/credential/webauthn.rs index 1613ea42f..aad36a033 100644 --- a/kanidmd/src/lib/credential/webauthn.rs +++ b/kanidmd/src/lib/credential/webauthn.rs @@ -7,15 +7,15 @@ pub struct WebauthnDomainConfig { } impl WebauthnConfig for WebauthnDomainConfig { - fn get_relying_party_name(&self) -> String { - self.rp_name.clone() + fn get_relying_party_name(&self) -> &str { + self.rp_name.as_str() } fn get_origin(&self) -> &str { self.origin.as_str() } - fn get_relying_party_id(&self) -> String { - self.rp_id.clone() + fn get_relying_party_id(&self) -> &str { + self.rp_id.as_str() } } diff --git a/kanidmd/src/lib/idm/authsession.rs b/kanidmd/src/lib/idm/authsession.rs index 9364229a2..a79c9eb2f 100644 --- a/kanidmd/src/lib/idm/authsession.rs +++ b/kanidmd/src/lib/idm/authsession.rs @@ -269,29 +269,38 @@ impl CredHandler { pw_mfa.backup_code.as_ref(), ) { (AuthCredential::Webauthn(resp), _, Some((_, wan_state)), _) => { - webauthn.authenticate_credential(&resp, wan_state.clone()) - .map(|(cid, auth_data)| { - pw_mfa.mfa_state = CredVerifyState::Success; - // Success. Determine if we need to update the counter - // async from r. - if auth_data.counter != 0 { - // Do async - if let Err(_e) = async_tx.send(DelayedAction::WebauthnCounterIncrement(WebauthnCounterIncrement { - target_uuid: who, - cid, - counter: auth_data.counter, - })) { - ladmin_warning!(au, "unable to queue delayed webauthn counter increment, continuing ... "); - }; + match webauthn.authenticate_credential(&resp, &wan_state) { + Ok((cid, auth_data)) => { + pw_mfa.mfa_state = CredVerifyState::Success; + // Success. Determine if we need to update the counter + // async from r. + if auth_data.counter != 0 { + // Do async + if let Err(_e) = + async_tx.send(DelayedAction::WebauthnCounterIncrement( + WebauthnCounterIncrement { + target_uuid: who, + cid: cid.clone(), + counter: auth_data.counter, + }, + )) + { + ladmin_warning!(au, "unable to queue delayed webauthn counter increment, continuing ... "); }; - CredState::Continue(vec![AuthAllowed::Password]) - }) - .unwrap_or_else(|e| { - pw_mfa.mfa_state = CredVerifyState::Fail; - // Denied. - lsecurity!(au, "Handler::Webauthn -> Result::Denied - webauthn error {:?}", e); - CredState::Denied(BAD_WEBAUTHN_MSG) - }) + }; + CredState::Continue(vec![AuthAllowed::Password]) + } + Err(e) => { + pw_mfa.mfa_state = CredVerifyState::Fail; + // Denied. + lsecurity!( + au, + "Handler::Webauthn -> Result::Denied - webauthn error {:?}", + e + ); + CredState::Denied(BAD_WEBAUTHN_MSG) + } + } } (AuthCredential::Totp(totp_chal), Some(totp), _, _) => { if totp.verify(*totp_chal, ts) { @@ -425,29 +434,36 @@ impl CredHandler { match cred { AuthCredential::Webauthn(resp) => { // lets see how we go. - webauthn.authenticate_credential(&resp, wan_cred.wan_state.clone()) - .map(|(cid, auth_data)| { + match webauthn.authenticate_credential(&resp, &wan_cred.wan_state) { + Ok((cid, auth_data)) => { wan_cred.state = CredVerifyState::Success; // Success. Determine if we need to update the counter // async from r. if auth_data.counter != 0 { // Do async - if let Err(_e) = async_tx.send(DelayedAction::WebauthnCounterIncrement(WebauthnCounterIncrement { - target_uuid: who, - cid, - counter: auth_data.counter, - })) { + if let Err(_e) = async_tx.send(DelayedAction::WebauthnCounterIncrement( + WebauthnCounterIncrement { + target_uuid: who, + cid: cid.clone(), + counter: auth_data.counter, + }, + )) { ladmin_warning!(au, "unable to queue delayed webauthn counter increment, continuing ... "); }; }; CredState::Success(AuthType::Webauthn) - }) - .unwrap_or_else(|e| { + } + Err(e) => { wan_cred.state = CredVerifyState::Fail; // Denied. - lsecurity!(au, "Handler::Webauthn -> Result::Denied - webauthn error {:?}", e); + lsecurity!( + au, + "Handler::Webauthn -> Result::Denied - webauthn error {:?}", + e + ); CredState::Denied(BAD_WEBAUTHN_MSG) - }) + } + } } _ => { lsecurity!( @@ -825,7 +841,6 @@ mod tests { use crate::utils::{duration_from_epoch_now, readable_password_from_random}; use kanidm_proto::v1::{AuthAllowed, AuthCredential, AuthMech}; use std::time::Duration; - use webauthn_rs::proto::UserVerificationPolicy; use webauthn_rs::Webauthn; use tokio::sync::mpsc::unbounded_channel as unbounded; @@ -1395,15 +1410,15 @@ mod tests { let mut wa = WebauthnAuthenticator::new(U2FSoft::new()); let (chal, reg_state) = webauthn - .generate_challenge_register(name, Some(UserVerificationPolicy::Discouraged)) + .generate_challenge_register(name, false) .expect("Failed to setup webauthn rego challenge"); let r = wa .do_registration("https://idm.example.com", chal) .expect("Failed to create soft token"); - let wan_cred = webauthn - .register_credential(&r, reg_state, |_| Ok(false)) + let (wan_cred, _) = webauthn + .register_credential(&r, ®_state, |_| Ok(false)) .expect("Failed to register soft token"); (webauthn, wa, wan_cred) @@ -1505,18 +1520,15 @@ mod tests { { let mut inv_wa = WebauthnAuthenticator::new(U2FSoft::new()); let (chal, reg_state) = webauthn - .generate_challenge_register( - &account.name, - Some(UserVerificationPolicy::Discouraged), - ) + .generate_challenge_register(&account.name, false) .expect("Failed to setup webauthn rego challenge"); let r = inv_wa .do_registration("https://idm.example.com", chal) .expect("Failed to create soft token"); - let inv_cred = webauthn - .register_credential(&r, reg_state, |_| Ok(false)) + let (inv_cred, _) = webauthn + .register_credential(&r, ®_state, |_| Ok(false)) .expect("Failed to register soft token"); // Discard the auth_state, we only need the invalid challenge. diff --git a/kanidmd/src/lib/idm/mfareg.rs b/kanidmd/src/lib/idm/mfareg.rs index 86892ae32..39943a7df 100644 --- a/kanidmd/src/lib/idm/mfareg.rs +++ b/kanidmd/src/lib/idm/mfareg.rs @@ -12,7 +12,7 @@ use uuid::Uuid; use webauthn_rs::proto::Credential as WebauthnCredential; use webauthn_rs::proto::{CreationChallengeResponse, RegisterPublicKeyCredential}; use webauthn_rs::RegistrationState as WebauthnRegistrationState; -use webauthn_rs::{proto::UserVerificationPolicy, Webauthn}; +use webauthn_rs::Webauthn; pub(crate) enum MfaRegCred { Totp(Totp), @@ -169,7 +169,7 @@ impl MfaRegSession { ) -> Result<(Self, MfaRegNext), OperationError> { // Setup the registration. let (chal, reg_state) = webauthn - .generate_challenge_register(&account.name, Some(UserVerificationPolicy::Discouraged)) + .generate_challenge_register(&account.name, false) .map_err(|e| { ladmin_error!(au, "Unable to generate webauthn challenge -> {:?}", e); OperationError::Webauthn @@ -204,12 +204,12 @@ impl MfaRegSession { match nstate { MfaRegState::WebauthnInit(label, reg_state) => webauthn - .register_credential(chal, reg_state, |_| Ok(false)) + .register_credential(chal, ®_state, |_| Ok(false)) .map_err(|e| { ladmin_error!(au, "Unable to register webauthn credential -> {:?}", e); OperationError::Webauthn }) - .map(|cred| (MfaRegNext::Success, Some(MfaRegCred::Webauthn(label, cred)))), + .map(|(cred, _)| (MfaRegNext::Success, Some(MfaRegCred::Webauthn(label, cred)))), _ => Err(OperationError::InvalidRequestState), } } diff --git a/kanidmd_web_ui/Cargo.toml b/kanidmd_web_ui/Cargo.toml index dc775dd9e..98e0cbc5f 100644 --- a/kanidmd_web_ui/Cargo.toml +++ b/kanidmd_web_ui/Cargo.toml @@ -19,7 +19,7 @@ yew = "0.17" kanidm_proto = { path = "../kanidm_proto", version = "1.1.0-alpha" } anyhow = "1" -webauthn-rs = { version = "0.3.0-alpha.7", default-features = false, features = ["wasm"] } +webauthn-rs = { version = "0.3.0-alpha.9", default-features = false, features = ["wasm"] } web-sys = { version = "0.3", features = ["PublicKeyCredentialUserEntity", "CredentialCreationOptions", "Navigator", "Window", "CredentialsContainer", "PublicKeyCredentialRpEntity", "PublicKeyCredentialCreationOptions", "PublicKeyCredential", "AuthenticatorResponse", "AuthenticationExtensionsClientOutputs", "CredentialRequestOptions"] } js-sys = "0.3"