From 92fb6d7ad43f6e932234caeb2be7b2a26f0680bc Mon Sep 17 00:00:00 2001 From: John Schanck Date: Wed, 3 May 2023 16:37:45 +0000 Subject: [PATCH] Bug 1830944 - vendor authenticator-rs v0.4.0-alpha14. r=keeler,supply-chain-reviewers Differential Revision: https://phabricator.services.mozilla.com/D176938 --- Cargo.lock | 4 +- browser/base/content/browser.js | 8 + .../en-US/chrome/browser/browser.properties | 2 + dom/webauthn/authrs_bridge/Cargo.toml | 2 +- dom/webauthn/authrs_bridge/src/lib.rs | 62 +- supply-chain/imports.lock | 7 + .../rust/authenticator/.cargo-checksum.json | 2 +- third_party/rust/authenticator/Cargo.lock | 2 +- third_party/rust/authenticator/Cargo.toml | 2 +- .../rust/authenticator/examples/ctap2.rs | 39 +- .../examples/ctap2_discoverable_creds.rs | 27 +- .../examples/interactive_management.rs | 201 +++++ .../rust/authenticator/examples/set_pin.rs | 3 + .../examples/test_exclude_list.rs | 31 +- .../authenticator/src/authenticatorservice.rs | 84 ++- .../rust/authenticator/src/crypto/mod.rs | 9 + .../src/ctap2/commands/client_pin.rs | 2 +- .../src/ctap2/commands/get_assertion.rs | 49 +- .../src/ctap2/commands/get_info.rs | 86 +-- .../src/ctap2/commands/make_credentials.rs | 68 +- .../authenticator/src/ctap2/commands/mod.rs | 8 +- .../rust/authenticator/src/ctap2/server.rs | 14 + third_party/rust/authenticator/src/errors.rs | 6 +- third_party/rust/authenticator/src/lib.rs | 1 + third_party/rust/authenticator/src/manager.rs | 80 +- .../rust/authenticator/src/statemachine.rs | 714 ++++++++++++------ .../rust/authenticator/src/status_update.rs | 96 +-- .../src/transport/device_selector.rs | 140 +--- .../src/transport/freebsd/device.rs | 19 - .../src/transport/freebsd/transaction.rs | 5 +- .../rust/authenticator/src/transport/hid.rs | 1 - .../src/transport/linux/device.rs | 20 - .../src/transport/linux/transaction.rs | 5 +- .../src/transport/macos/device.rs | 13 - .../src/transport/macos/transaction.rs | 5 +- .../src/transport/mock/device.rs | 13 - .../src/transport/netbsd/device.rs | 15 - .../src/transport/netbsd/transaction.rs | 5 +- .../src/transport/openbsd/device.rs | 21 - .../src/transport/openbsd/transaction.rs | 5 +- .../src/transport/stub/device.rs | 4 - .../src/transport/stub/transaction.rs | 4 +- .../src/transport/windows/device.rs | 18 - .../src/transport/windows/transaction.rs | 5 +- 44 files changed, 1074 insertions(+), 833 deletions(-) create mode 100644 third_party/rust/authenticator/examples/interactive_management.rs diff --git a/Cargo.lock b/Cargo.lock index a5a0c2842df9..1e73da7ffcb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,9 +320,9 @@ dependencies = [ [[package]] name = "authenticator" -version = "0.4.0-alpha.13" +version = "0.4.0-alpha.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7571904a2c563ed99aac46a7d990186f76a8568f0fd1a769a78214dd385946bf" +checksum = "9f3aaf9a8213bf1a4cfafca1a38befe0873a2a4d8013136d2dd12227e14bb2ba" dependencies = [ "base64 0.13.999", "bitflags 1.3.2", diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js index 4173bad03755..4a42b9665a14 100644 --- a/browser/base/content/browser.js +++ b/browser/base/content/browser.js @@ -7609,6 +7609,14 @@ var WebAuthnPromptHelper = { "deviceBlocked", "webauthn.deviceBlockedPrompt" ); + } else if (data.action == "pin-not-set") { + this.show_info( + mgr, + data.origin, + data.tid, + "pinNotSet", + "webauthn.pinNotSetPrompt" + ); } }, diff --git a/browser/locales/en-US/chrome/browser/browser.properties b/browser/locales/en-US/chrome/browser/browser.properties index 76bcdd974e09..03d138415aea 100644 --- a/browser/locales/en-US/chrome/browser/browser.properties +++ b/browser/locales/en-US/chrome/browser/browser.properties @@ -462,6 +462,8 @@ webauthn.selectDevicePrompt=Multiple devices found for %S. Please select one. webauthn.deviceBlockedPrompt=User verification failed on %S. There are no attempts left and your device has been locked, because the wrong PIN was provided too many times. The device needs a reset. # LOCALIZATION NOTE (webauthn.pinAuthBlockedPrompt): %S is hostname webauthn.pinAuthBlockedPrompt=User verification failed on %S. There were too many failed attempts in a row and PIN authentication has been temporarily blocked. Your device needs a power cycle (unplug and re-insert). +# LOCALIZATION NOTE (webauthn.pinNotSetPrompt): %S is hostname +webauthn.pinNotSetPrompt=User verification failed on %S. You may need to set a PIN on your device. webauthn.cancel=Cancel webauthn.cancel.accesskey=c webauthn.proceed=Proceed diff --git a/dom/webauthn/authrs_bridge/Cargo.toml b/dom/webauthn/authrs_bridge/Cargo.toml index a05c2a171c8b..25c6c5d310c2 100644 --- a/dom/webauthn/authrs_bridge/Cargo.toml +++ b/dom/webauthn/authrs_bridge/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" authors = ["Martin Sirringhaus", "John Schanck"] [dependencies] -authenticator = { version = "0.4.0-alpha.13", features = ["gecko"] } +authenticator = { version = "0.4.0-alpha.14", features = ["gecko"] } log = "0.4" moz_task = { path = "../../../xpcom/rust/moz_task" } nserror = { path = "../../../xpcom/rust/nserror" } diff --git a/dom/webauthn/authrs_bridge/src/lib.rs b/dom/webauthn/authrs_bridge/src/lib.rs index fe5ae86014bb..13897c883bfe 100644 --- a/dom/webauthn/authrs_bridge/src/lib.rs +++ b/dom/webauthn/authrs_bridge/src/lib.rs @@ -9,13 +9,11 @@ extern crate log; extern crate xpcom; use authenticator::{ - authenticatorservice::{ - AuthenticatorService, GetAssertionOptions, MakeCredentialsOptions, - RegisterArgs, SignArgs, - }, + authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs}, ctap2::attestation::AttestationStatement, ctap2::server::{ - PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User, + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, + ResidentKeyRequirement, User, UserVerificationRequirement, }, errors::{AuthenticatorError, PinError, U2FTokenError}, statecallback::StateCallback, @@ -85,6 +83,7 @@ fn authrs_to_nserror(e: &AuthenticatorError) -> nsresult { AuthenticatorError::PinError(PinError::InvalidPin(_)) => NS_ERROR_DOM_OPERATION_ERR, AuthenticatorError::PinError(PinError::PinAuthBlocked) => NS_ERROR_DOM_OPERATION_ERR, AuthenticatorError::PinError(PinError::PinBlocked) => NS_ERROR_DOM_OPERATION_ERR, + AuthenticatorError::PinError(PinError::PinNotSet) => NS_ERROR_DOM_OPERATION_ERR, _ => NS_ERROR_DOM_UNKNOWN_ERR, } } @@ -376,9 +375,20 @@ fn status_callback( make_pin_error_prompt("device-blocked", tid, origin, browsing_context_id); controller.send_prompt(tid, ¬ification_str); } + Ok(StatusUpdate::PinUvError(StatusPinUv::PinNotSet)) => { + let notification_str = + make_pin_error_prompt("pin-not-set", tid, origin, browsing_context_id); + controller.send_prompt(tid, ¬ification_str); + } Ok(StatusUpdate::PinUvError(e)) => { warn!("Unexpected error: {:?}", e) } + Ok(StatusUpdate::InteractiveManagement((_, dev_info, auth_info))) => { + debug!( + "STATUS: interactive management: {}, {:?}", + dev_info, auth_info + ); + } Err(RecvError) => { debug!("STATUS: end"); return; @@ -482,22 +492,26 @@ impl AuthrsTransport { let mut resident_key = nsString::new(); unsafe { args.GetResidentKey(&mut *resident_key) }.to_result()?; - let resident_key = if resident_key.eq("required") { - Some(true) + let resident_key_req = if resident_key.eq("required") { + ResidentKeyRequirement::Required + } else if resident_key.eq("preferred") { + ResidentKeyRequirement::Preferred } else if resident_key.eq("discouraged") { - Some(false) + ResidentKeyRequirement::Discouraged } else { - None + return Err(NS_ERROR_FAILURE); }; let mut user_verification = nsString::new(); unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?; - let user_verification = if user_verification.eq("required") { - Some(true) + let user_verification_req = if user_verification.eq("required") { + UserVerificationRequirement::Required + } else if user_verification.eq("preferred") { + UserVerificationRequirement::Preferred } else if user_verification.eq("discouraged") { - Some(false) + UserVerificationRequirement::Discouraged } else { - None + return Err(NS_ERROR_FAILURE); }; let mut attestation_conveyance_preference = nsString::new(); @@ -529,10 +543,8 @@ impl AuthrsTransport { }, pub_cred_params, exclude_list, - options: MakeCredentialsOptions { - resident_key, - user_verification, - }, + user_verification_req, + resident_key_req, extensions: Default::default(), pin: None, use_ctap1_fallback: static_prefs::pref!("security.webauthn.ctap2") == false, @@ -625,12 +637,14 @@ impl AuthrsTransport { let mut user_verification = nsString::new(); unsafe { args.GetUserVerification(&mut *user_verification) }.to_result()?; - let user_verification = if user_verification.eq("required") { - Some(true) + let user_verification_req = if user_verification.eq("required") { + UserVerificationRequirement::Required + } else if user_verification.eq("preferred") { + UserVerificationRequirement::Preferred } else if user_verification.eq("discouraged") { - Some(false) + UserVerificationRequirement::Discouraged } else { - None + return Err(NS_ERROR_FAILURE); }; let mut alternate_rp_id = None; @@ -695,10 +709,8 @@ impl AuthrsTransport { relying_party_id: relying_party_id.to_string(), origin: origin.to_string(), allow_list, - options: GetAssertionOptions { - user_presence: Some(true), - user_verification, - }, + user_verification_req, + user_presence_req: true, extensions: Default::default(), pin: None, alternate_rp_id, diff --git a/supply-chain/imports.lock b/supply-chain/imports.lock index 38998f783410..16c9d0a1e9c5 100644 --- a/supply-chain/imports.lock +++ b/supply-chain/imports.lock @@ -8,6 +8,13 @@ user-id = 1258 user-login = "padenot" user-name = "Paul Adenot" +[[publisher.authenticator]] +version = "0.4.0-alpha.14" +when = "2023-05-02" +user-id = 175410 +user-login = "jschanck" +user-name = "John Schanck" + [[publisher.bhttp]] version = "0.3.1" when = "2023-02-23" diff --git a/third_party/rust/authenticator/.cargo-checksum.json b/third_party/rust/authenticator/.cargo-checksum.json index 532750f195a4..11b3960ce589 100644 --- a/third_party/rust/authenticator/.cargo-checksum.json +++ b/third_party/rust/authenticator/.cargo-checksum.json @@ -1 +1 @@ -{"files":{"Cargo.lock":"8f40f0157b3522c9bcbeb721d0499701de22b86711fc596e9ba938ff30df43f9","Cargo.toml":"cffa6c3c1291153a9b6a5cbf401e1d450c4320a8424fa6cc3a4ae2f7c8e2bb85","Cross.toml":"8d132da818d48492aa9f4b78a348f0df3adfae45d988d42ebd6be8a5adadb6c3","LICENSE":"e866c8f5864d4cacfe403820e722e9dc03fe3c7565efa5e4dad9051d827bb92a","README.md":"c87d9c7cc44f1dd4ef861a3a9f8cd2eb68aedd3814768871f5fb63c2070806cd","build.rs":"01092254718e4cd5d6bffcd64d55cc3240dc00e79f3d7344a5dc4abf6c27bca6","examples/ctap2.rs":"30212d2202c9a4cf322a721497d36dffa42720377e9cbdd2d78d496add0e0698","examples/ctap2_discoverable_creds.rs":"fc957a1810c985e58cff807d1662bd1181339db7830f1bfbf790442841e73568","examples/reset.rs":"f6e03955cc5e40d6c7372031448faf78a2236479d4cba96a2ec36a804c65b89b","examples/set_pin.rs":"1503187acda5d96cdf562af2f6e41e0bee08b135006755dc31b6805ae0382f94","examples/test_exclude_list.rs":"7dc75127f4b9ad6afdebfdf1d36f88a4f230d33eba92dae87221f4bf0dcf01c4","rustfmt.toml":"ceb6615363d6fff16426eb56f5727f98a7f7ed459ba9af735b1d8b672e2c3b9b","src/authenticatorservice.rs":"a9828835ad31bd4c16596f4ec395723944f58136c874c93342a026abe0a82d8b","src/consts.rs":"d7b274ec55e56a8d2c150df28e23d72a7379e5bd7257d53d63c2a7b59ab90800","src/crypto/dummy.rs":"9cc6be0dc1e28c7328121e7a4bf435211ae8b1455784472b24993571c4009579","src/crypto/mod.rs":"40d9190ed3bad6ec3f1fb33b1e9a96e607cd33ac9127301fd04ad7dbd4604174","src/crypto/nss.rs":"2bf33898728760f194f204876450d0906b47907d259270f6e3d43c62a709c99a","src/crypto/openssl.rs":"ef6e4dbcc7230137e505e3fc4ad37e102e6b26b37470afd0f4709a297b3aa546","src/ctap2/attestation.rs":"5e23b9895ae763ebdcdfd1f2417d5b1773609c7c60cfc27ce9eb34e55281918a","src/ctap2/client_data.rs":"04ee84b34e91c988183871b4975fc08e12234965187c793ad26d0d82ed44642f","src/ctap2/commands/client_pin.rs":"41ce7ab057b41db38fa55b8e65a121b0d4b00cd9b83c620063404254bfe07304","src/ctap2/commands/get_assertion.rs":"3f77a0f653b7e8559546776b0675eea9562a9b49f24a97e0483bc084622f87fa","src/ctap2/commands/get_info.rs":"90e3586f7fa7a16b91cb199d3731e44fba7b1c3c212900190ef0fc6749240a2d","src/ctap2/commands/get_next_assertion.rs":"1120b0301197ada11751c4c42fc0a4147368ec5427f62da3868347567d28ba3a","src/ctap2/commands/get_version.rs":"e7e2f83ead1c9d6d8611513120e1ac354109cff8e14dce5c3073a5c1fa677d71","src/ctap2/commands/make_credentials.rs":"e16cb406114b7ac4890b159d675c79275c3e6ecc6b0ac58e636c3b5c766dc665","src/ctap2/commands/mod.rs":"034f8631f1ed6289685fae5f681becb90a69645c8d035fdd7a25da7570012647","src/ctap2/commands/reset.rs":"95dac8a400f66fb4725a3850a06d6b605ffc99bec4a90ca302a6e8f21deb48d8","src/ctap2/commands/selection.rs":"a2a4b2e30df1923c296f7ac3a8d38cfcac7c231669cfc39babcb7b02fac7cdee","src/ctap2/mod.rs":"672c581502ac6d1417d0fe42a92ef86b1f1d64799f48061b3d9ac026e35fb69d","src/ctap2/server.rs":"48ec9eb938ceb344fc62413baac45c37fc7471992f2e462e25e4eee82841b6b4","src/ctap2/utils.rs":"ad0aa36a0dbeb510b7f37789329f1957eab206eb529dc083e6176b142984e26e","src/errors.rs":"a1d64f386e1e4cd5dde980e19354c822f6771c68b5386efcb688be4d771c1f84","src/lib.rs":"616781d573d22100e32f859394fdefe27c7d7d578db43c04ad4c4dd1e44fe41b","src/manager.rs":"c9306ddacfd18817f2da7ce32723b4ddc393748fbc9589f80911945326cc5918","src/statecallback.rs":"6b16f97176db1ae3fc3851fe8394e4ffc324bc6fe59313845ac3a88132fd52f1","src/statemachine.rs":"b88918b0ad4f1c3b5807f80e8f0b699740942faafa3adca0826699e65163a042","src/status_update.rs":"8eb5a9a4ae5c7ced7507c00f4da569ee449dd772e2642575151ef99d483f47dc","src/transport/device_selector.rs":"a3e07b555a2d7ed8ccc7fbc061448e13860bbe069232196bb9825a992378635a","src/transport/errors.rs":"5af7cb8d22ffa63bf4264d182a0f54b9b3a2cc9d19d832b3495857229f9a2875","src/transport/freebsd/device.rs":"e065cf6ab215a9846bfd83a234103293193263c46f6b122c0dda5d8fae798764","src/transport/freebsd/mod.rs":"42dcb57fbeb00140003a8ad39acac9b547062b8f281a3fa5deb5f92a6169dde6","src/transport/freebsd/monitor.rs":"a6b34af4dd2e357a5775b1f3a723766107c11ef98dba859b1188ed08e0e450a2","src/transport/freebsd/transaction.rs":"bd65fce8864be95839d7918f0fecc065f907265e0ce5411845f409c8e206f9a4","src/transport/freebsd/uhid.rs":"a194416a8bc5d428c337f8d96a2248769ca190810852bbe5ee686ab595d8eb4c","src/transport/hid.rs":"753ecad34005973f5d902470354862a932ad7f20355851cba6608cd6b56cb00a","src/transport/hidproto.rs":"9d490f161807b75f4d7d5096355006627c1f47c0d90fca53bade3692efc92a2d","src/transport/linux/device.rs":"b7eb15880aaa484e7067caa9682d17e0ace3c8f11e9cab0a6a18d72d01a9a2a0","src/transport/linux/hidraw.rs":"c7a0df9b4e51cb2736218ffffa02b2b2547b7c515d69f9bae2c9a8c8f1cb547b","src/transport/linux/hidwrapper.h":"72785db3a9b27ea72b6cf13a958fee032af54304522d002f56322473978a20f9","src/transport/linux/hidwrapper.rs":"753c7459dbb73befdd186b6269ac33f7a4537b4c935928f50f2b2131756e787d","src/transport/linux/ioctl_aarch64le.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_armle.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_mips64le.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_mipsbe.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_mipsle.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_powerpc64be.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_powerpc64le.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_powerpcbe.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_riscv64.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_s390xbe.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_x86.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_x86_64.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/mod.rs":"446e435126d2a58f167f648dd95cba28e8ac9c17f1f799e1eaeab80ea800fc57","src/transport/linux/monitor.rs":"5e3ec2618dd74027ae6ca1527991254e3271cce59106d4920ce0414094e22f64","src/transport/linux/transaction.rs":"bd65fce8864be95839d7918f0fecc065f907265e0ce5411845f409c8e206f9a4","src/transport/macos/device.rs":"503eced38fa8a7f28c724f76c8de110b51c2d3e0edc3991dd4b19e9ebda26b8d","src/transport/macos/iokit.rs":"7dc4e7bbf8e42e2fcde0cee8e48d14d6234a5a910bd5d3c4e966d8ba6b73992f","src/transport/macos/mod.rs":"333e561554fc901d4f6092f6e4c85823e2b0c4ff31c9188d0e6d542b71a0a07c","src/transport/macos/monitor.rs":"e02288454bb4010e06b705d82646abddb3799f0cd655f574aa19f9d91485a4a2","src/transport/macos/transaction.rs":"b034104484c6864668425bbd4499540c3117b0de2e80b0b03ee452d00323ed50","src/transport/mock/device.rs":"3cb7290e6daba06d6c33d4d2f9d39e2539e60c1dbf990f86e8631025c38f39e2","src/transport/mock/mod.rs":"9c4c87efd19adddc1a91c699a6c328063cfbac5531b76346a5ff92e986aded8f","src/transport/mock/transaction.rs":"be3ed8c389dfa04122364b82515edd76fad6f5d5f72d15cacd45a84fb8397292","src/transport/mod.rs":"2c35da1ef07ba8119bb003fc9ad7944ae81469469147be3752bdc1d2c2e8aea3","src/transport/netbsd/device.rs":"325c22a95bc2f173ecdf19d0d988b7ccac3aa5bbafa4665dbf5e5a126b9910fb","src/transport/netbsd/fd.rs":"5464019025d03ea2a39c82f76b238bbbdb0ea63f5a5fc7c9d974e235139cd53b","src/transport/netbsd/mod.rs":"b1c52aa29537330cebe67427062d6c94871cab2a9b0c04b2305d686f07e88fd5","src/transport/netbsd/monitor.rs":"fb2917e4ba53cc9867987a539061f82d011f4c6e478df1157d965d32df2eb922","src/transport/netbsd/transaction.rs":"bd65fce8864be95839d7918f0fecc065f907265e0ce5411845f409c8e206f9a4","src/transport/netbsd/uhid.rs":"d15be35e2413240066a8f086bb8846b08a6a92bf6a1941c3eec1329dd3a4f9ce","src/transport/openbsd/device.rs":"6bc950da80d8c59d6cedab41d7741ac2a25ee774a737b3bc021e4ee69a238e97","src/transport/openbsd/mod.rs":"514274d414042ff84b3667a41a736e78581e22fda87ccc97c2bc05617e381a30","src/transport/openbsd/monitor.rs":"2e0ba6ecc69b450be9cbfd21a7c65036ed2ce593b12363596d3eae0b5bfb79e8","src/transport/openbsd/transaction.rs":"bd65fce8864be95839d7918f0fecc065f907265e0ce5411845f409c8e206f9a4","src/transport/stub/device.rs":"02f31ec60dc8c81a09cb1568ed696427fe80b09c261d221d51b8a238d00cf867","src/transport/stub/mod.rs":"6a7fec504a52d403b0241b18cd8b95088a31807571f4c0a67e4055afc74f4453","src/transport/stub/transaction.rs":"a3dbea5ccf691cff69278ed8a8ba67d27baba0ea2e1c6a53c9b0b04027283221","src/transport/windows/device.rs":"d385078045bd707d5e4deecdb3ce580c54e6127a32964705bd36581754122295","src/transport/windows/mod.rs":"218e7f2fe91ecb390c12bba5a5ffdad2c1f0b22861c937f4d386262e5b3dd617","src/transport/windows/monitor.rs":"95913d49e7d83482e420493d89b53ffceb6a49e646a87de934dff507b3092b4c","src/transport/windows/transaction.rs":"bd65fce8864be95839d7918f0fecc065f907265e0ce5411845f409c8e206f9a4","src/transport/windows/winapi.rs":"b2a4cc85f14e39cadfbf068ee001c9d776f028d3cf09cb926d4364c5b437c112","src/u2fprotocol.rs":"e61ac223aab79ae82383cd32a23213d18461e229c448373bf2483357a9eae69e","src/u2ftypes.rs":"8511c6f04f69670ddd403178a46060644a27128ca4077a9a3e00bc6671e3864b","src/util.rs":"cf37c4c3caf6dde4fc3cf6f5f297ed3c0f13bcb50fb0e8955899fc837483ef31","src/virtualdevices/mod.rs":"2c7df7691d5c150757304241351612aed4260d65b70ab0f483edbc1a5cfb5674","src/virtualdevices/software_u2f.rs":"83e63c0c4a597e71d87b5cd1f33a49646d00b3062edbdd05c51623b80fb60168","src/virtualdevices/webdriver/mod.rs":"4a36e6dfa9f45f941d863b4039bfbcfa8eaca660bd6ed78aeb1a2962db64be5a","src/virtualdevices/webdriver/testtoken.rs":"7146e02f1a5dad2c8827dd11c12ee408c0e42a0706ac65f139998feffd42570f","src/virtualdevices/webdriver/virtualmanager.rs":"7205a0397833628fc0847aa942a6a314dc1e23306858b546053e0de6a360ebe1","src/virtualdevices/webdriver/web_api.rs":"9032525af458b6fe9a3274c36b6ef8c791ecc4ec46d38ae36583fc9a4535b59d","testing/cross/powerpc64le-unknown-linux-gnu.Dockerfile":"d7463ff4376e3e0ca3fed879fab4aa975c4c0a3e7924c5b88aef9381a5d013de","testing/cross/x86_64-unknown-linux-gnu.Dockerfile":"11c79c04b07a171b0c9b63ef75fa75f33263ce76e3c1eda0879a3e723ebd0c24","testing/run_cross.sh":"cc2a7e0359f210eba2e7121f81eb8ab0125cea6e0d0f2698177b0fe2ad0c33d8","webdriver-tools/requirements.txt":"8236aa3dedad886f213c9b778fec80b037212d30e640b458984110211d546005","webdriver-tools/webdriver-driver.py":"82327c26ba271d1689acc87b612ab8436cb5475f0a3c0dba7baa06e7f6f5e19c"},"package":"7571904a2c563ed99aac46a7d990186f76a8568f0fd1a769a78214dd385946bf"} \ No newline at end of file +{"files":{"Cargo.lock":"0c293d2be0bf21fd2282ecfc77ff23fa45f69ec037b96c7b99ef3bddcbc2350b","Cargo.toml":"ad6277aa6cca1dcb09544608f384794b09a6de1a81c23d511ce0b9946242c760","Cross.toml":"8d132da818d48492aa9f4b78a348f0df3adfae45d988d42ebd6be8a5adadb6c3","LICENSE":"e866c8f5864d4cacfe403820e722e9dc03fe3c7565efa5e4dad9051d827bb92a","README.md":"c87d9c7cc44f1dd4ef861a3a9f8cd2eb68aedd3814768871f5fb63c2070806cd","build.rs":"01092254718e4cd5d6bffcd64d55cc3240dc00e79f3d7344a5dc4abf6c27bca6","examples/ctap2.rs":"b56894078e5a49ede4c5c6fa10db5620049d55f0bde31834a04cc2087d618000","examples/ctap2_discoverable_creds.rs":"f683c2eac77f24bed57ddc1c9be2e67b37c8e3084ca4da4db9d1621da7beebf0","examples/interactive_management.rs":"2b3f7d9fc4f9d32806fa4974211b15a160ea26ae39138b4058d0a78dd1ee2fab","examples/reset.rs":"f6e03955cc5e40d6c7372031448faf78a2236479d4cba96a2ec36a804c65b89b","examples/set_pin.rs":"c220aeaf94d4b1f82314ee012a5ca2b487e263d261d14a2e38069b25004b7a90","examples/test_exclude_list.rs":"f6f24e03dea22391efcd9182055724980a05b0b6a52e0f6472b9d407e377de6a","rustfmt.toml":"ceb6615363d6fff16426eb56f5727f98a7f7ed459ba9af735b1d8b672e2c3b9b","src/authenticatorservice.rs":"dc756ae9d420dac187b04afbb4831527c12fa307ef072f1c1cb4480df9cbda5f","src/consts.rs":"d7b274ec55e56a8d2c150df28e23d72a7379e5bd7257d53d63c2a7b59ab90800","src/crypto/dummy.rs":"9cc6be0dc1e28c7328121e7a4bf435211ae8b1455784472b24993571c4009579","src/crypto/mod.rs":"dae4a08f6994b6bbd9df40fda10f0337a4cdd95527caf34fd52d756d725e1c53","src/crypto/nss.rs":"2bf33898728760f194f204876450d0906b47907d259270f6e3d43c62a709c99a","src/crypto/openssl.rs":"ef6e4dbcc7230137e505e3fc4ad37e102e6b26b37470afd0f4709a297b3aa546","src/ctap2/attestation.rs":"5e23b9895ae763ebdcdfd1f2417d5b1773609c7c60cfc27ce9eb34e55281918a","src/ctap2/client_data.rs":"04ee84b34e91c988183871b4975fc08e12234965187c793ad26d0d82ed44642f","src/ctap2/commands/client_pin.rs":"40aeb673084a9ef1d3288e7107162ca1084f8d5cc762bad3e32f3f54e1e75fc2","src/ctap2/commands/get_assertion.rs":"8a141a198deff2f44fce57760afb31e2f9fb28c77b2a49d9fe0009cd618f5745","src/ctap2/commands/get_info.rs":"c3a1c66279cb007aa4e19d29eadd84abc7d84d7763c9a05de17047f4baa8a692","src/ctap2/commands/get_next_assertion.rs":"1120b0301197ada11751c4c42fc0a4147368ec5427f62da3868347567d28ba3a","src/ctap2/commands/get_version.rs":"e7e2f83ead1c9d6d8611513120e1ac354109cff8e14dce5c3073a5c1fa677d71","src/ctap2/commands/make_credentials.rs":"17cdad6efcda5a468852bde14e7152c7abae0df009b4f003bdb9eff325afcb2e","src/ctap2/commands/mod.rs":"ee7869a42082acde4909e08f9d2a3c955777f1414fa09b573baeac9a95b360f2","src/ctap2/commands/reset.rs":"95dac8a400f66fb4725a3850a06d6b605ffc99bec4a90ca302a6e8f21deb48d8","src/ctap2/commands/selection.rs":"a2a4b2e30df1923c296f7ac3a8d38cfcac7c231669cfc39babcb7b02fac7cdee","src/ctap2/mod.rs":"672c581502ac6d1417d0fe42a92ef86b1f1d64799f48061b3d9ac026e35fb69d","src/ctap2/server.rs":"98ab464cb4368a57f4f0688fe6de3ca46e781aa6fdc5d0e6ada855be4b412efd","src/ctap2/utils.rs":"ad0aa36a0dbeb510b7f37789329f1957eab206eb529dc083e6176b142984e26e","src/errors.rs":"2fb7117e8cd87aafe305bb997c83dc91edd42e5528b0dc2d5e24cfac9597e8d7","src/lib.rs":"144ca5f912189bc9b50b47fbc8d2b776cdc5fe216a1d7754eee7180e19f724ca","src/manager.rs":"d72f8523d0a549487504ef6d370aee9132ad7436aaae777e6d65a0a03f3c0c27","src/statecallback.rs":"6b16f97176db1ae3fc3851fe8394e4ffc324bc6fe59313845ac3a88132fd52f1","src/statemachine.rs":"7c30ff7f5c005844cad1b9ba1341b6f10811a8c0ce4a02fafa1273fa166e2cee","src/status_update.rs":"45ae7897de9f676f3e3c63082c8e37f4cf6fa044f2111bf3337dd56180791df8","src/transport/device_selector.rs":"c703aa8e59b0b7ac9d11be0aac434dffda8b0c91e1a84298c48e598978e1576e","src/transport/errors.rs":"5af7cb8d22ffa63bf4264d182a0f54b9b3a2cc9d19d832b3495857229f9a2875","src/transport/freebsd/device.rs":"f41c7cf29c48bf2b403cf460e6387864372a134d6daeefc5c3afc3f40d0d4575","src/transport/freebsd/mod.rs":"42dcb57fbeb00140003a8ad39acac9b547062b8f281a3fa5deb5f92a6169dde6","src/transport/freebsd/monitor.rs":"a6b34af4dd2e357a5775b1f3a723766107c11ef98dba859b1188ed08e0e450a2","src/transport/freebsd/transaction.rs":"ec28475a70dded260f9a7908c7f88dd3771f5d64b9a5dda835411d13b713c39a","src/transport/freebsd/uhid.rs":"a194416a8bc5d428c337f8d96a2248769ca190810852bbe5ee686ab595d8eb4c","src/transport/hid.rs":"d0879b98abfb765cdc88281d47a9976fd7537971d660836ca74a3fc4405fd6a6","src/transport/hidproto.rs":"9d490f161807b75f4d7d5096355006627c1f47c0d90fca53bade3692efc92a2d","src/transport/linux/device.rs":"e79bd06d98723a0d7e4f25b7cf2ac3e0260b10e52d2b0695909d2932288e10a4","src/transport/linux/hidraw.rs":"c7a0df9b4e51cb2736218ffffa02b2b2547b7c515d69f9bae2c9a8c8f1cb547b","src/transport/linux/hidwrapper.h":"72785db3a9b27ea72b6cf13a958fee032af54304522d002f56322473978a20f9","src/transport/linux/hidwrapper.rs":"753c7459dbb73befdd186b6269ac33f7a4537b4c935928f50f2b2131756e787d","src/transport/linux/ioctl_aarch64le.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_armle.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_mips64le.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_mipsbe.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_mipsle.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_powerpc64be.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_powerpc64le.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_powerpcbe.rs":"fbda309934ad8bda689cd4fb5c0ca696fe26dedb493fe9d5a5322c3047d474fd","src/transport/linux/ioctl_riscv64.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_s390xbe.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_x86.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/ioctl_x86_64.rs":"2d8b265cd39a9f46816f83d5a5df0701c13eb842bc609325bad42ce50add3bf0","src/transport/linux/mod.rs":"446e435126d2a58f167f648dd95cba28e8ac9c17f1f799e1eaeab80ea800fc57","src/transport/linux/monitor.rs":"5e3ec2618dd74027ae6ca1527991254e3271cce59106d4920ce0414094e22f64","src/transport/linux/transaction.rs":"ec28475a70dded260f9a7908c7f88dd3771f5d64b9a5dda835411d13b713c39a","src/transport/macos/device.rs":"f508d0585079ecf87a73d6135c52e8b5a887fbf16e241676d51a8099a8001a81","src/transport/macos/iokit.rs":"7dc4e7bbf8e42e2fcde0cee8e48d14d6234a5a910bd5d3c4e966d8ba6b73992f","src/transport/macos/mod.rs":"333e561554fc901d4f6092f6e4c85823e2b0c4ff31c9188d0e6d542b71a0a07c","src/transport/macos/monitor.rs":"e02288454bb4010e06b705d82646abddb3799f0cd655f574aa19f9d91485a4a2","src/transport/macos/transaction.rs":"9dcdebd13d5fd5a185b5ad777a80c825a6ba5e76b141c238aa115b451b9a72fa","src/transport/mock/device.rs":"582b2b55f13d95dd9f1127e3dde49d2137a5ca020f9c1fa1ffa5c4083d05c0e7","src/transport/mock/mod.rs":"9c4c87efd19adddc1a91c699a6c328063cfbac5531b76346a5ff92e986aded8f","src/transport/mock/transaction.rs":"be3ed8c389dfa04122364b82515edd76fad6f5d5f72d15cacd45a84fb8397292","src/transport/mod.rs":"2c35da1ef07ba8119bb003fc9ad7944ae81469469147be3752bdc1d2c2e8aea3","src/transport/netbsd/device.rs":"a7dec83b5040faf1a8ddb37e9fc2b45b9b12814be4802b3b351eff081d1b80c3","src/transport/netbsd/fd.rs":"5464019025d03ea2a39c82f76b238bbbdb0ea63f5a5fc7c9d974e235139cd53b","src/transport/netbsd/mod.rs":"b1c52aa29537330cebe67427062d6c94871cab2a9b0c04b2305d686f07e88fd5","src/transport/netbsd/monitor.rs":"fb2917e4ba53cc9867987a539061f82d011f4c6e478df1157d965d32df2eb922","src/transport/netbsd/transaction.rs":"ec28475a70dded260f9a7908c7f88dd3771f5d64b9a5dda835411d13b713c39a","src/transport/netbsd/uhid.rs":"d15be35e2413240066a8f086bb8846b08a6a92bf6a1941c3eec1329dd3a4f9ce","src/transport/openbsd/device.rs":"47d8dfeb12c33e6cada2b2cd76476827059c797d8a16f2c4aea6e78d32ebab46","src/transport/openbsd/mod.rs":"514274d414042ff84b3667a41a736e78581e22fda87ccc97c2bc05617e381a30","src/transport/openbsd/monitor.rs":"2e0ba6ecc69b450be9cbfd21a7c65036ed2ce593b12363596d3eae0b5bfb79e8","src/transport/openbsd/transaction.rs":"ec28475a70dded260f9a7908c7f88dd3771f5d64b9a5dda835411d13b713c39a","src/transport/stub/device.rs":"aa21711d6690ed68bd878b28463172ba69c6324be7afabeccb1f07b4831cb020","src/transport/stub/mod.rs":"6a7fec504a52d403b0241b18cd8b95088a31807571f4c0a67e4055afc74f4453","src/transport/stub/transaction.rs":"c9a3ade9562468163f28fd51e7ff3e0bf5854b7edade9e987000d11c5d0e62d2","src/transport/windows/device.rs":"148b1572ed5fa8d476efbdb2a3a35608ec23012d6a805129f3c25c453bab4b7a","src/transport/windows/mod.rs":"218e7f2fe91ecb390c12bba5a5ffdad2c1f0b22861c937f4d386262e5b3dd617","src/transport/windows/monitor.rs":"95913d49e7d83482e420493d89b53ffceb6a49e646a87de934dff507b3092b4c","src/transport/windows/transaction.rs":"ec28475a70dded260f9a7908c7f88dd3771f5d64b9a5dda835411d13b713c39a","src/transport/windows/winapi.rs":"b2a4cc85f14e39cadfbf068ee001c9d776f028d3cf09cb926d4364c5b437c112","src/u2fprotocol.rs":"e61ac223aab79ae82383cd32a23213d18461e229c448373bf2483357a9eae69e","src/u2ftypes.rs":"8511c6f04f69670ddd403178a46060644a27128ca4077a9a3e00bc6671e3864b","src/util.rs":"cf37c4c3caf6dde4fc3cf6f5f297ed3c0f13bcb50fb0e8955899fc837483ef31","src/virtualdevices/mod.rs":"2c7df7691d5c150757304241351612aed4260d65b70ab0f483edbc1a5cfb5674","src/virtualdevices/software_u2f.rs":"83e63c0c4a597e71d87b5cd1f33a49646d00b3062edbdd05c51623b80fb60168","src/virtualdevices/webdriver/mod.rs":"4a36e6dfa9f45f941d863b4039bfbcfa8eaca660bd6ed78aeb1a2962db64be5a","src/virtualdevices/webdriver/testtoken.rs":"7146e02f1a5dad2c8827dd11c12ee408c0e42a0706ac65f139998feffd42570f","src/virtualdevices/webdriver/virtualmanager.rs":"7205a0397833628fc0847aa942a6a314dc1e23306858b546053e0de6a360ebe1","src/virtualdevices/webdriver/web_api.rs":"9032525af458b6fe9a3274c36b6ef8c791ecc4ec46d38ae36583fc9a4535b59d","testing/cross/powerpc64le-unknown-linux-gnu.Dockerfile":"d7463ff4376e3e0ca3fed879fab4aa975c4c0a3e7924c5b88aef9381a5d013de","testing/cross/x86_64-unknown-linux-gnu.Dockerfile":"11c79c04b07a171b0c9b63ef75fa75f33263ce76e3c1eda0879a3e723ebd0c24","testing/run_cross.sh":"cc2a7e0359f210eba2e7121f81eb8ab0125cea6e0d0f2698177b0fe2ad0c33d8","webdriver-tools/requirements.txt":"8236aa3dedad886f213c9b778fec80b037212d30e640b458984110211d546005","webdriver-tools/webdriver-driver.py":"82327c26ba271d1689acc87b612ab8436cb5475f0a3c0dba7baa06e7f6f5e19c"},"package":"9f3aaf9a8213bf1a4cfafca1a38befe0873a2a4d8013136d2dd12227e14bb2ba"} \ No newline at end of file diff --git a/third_party/rust/authenticator/Cargo.lock b/third_party/rust/authenticator/Cargo.lock index 15bb41dd479f..dc3ef4e6d4cb 100644 --- a/third_party/rust/authenticator/Cargo.lock +++ b/third_party/rust/authenticator/Cargo.lock @@ -39,7 +39,7 @@ dependencies = [ [[package]] name = "authenticator" -version = "0.4.0-alpha.13" +version = "0.4.0-alpha.14" dependencies = [ "assert_matches", "base64", diff --git a/third_party/rust/authenticator/Cargo.toml b/third_party/rust/authenticator/Cargo.toml index cecea7dee047..b0c8eb20cdbb 100644 --- a/third_party/rust/authenticator/Cargo.toml +++ b/third_party/rust/authenticator/Cargo.toml @@ -12,7 +12,7 @@ [package] edition = "2018" name = "authenticator" -version = "0.4.0-alpha.13" +version = "0.4.0-alpha.14" authors = [ "J.C. Jones ", "Tim Taubert ", diff --git a/third_party/rust/authenticator/examples/ctap2.rs b/third_party/rust/authenticator/examples/ctap2.rs index dcd8e4f29118..84dbcd72dff2 100644 --- a/third_party/rust/authenticator/examples/ctap2.rs +++ b/third_party/rust/authenticator/examples/ctap2.rs @@ -4,12 +4,12 @@ use authenticator::{ authenticatorservice::{ - AuthenticatorService, GetAssertionExtensions, GetAssertionOptions, - HmacSecretExtension, MakeCredentialsExtensions, MakeCredentialsOptions, RegisterArgs, - SignArgs, + AuthenticatorService, GetAssertionExtensions, HmacSecretExtension, + MakeCredentialsExtensions, RegisterArgs, SignArgs, }, ctap2::server::{ - PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, + ResidentKeyRequirement, Transport, User, UserVerificationRequirement, }, statecallback::StateCallback, COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate, @@ -40,6 +40,7 @@ fn main() { ); opts.optflag("s", "hmac_secret", "With hmac-secret"); opts.optflag("h", "help", "print this help menu"); + opts.optflag("f", "fallback", "Use CTAP1 fallback implementation"); let matches = match opts.parse(&args[1..]) { Ok(m) => m, Err(f) => panic!("{}", f.to_string()), @@ -49,13 +50,15 @@ fn main() { return; } - let mut manager = AuthenticatorService::new() - .expect("The auth service should initialize safely"); + let mut manager = + AuthenticatorService::new().expect("The auth service should initialize safely"); if !matches.opt_present("no-u2f-usb-hid") { manager.add_u2f_usb_hid_platform_transports(); } + let fallback = matches.opt_present("fallback"); + let timeout_ms = match matches.opt_get_default::("timeout", 25) { Ok(timeout_s) => { println!("Using {}s as the timeout", &timeout_s); @@ -78,12 +81,12 @@ fn main() { challenge.update(challenge_str.as_bytes()); let chall_bytes: [u8; 32] = challenge.finalize().into(); - // TODO(MS): Needs to be added to RegisterArgsCtap2 - // let flags = RegisterFlags::empty(); - let (status_tx, status_rx) = channel::(); thread::spawn(move || loop { match status_rx.recv() { + Ok(StatusUpdate::InteractiveManagement(..)) => { + panic!("STATUS: This can't happen when doing non-interactive usage"); + } Ok(StatusUpdate::DeviceAvailable { dev_info }) => { println!("STATUS: device available: {dev_info}") } @@ -153,7 +156,7 @@ fn main() { display_name: None, }; let origin = "https://example.com".to_string(); - let ctap_args = RegisterArgs{ + let ctap_args = RegisterArgs { client_data_hash: chall_bytes, relying_party: RelyingParty { id: "example.com".to_string(), @@ -178,10 +181,8 @@ fn main() { ], transports: vec![Transport::USB, Transport::NFC], }], - options: MakeCredentialsOptions { - resident_key: None, - user_verification: None, - }, + user_verification_req: UserVerificationRequirement::Preferred, + resident_key_req: ResidentKeyRequirement::Discouraged, extensions: MakeCredentialsExtensions { hmac_secret: if matches.opt_present("hmac_secret") { Some(true) @@ -191,7 +192,7 @@ fn main() { ..Default::default() }, pin: None, - use_ctap1_fallback: false, + use_ctap1_fallback: fallback, }; let attestation_object; @@ -201,8 +202,7 @@ fn main() { register_tx.send(rv).unwrap(); })); - if let Err(e) = manager.register(timeout_ms, ctap_args, status_tx.clone(), callback) - { + if let Err(e) = manager.register(timeout_ms, ctap_args, status_tx.clone(), callback) { panic!("Couldn't register: {:?}", e); }; @@ -242,7 +242,8 @@ fn main() { origin, relying_party_id: "example.com".to_string(), allow_list, - options: GetAssertionOptions::default(), + user_verification_req: UserVerificationRequirement::Preferred, + user_presence_req: true, extensions: GetAssertionExtensions { hmac_secret: if matches.opt_present("hmac_secret") { Some(HmacSecretExtension::new( @@ -259,7 +260,7 @@ fn main() { }, pin: None, alternate_rp_id: None, - use_ctap1_fallback: false, + use_ctap1_fallback: fallback, }; loop { diff --git a/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs index c30bd85ae943..0dc67fc4081e 100644 --- a/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs +++ b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs @@ -3,12 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use authenticator::{ - authenticatorservice::{ - AuthenticatorService, GetAssertionOptions, MakeCredentialsOptions, - RegisterArgs, SignArgs, - }, + authenticatorservice::{AuthenticatorService, RegisterArgs, SignArgs}, ctap2::server::{ - PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, + ResidentKeyRequirement, Transport, User, UserVerificationRequirement, }, statecallback::StateCallback, COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate, @@ -49,6 +47,9 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms: let (status_tx, status_rx) = channel::(); thread::spawn(move || loop { match status_rx.recv() { + Ok(StatusUpdate::InteractiveManagement(..)) => { + panic!("STATUS: This can't happen when doing non-interactive usage"); + } Ok(StatusUpdate::DeviceAvailable { dev_info }) => { println!("STATUS: device available: {dev_info}") } @@ -139,10 +140,8 @@ fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms: id: vec![], transports: vec![Transport::USB, Transport::NFC], }], - options: MakeCredentialsOptions { - resident_key: Some(true), - user_verification: Some(true), - }, + user_verification_req: UserVerificationRequirement::Required, + resident_key_req: ResidentKeyRequirement::Required, extensions: Default::default(), pin: None, use_ctap1_fallback: false, @@ -201,8 +200,8 @@ fn main() { return; } - let mut manager = AuthenticatorService::new() - .expect("The auth service should initialize safely"); + let mut manager = + AuthenticatorService::new().expect("The auth service should initialize safely"); if !matches.opt_present("no-u2f-usb-hid") { manager.add_u2f_usb_hid_platform_transports(); @@ -241,6 +240,9 @@ fn main() { let (status_tx, status_rx) = channel::(); thread::spawn(move || loop { match status_rx.recv() { + Ok(StatusUpdate::InteractiveManagement(..)) => { + panic!("STATUS: This can't happen when doing non-interactive usage"); + } Ok(StatusUpdate::DeviceAvailable { dev_info }) => { println!("STATUS: device available: {dev_info}") } @@ -311,7 +313,8 @@ fn main() { origin, relying_party_id: "example.com".to_string(), allow_list, - options: GetAssertionOptions::default(), + user_verification_req: UserVerificationRequirement::Required, + user_presence_req: true, extensions: Default::default(), pin: None, alternate_rp_id: None, diff --git a/third_party/rust/authenticator/examples/interactive_management.rs b/third_party/rust/authenticator/examples/interactive_management.rs new file mode 100644 index 000000000000..fc82f9f016e0 --- /dev/null +++ b/third_party/rust/authenticator/examples/interactive_management.rs @@ -0,0 +1,201 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use authenticator::{ + authenticatorservice::AuthenticatorService, errors::AuthenticatorError, + statecallback::StateCallback, InteractiveRequest, Pin, ResetResult, StatusUpdate, +}; +use getopts::Options; +use log::debug; +use std::{env, io, thread}; +use std::{ + io::Write, + sync::mpsc::{channel, Receiver, RecvError}, +}; + +fn print_usage(program: &str, opts: Options) { + let brief = format!("Usage: {program} [options]"); + print!("{}", opts.usage(&brief)); +} + +fn interactive_status_callback(status_rx: Receiver) { + let mut num_of_devices = 0; + loop { + match status_rx.recv() { + Ok(StatusUpdate::InteractiveManagement((tx, dev_info, auth_info))) => { + debug!( + "STATUS: interactive management: {:#}, {:#?}", + dev_info, auth_info + ); + println!("Device info {:#}", dev_info); + let mut change_pin = false; + if let Some(info) = auth_info { + println!("Authenticator Info {:#?}", info); + println!(); + println!("What do you wish to do?"); + + let mut choices = vec!["0", "1"]; + println!("(0) Quit"); + println!("(1) Reset token"); + match info.options.client_pin { + None => {} + Some(true) => { + println!("(2) Change PIN"); + choices.push("2"); + change_pin = true; + } + Some(false) => { + println!("(2) Set PIN"); + choices.push("2"); + } + } + + let mut input = String::new(); + while !choices.contains(&input.trim()) { + input.clear(); + print!("Your choice: "); + io::stdout() + .lock() + .flush() + .expect("Failed to flush stdout!"); + io::stdin() + .read_line(&mut input) + .expect("error: unable to read user input"); + } + input = input.trim().to_string(); + + match input.as_str() { + "0" => { + return; + } + "1" => { + tx.send(InteractiveRequest::Reset) + .expect("Failed to send Reset request."); + } + "2" => { + let raw_new_pin = rpassword::prompt_password_stderr("Enter new PIN: ") + .expect("Failed to read PIN"); + let new_pin = Pin::new(&raw_new_pin); + if change_pin { + let raw_curr_pin = + rpassword::prompt_password_stderr("Enter current PIN: ") + .expect("Failed to read PIN"); + let curr_pin = Pin::new(&raw_curr_pin); + tx.send(InteractiveRequest::ChangePIN(curr_pin, new_pin)) + .expect("Failed to send PIN-change request"); + } else { + tx.send(InteractiveRequest::SetPIN(new_pin)) + .expect("Failed to send PIN-set request"); + } + return; + } + _ => { + panic!("Can't happen"); + } + } + } else { + println!("Device only supports CTAP1 and can't be managed."); + } + } + Ok(StatusUpdate::DeviceAvailable { dev_info }) => { + num_of_devices += 1; + debug!( + "STATUS: New device #{} available: {}", + num_of_devices, dev_info + ); + } + Ok(StatusUpdate::DeviceUnavailable { dev_info }) => { + num_of_devices -= 1; + if num_of_devices <= 0 { + println!("No more devices left. Please plug in a device!"); + } + debug!("STATUS: Device became unavailable: {}", dev_info) + } + Ok(StatusUpdate::Success { dev_info }) => { + println!("STATUS: success using device: {}", dev_info); + } + Ok(StatusUpdate::SelectDeviceNotice) => { + println!("STATUS: Please select a device by touching one of them."); + } + Ok(StatusUpdate::DeviceSelected(_dev_info)) => {} + Ok(StatusUpdate::PinUvError(..)) => { + println!("STATUS: Pin Error!"); + } + Err(RecvError) => { + println!("STATUS: end"); + return; + } + } + } +} + +fn main() { + env_logger::init(); + + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + let mut opts = Options::new(); + opts.optflag("x", "no-u2f-usb-hid", "do not enable u2f-usb-hid platforms"); + opts.optflag("h", "help", "print this help menu").optopt( + "t", + "timeout", + "timeout in seconds", + "SEC", + ); + opts.optflag("h", "help", "print this help menu"); + let matches = match opts.parse(&args[1..]) { + Ok(m) => m, + Err(f) => panic!("{}", f.to_string()), + }; + if matches.opt_present("help") { + print_usage(&program, opts); + return; + } + + let mut manager = + AuthenticatorService::new().expect("The auth service should initialize safely"); + + if !matches.opt_present("no-u2f-usb-hid") { + manager.add_u2f_usb_hid_platform_transports(); + } + + let timeout_ms = match matches.opt_get_default::("timeout", 120) { + Ok(timeout_s) => { + println!("Using {}s as the timeout", &timeout_s); + timeout_s * 1_000 + } + Err(e) => { + println!("{e}"); + print_usage(&program, opts); + return; + } + }; + + let (status_tx, status_rx) = channel::(); + thread::spawn(move || interactive_status_callback(status_rx)); + + let (manage_tx, manage_rx) = channel(); + let state_callback = + StateCallback::>::new(Box::new(move |rv| { + manage_tx.send(rv).unwrap(); + })); + + match manager.manage(timeout_ms, status_tx, state_callback) { + Ok(_) => { + debug!("Started management") + } + Err(e) => { + println!("Error! Failed to start interactive management: {:?}", e) + } + } + let manage_result = manage_rx + .recv() + .expect("Problem receiving, unable to continue"); + match manage_result { + Ok(_) => println!("Success!"), + Err(e) => println!("Error! {:?}", e), + }; + println!("Done"); +} diff --git a/third_party/rust/authenticator/examples/set_pin.rs b/third_party/rust/authenticator/examples/set_pin.rs index 163fc1aa26a0..8005e226e7d2 100644 --- a/third_party/rust/authenticator/examples/set_pin.rs +++ b/third_party/rust/authenticator/examples/set_pin.rs @@ -70,6 +70,9 @@ fn main() { let (status_tx, status_rx) = channel::(); thread::spawn(move || loop { match status_rx.recv() { + Ok(StatusUpdate::InteractiveManagement(..)) => { + panic!("STATUS: This can't happen when doing non-interactive usage"); + } Ok(StatusUpdate::DeviceAvailable { dev_info }) => { println!("STATUS: device available: {dev_info}") } diff --git a/third_party/rust/authenticator/examples/test_exclude_list.rs b/third_party/rust/authenticator/examples/test_exclude_list.rs index d7dff61704df..86a3b0b51e18 100644 --- a/third_party/rust/authenticator/examples/test_exclude_list.rs +++ b/third_party/rust/authenticator/examples/test_exclude_list.rs @@ -3,12 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use authenticator::{ - authenticatorservice::{ - AuthenticatorService, MakeCredentialsOptions, RegisterArgs, - }, + authenticatorservice::{AuthenticatorService, RegisterArgs}, ctap2::commands::StatusCode, ctap2::server::{ - PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User, + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, + ResidentKeyRequirement, Transport, User, UserVerificationRequirement, }, errors::{AuthenticatorError, CommandError, HIDError}, statecallback::StateCallback, @@ -48,8 +47,8 @@ fn main() { return; } - let mut manager = AuthenticatorService::new() - .expect("The auth service should initialize safely"); + let mut manager = + AuthenticatorService::new().expect("The auth service should initialize safely"); manager.add_u2f_usb_hid_platform_transports(); @@ -75,12 +74,12 @@ fn main() { challenge.update(challenge_str.as_bytes()); let chall_bytes = challenge.finalize().into(); - // TODO(MS): Needs to be added to RegisterArgsCtap2 - // let flags = RegisterFlags::empty(); - let (status_tx, status_rx) = channel::(); thread::spawn(move || loop { match status_rx.recv() { + Ok(StatusUpdate::InteractiveManagement(..)) => { + panic!("STATUS: This can't happen when doing non-interactive usage"); + } Ok(StatusUpdate::DeviceAvailable { dev_info }) => { println!("STATUS: device available: {dev_info}") } @@ -168,10 +167,8 @@ fn main() { }, ], exclude_list: vec![], - options: MakeCredentialsOptions { - resident_key: None, - user_verification: None, - }, + user_verification_req: UserVerificationRequirement::Preferred, + resident_key_req: ResidentKeyRequirement::Discouraged, extensions: Default::default(), pin: None, use_ctap1_fallback: false, @@ -183,12 +180,8 @@ fn main() { register_tx.send(rv).unwrap(); })); - if let Err(e) = manager.register( - timeout_ms, - ctap_args.clone(), - status_tx.clone(), - callback, - ) { + if let Err(e) = manager.register(timeout_ms, ctap_args.clone(), status_tx.clone(), callback) + { panic!("Couldn't register: {:?}", e); }; diff --git a/third_party/rust/authenticator/src/authenticatorservice.rs b/third_party/rust/authenticator/src/authenticatorservice.rs index d439cb8f2cb1..4bfce3bf4cc5 100644 --- a/third_party/rust/authenticator/src/authenticatorservice.rs +++ b/third_party/rust/authenticator/src/authenticatorservice.rs @@ -3,14 +3,11 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use crate::ctap2::commands::client_pin::Pin; -pub use crate::ctap2::commands::get_assertion::{ - GetAssertionExtensions, GetAssertionOptions, HmacSecretExtension, -}; -pub use crate::ctap2::commands::make_credentials::{ - MakeCredentialsExtensions, MakeCredentialsOptions, -}; +pub use crate::ctap2::commands::get_assertion::{GetAssertionExtensions, HmacSecretExtension}; +pub use crate::ctap2::commands::make_credentials::MakeCredentialsExtensions; use crate::ctap2::server::{ - PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, User, + PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, + ResidentKeyRequirement, User, UserVerificationRequirement, }; use crate::errors::*; use crate::manager::Manager; @@ -25,7 +22,8 @@ pub struct RegisterArgs { pub user: User, pub pub_cred_params: Vec, pub exclude_list: Vec, - pub options: MakeCredentialsOptions, + pub user_verification_req: UserVerificationRequirement, + pub resident_key_req: ResidentKeyRequirement, pub extensions: MakeCredentialsExtensions, pub pin: Option, pub use_ctap1_fallback: bool, @@ -37,7 +35,8 @@ pub struct SignArgs { pub origin: String, pub relying_party_id: String, pub allow_list: Vec, - pub options: GetAssertionOptions, + pub user_verification_req: UserVerificationRequirement, + pub user_presence_req: bool, pub extensions: GetAssertionExtensions, pub pin: Option, pub alternate_rp_id: Option, @@ -85,6 +84,12 @@ pub trait AuthenticatorTransport { status: Sender, callback: StateCallback>, ) -> crate::Result<()>; + fn manage( + &mut self, + timeout: u64, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()>; } pub struct AuthenticatorService { @@ -288,6 +293,39 @@ impl AuthenticatorService { Ok(()) } + + pub fn manage( + &mut self, + timeout: u64, + status: Sender, + callback: StateCallback>, + ) -> crate::Result<()> { + let iterable_transports = self.transports.clone(); + if iterable_transports.is_empty() { + return Err(AuthenticatorError::NoConfiguredTransports); + } + + debug!( + "Manage called with {} transports, iterable is {}", + self.transports.len(), + iterable_transports.len() + ); + + for (idx, transport_mutex) in iterable_transports.iter().enumerate() { + let mut transports_to_cancel = iterable_transports.clone(); + transports_to_cancel.remove(idx); + + debug!("reset transports_to_cancel {}", transports_to_cancel.len()); + + transport_mutex.lock().unwrap().manage( + timeout, + status.clone(), + clone_and_configure_cancellation_callback(callback.clone(), transports_to_cancel), + )?; + } + + Ok(()) + } } //////////////////////////////////////////////////////////////////////// @@ -298,7 +336,9 @@ impl AuthenticatorService { mod tests { use super::{AuthenticatorService, AuthenticatorTransport, Pin, RegisterArgs, SignArgs}; use crate::consts::{Capability, PARAMETER_SIZE}; - use crate::ctap2::server::{RelyingParty, User}; + use crate::ctap2::server::{ + RelyingParty, ResidentKeyRequirement, User, UserVerificationRequirement, + }; use crate::statecallback::StateCallback; use crate::{RegisterResult, SignResult, StatusUpdate}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -401,6 +441,15 @@ mod tests { ) -> crate::Result<()> { unimplemented!(); } + + fn manage( + &mut self, + _timeout: u64, + _status: Sender, + _callback: StateCallback>, + ) -> crate::Result<()> { + unimplemented!(); + } } fn mk_challenge() -> [u8; PARAMETER_SIZE] { @@ -432,7 +481,8 @@ mod tests { }, pub_cred_params: vec![], exclude_list: vec![], - options: Default::default(), + user_verification_req: UserVerificationRequirement::Preferred, + resident_key_req: ResidentKeyRequirement::Preferred, extensions: Default::default(), pin: None, use_ctap1_fallback: false, @@ -453,7 +503,8 @@ mod tests { origin: "example.com".to_string(), relying_party_id: "example.com".to_string(), allow_list: vec![], - options: Default::default(), + user_verification_req: UserVerificationRequirement::Preferred, + user_presence_req: true, extensions: Default::default(), pin: None, alternate_rp_id: None, @@ -511,7 +562,8 @@ mod tests { }, pub_cred_params: vec![], exclude_list: vec![], - options: Default::default(), + user_verification_req: UserVerificationRequirement::Preferred, + resident_key_req: ResidentKeyRequirement::Preferred, extensions: Default::default(), pin: None, use_ctap1_fallback: false, @@ -555,7 +607,8 @@ mod tests { origin: "example.com".to_string(), relying_party_id: "example.com".to_string(), allow_list: vec![], - options: Default::default(), + user_verification_req: UserVerificationRequirement::Preferred, + user_presence_req: true, extensions: Default::default(), pin: None, alternate_rp_id: None, @@ -609,7 +662,8 @@ mod tests { }, pub_cred_params: vec![], exclude_list: vec![], - options: Default::default(), + user_verification_req: UserVerificationRequirement::Preferred, + resident_key_req: ResidentKeyRequirement::Preferred, extensions: Default::default(), pin: None, use_ctap1_fallback: false, diff --git a/third_party/rust/authenticator/src/crypto/mod.rs b/third_party/rust/authenticator/src/crypto/mod.rs index 2fb6e850f5b8..6ad5b9b1bf27 100644 --- a/third_party/rust/authenticator/src/crypto/mod.rs +++ b/third_party/rust/authenticator/src/crypto/mod.rs @@ -362,6 +362,15 @@ pub struct PinUvAuthParam { pin_auth: Vec, } +impl PinUvAuthParam { + pub(crate) fn create_empty() -> Self { + Self { + pin_protocol: PinUvAuthProtocol(Box::new(PinUvAuth1 {})), + pin_auth: vec![], + } + } +} + impl Serialize for PinUvAuthParam { fn serialize(&self, serializer: S) -> Result where diff --git a/third_party/rust/authenticator/src/ctap2/commands/client_pin.rs b/third_party/rust/authenticator/src/ctap2/commands/client_pin.rs index a7afc2e39f26..f97070606174 100644 --- a/third_party/rust/authenticator/src/ctap2/commands/client_pin.rs +++ b/third_party/rust/authenticator/src/ctap2/commands/client_pin.rs @@ -679,7 +679,7 @@ impl AsRef<[u8]> for EncryptedPinToken { } } -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct Pin(String); impl fmt::Debug for Pin { diff --git a/third_party/rust/authenticator/src/ctap2/commands/get_assertion.rs b/third_party/rust/authenticator/src/ctap2/commands/get_assertion.rs index 91ba6cddf7d5..7e6cff530cec 100644 --- a/third_party/rust/authenticator/src/ctap2/commands/get_assertion.rs +++ b/third_party/rust/authenticator/src/ctap2/commands/get_assertion.rs @@ -12,7 +12,9 @@ use crate::ctap2::client_data::ClientDataHash; use crate::ctap2::commands::client_pin::Pin; use crate::ctap2::commands::get_next_assertion::GetNextAssertion; use crate::ctap2::commands::make_credentials::UserVerification; -use crate::ctap2::server::{PublicKeyCredentialDescriptor, RelyingPartyWrapper, RpIdHash, User}; +use crate::ctap2::server::{ + PublicKeyCredentialDescriptor, RelyingPartyWrapper, RpIdHash, User, UserVerificationRequirement, +}; use crate::errors::AuthenticatorError; use crate::transport::errors::{ApduErrorStatus, HIDError}; use crate::transport::FidoDevice; @@ -218,7 +220,6 @@ pub struct GetAssertion { // This is used to implement the FIDO AppID extension. pub(crate) alternate_rp_id: Option, - pub(crate) use_ctap1_fallback: bool, } impl GetAssertion { @@ -230,9 +231,8 @@ impl GetAssertion { extensions: GetAssertionExtensions, pin: Option, alternate_rp_id: Option, - use_ctap1_fallback: bool, - ) -> Result { - Ok(Self { + ) -> Self { + Self { client_data_hash, rp, allow_list, @@ -241,8 +241,7 @@ impl GetAssertion { pin, pin_uv_auth_param: None, alternate_rp_id, - use_ctap1_fallback, - }) + } } } @@ -275,10 +274,6 @@ impl PinUvAuthCommand for GetAssertion { self.options.user_verification = uv; } - fn get_uv_option(&mut self) -> Option { - self.options.user_verification - } - fn get_rp_id(&self) -> Option<&String> { match &self.rp { // CTAP1 case: We only have the hash, not the entire RpID @@ -287,24 +282,18 @@ impl PinUvAuthCommand for GetAssertion { } } - fn can_skip_user_verification(&mut self, info: &AuthenticatorInfo) -> bool { + fn can_skip_user_verification( + &mut self, + info: &AuthenticatorInfo, + uv_req: UserVerificationRequirement, + ) -> bool { let supports_uv = info.options.user_verification == Some(true); let pin_configured = info.options.client_pin == Some(true); let device_protected = supports_uv || pin_configured; - let uv_preferred_or_required = self.get_uv_option() != Some(false); + let uv_discouraged = uv_req == UserVerificationRequirement::Discouraged; let always_uv = info.options.always_uv == Some(true); - if always_uv || (device_protected && uv_preferred_or_required) { - // If the token is protected AND the RP doesn't specifically discourage UV, we have to use it - self.set_uv_option(Some(true)); - false - } else { - // "[..] the Relying Party does not wish to require user verification (e.g., by setting options.userVerification - // to "discouraged" in the WebAuthn API), the platform invokes the authenticatorGetAssertion operation using - // the marshalled input parameters along with an absent "uv" option key." - self.set_uv_option(None); - true - } + !always_uv && (!device_protected || uv_discouraged) } } @@ -730,9 +719,7 @@ pub mod test { Default::default(), None, None, - false, - ) - .expect("Failed to create GetAssertion"); + ); let mut device = Device::new("commands/get_assertion").unwrap(); let mut cid = [0u8; 4]; thread_rng().fill_bytes(&mut cid); @@ -933,9 +920,7 @@ pub mod test { Default::default(), None, None, - false, - ) - .expect("Failed to create GetAssertion"); + ); let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it) // channel id let mut cid = [0u8; 4]; @@ -1022,9 +1007,7 @@ pub mod test { Default::default(), None, None, - false, - ) - .expect("Failed to create GetAssertion"); + ); let mut device = Device::new("commands/get_assertion").unwrap(); // not really used (all functions ignore it) // channel id diff --git a/third_party/rust/authenticator/src/ctap2/commands/get_info.rs b/third_party/rust/authenticator/src/ctap2/commands/get_info.rs index 22da6efc0a2c..afbbcaa08b9f 100644 --- a/third_party/rust/authenticator/src/ctap2/commands/get_info.rs +++ b/third_party/rust/authenticator/src/ctap2/commands/get_info.rs @@ -62,17 +62,17 @@ fn true_val() -> bool { true } -#[derive(Debug, Deserialize, Clone, Eq, PartialEq)] -pub(crate) struct AuthenticatorOptions { +#[derive(Debug, Deserialize, Clone, Eq, PartialEq, Serialize)] +pub struct AuthenticatorOptions { /// Indicates that the device is attached to the client and therefore can’t /// be removed and used on another client. #[serde(rename = "plat", default)] - pub(crate) platform_device: bool, + pub platform_device: bool, /// Indicates that the device is capable of storing keys on the device /// itself and therefore can satisfy the authenticatorGetAssertion request /// with allowList parameter not specified or empty. #[serde(rename = "rk", default)] - pub(crate) resident_key: bool, + pub resident_key: bool, /// Client PIN: /// If present and set to true, it indicates that the device is capable of @@ -83,11 +83,11 @@ pub(crate) struct AuthenticatorOptions { /// PIN from the client. /// Client PIN is one of the ways to do user verification. #[serde(rename = "clientPin")] - pub(crate) client_pin: Option, + pub client_pin: Option, /// Indicates that the device is capable of testing user presence. #[serde(rename = "up", default = "true_val")] - pub(crate) user_presence: bool, + pub user_presence: bool, /// Indicates that the device is capable of verifying the user within /// itself. For example, devices with UI, biometrics fall into this @@ -107,7 +107,7 @@ pub(crate) struct AuthenticatorOptions { // TODO(MS): My Token (key-ID FIDO2) does return Some(false) here, even though // it has no built-in verification method. Not to be trusted... #[serde(rename = "uv")] - pub(crate) user_verification: Option, + pub user_verification: Option, // ---------------------------------------------------- // CTAP 2.1 options @@ -124,7 +124,7 @@ pub(crate) struct AuthenticatorOptions { /// getPinUvAuthTokenUsingPinWithPermissions and getPinUvAuthTokenUsingUvWithPermissions /// subcommands. #[serde(rename = "pinUvAuthToken")] - pub(crate) pin_uv_auth_token: Option, + pub pin_uv_auth_token: Option, /// If this noMcGaPermissionsWithClientPin is: /// present and set to true @@ -141,7 +141,7 @@ pub(crate) struct AuthenticatorOptions { /// Note: noMcGaPermissionsWithClientPin MUST only be present if the /// clientPin option ID is present. #[serde(rename = "noMcGaPermissionsWithClientPin")] - pub(crate) no_mc_ga_permissions_with_client_pin: Option, + pub no_mc_ga_permissions_with_client_pin: Option, /// If largeBlobs is: /// present and set to true @@ -149,7 +149,7 @@ pub(crate) struct AuthenticatorOptions { /// present and set to false, or absent. /// The authenticatorLargeBlobs command is NOT supported. #[serde(rename = "largeBlobs")] - pub(crate) large_blobs: Option, + pub large_blobs: Option, /// Enterprise Attestation feature support: /// If ep is: @@ -162,7 +162,7 @@ pub(crate) struct AuthenticatorOptions { /// Absent /// The Enterprise Attestation feature is NOT supported. #[serde(rename = "ep")] - pub(crate) ep: Option, + pub ep: Option, /// If bioEnroll is: /// present and set to true @@ -174,7 +174,7 @@ pub(crate) struct AuthenticatorOptions { /// absent /// the authenticatorBioEnrollment commands are NOT supported. #[serde(rename = "bioEnroll")] - pub(crate) bio_enroll: Option, + pub bio_enroll: Option, /// "FIDO_2_1_PRE" Prototype Credential management support: /// If userVerificationMgmtPreview is: @@ -187,7 +187,7 @@ pub(crate) struct AuthenticatorOptions { /// absent /// the Prototype authenticatorBioEnrollment (0x41) commands are not supported. #[serde(rename = "userVerificationMgmtPreview")] - pub(crate) user_verification_mgmt_preview: Option, + pub user_verification_mgmt_preview: Option, /// getPinUvAuthTokenUsingUvWithPermissions support for requesting the be permission: /// This option ID MUST only be present if bioEnroll is also present. @@ -199,7 +199,7 @@ pub(crate) struct AuthenticatorOptions { /// requesting the be permission when invoking getPinUvAuthTokenUsingUvWithPermissions /// is NOT supported. #[serde(rename = "uvBioEnroll")] - pub(crate) uv_bio_enroll: Option, + pub uv_bio_enroll: Option, /// authenticatorConfig command support: /// If authnrCfg is: @@ -208,7 +208,7 @@ pub(crate) struct AuthenticatorOptions { /// present and set to false, or absent. /// the authenticatorConfig command is NOT supported. #[serde(rename = "authnrCfg")] - pub(crate) authnr_cfg: Option, + pub authnr_cfg: Option, /// getPinUvAuthTokenUsingUvWithPermissions support for requesting the acfg permission: /// This option ID MUST only be present if authnrCfg is also present. @@ -220,7 +220,7 @@ pub(crate) struct AuthenticatorOptions { /// requesting the acfg permission when invoking getPinUvAuthTokenUsingUvWithPermissions /// is NOT supported. #[serde(rename = "uvAcfg")] - pub(crate) uv_acfg: Option, + pub uv_acfg: Option, /// Credential management support: /// If credMgmt is: @@ -229,7 +229,7 @@ pub(crate) struct AuthenticatorOptions { /// present and set to false, or absent. /// the authenticatorCredentialManagement command is NOT supported. #[serde(rename = "credMgmt")] - pub(crate) cred_mgmt: Option, + pub cred_mgmt: Option, /// "FIDO_2_1_PRE" Prototype Credential management support: /// If credentialMgmtPreview is: @@ -238,7 +238,7 @@ pub(crate) struct AuthenticatorOptions { /// present and set to false, or absent. /// the Prototype authenticatorCredentialManagement (0x41) command is NOT supported. #[serde(rename = "credentialMgmtPreview")] - pub(crate) credential_mgmt_preview: Option, + pub credential_mgmt_preview: Option, /// Support for the Set Minimum PIN Length feature. /// If setMinPINLength is: @@ -248,7 +248,7 @@ pub(crate) struct AuthenticatorOptions { /// the setMinPINLength subcommand is NOT supported. /// Note: setMinPINLength MUST only be present if the clientPin option ID is present. #[serde(rename = "setMinPINLength")] - pub(crate) set_min_pin_length: Option, + pub set_min_pin_length: Option, /// Support for making non-discoverable credentials without requiring User Verification. /// If makeCredUvNotRqd is: @@ -261,7 +261,7 @@ pub(crate) struct AuthenticatorOptions { /// for the authenticatorMakeCredential command. /// Authenticators SHOULD include this option with the value true. #[serde(rename = "makeCredUvNotRqd")] - pub(crate) make_cred_uv_not_rqd: Option, + pub make_cred_uv_not_rqd: Option, /// Support for the Always Require User Verification feature: /// If alwaysUv is @@ -274,7 +274,7 @@ pub(crate) struct AuthenticatorOptions { /// Note: If the alwaysUv option ID is present and true the authenticator MUST set the value /// of makeCredUvNotRqd to false. #[serde(rename = "alwaysUv")] - pub(crate) always_uv: Option, + pub always_uv: Option, } impl Default for AuthenticatorOptions { @@ -312,30 +312,30 @@ pub enum AuthenticatorVersion { FIDO_2_1, } -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct AuthenticatorInfo { - pub(crate) versions: Vec, - pub(crate) extensions: Vec, - pub(crate) aaguid: AAGuid, - pub(crate) options: AuthenticatorOptions, - pub(crate) max_msg_size: Option, - pub(crate) pin_protocols: Vec, + pub versions: Vec, + pub extensions: Vec, + pub aaguid: AAGuid, + pub options: AuthenticatorOptions, + pub max_msg_size: Option, + pub pin_protocols: Vec, // CTAP 2.1 - pub(crate) max_credential_count_in_list: Option, - pub(crate) max_credential_id_length: Option, - pub(crate) transports: Option>, - pub(crate) algorithms: Option>, - pub(crate) max_ser_large_blob_array: Option, - pub(crate) force_pin_change: Option, - pub(crate) min_pin_length: Option, - pub(crate) firmware_version: Option, - pub(crate) max_cred_blob_length: Option, - pub(crate) max_rpids_for_set_min_pin_length: Option, - pub(crate) preferred_platform_uv_attempts: Option, - pub(crate) uv_modality: Option, - pub(crate) certifications: Option>, - pub(crate) remaining_discoverable_credentials: Option, - pub(crate) vendor_prototype_config_commands: Option>, + pub max_credential_count_in_list: Option, + pub max_credential_id_length: Option, + pub transports: Option>, + pub algorithms: Option>, + pub max_ser_large_blob_array: Option, + pub force_pin_change: Option, + pub min_pin_length: Option, + pub firmware_version: Option, + pub max_cred_blob_length: Option, + pub max_rpids_for_set_min_pin_length: Option, + pub preferred_platform_uv_attempts: Option, + pub uv_modality: Option, + pub certifications: Option>, + pub remaining_discoverable_credentials: Option, + pub vendor_prototype_config_commands: Option>, } impl AuthenticatorInfo { diff --git a/third_party/rust/authenticator/src/ctap2/commands/make_credentials.rs b/third_party/rust/authenticator/src/ctap2/commands/make_credentials.rs index b815e3324d86..ae885c2e37c6 100644 --- a/third_party/rust/authenticator/src/ctap2/commands/make_credentials.rs +++ b/third_party/rust/authenticator/src/ctap2/commands/make_credentials.rs @@ -17,7 +17,7 @@ use crate::ctap2::commands::client_pin::Pin; use crate::ctap2::commands::get_assertion::CheckKeyHandle; use crate::ctap2::server::{ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, - RelyingPartyWrapper, RpIdHash, User, + RelyingPartyWrapper, RpIdHash, User, UserVerificationRequirement, }; use crate::errors::AuthenticatorError; use crate::transport::{ @@ -174,7 +174,6 @@ pub struct MakeCredentials { pub(crate) pin: Option, pub(crate) pin_uv_auth_param: Option, pub(crate) enterprise_attestation: Option, - pub(crate) use_ctap1_fallback: bool, } impl MakeCredentials { @@ -188,9 +187,8 @@ impl MakeCredentials { options: MakeCredentialsOptions, extensions: MakeCredentialsExtensions, pin: Option, - use_ctap1_fallback: bool, - ) -> Result { - Ok(Self { + ) -> Self { + Self { client_data_hash, rp, user, @@ -201,8 +199,7 @@ impl MakeCredentials { pin, pin_uv_auth_param: None, enterprise_attestation: None, - use_ctap1_fallback, - }) + } } } @@ -235,10 +232,6 @@ impl PinUvAuthCommand for MakeCredentials { self.options.user_verification = uv; } - fn get_uv_option(&mut self) -> Option { - self.options.user_verification - } - fn get_rp_id(&self) -> Option<&String> { match &self.rp { // CTAP1 case: We only have the hash, not the entire RpID @@ -247,7 +240,11 @@ impl PinUvAuthCommand for MakeCredentials { } } - fn can_skip_user_verification(&mut self, info: &AuthenticatorInfo) -> bool { + fn can_skip_user_verification( + &mut self, + info: &AuthenticatorInfo, + uv_req: UserVerificationRequirement, + ) -> bool { // TODO(MS): Handle here the case where we NEED a UV, the device supports PINs, but hasn't set a PIN. // For this, the user has to be prompted to set a PIN first (see https://github.com/mozilla/authenticator-rs/issues/223) @@ -260,27 +257,13 @@ impl PinUvAuthCommand for MakeCredentials { // For CTAP2.0, UV is always required when doing MakeCredential let always_uv = info.options.always_uv == Some(true) || info.max_supported_version() == AuthenticatorVersion::FIDO_2_0; - let uv_discouraged = self.get_uv_option() == Some(false); + let uv_discouraged = uv_req == UserVerificationRequirement::Discouraged; // CTAP 2.1 authenticators can allow MakeCredential without PinUvAuth, // but that is only relevant, if RP also discourages UV. let can_make_cred_without_uv = make_cred_uv_not_required && uv_discouraged; - if always_uv || (device_protected && !can_make_cred_without_uv) { - // If the token is protected, we have to require UV anyways - self.set_uv_option(Some(true)); - false - } else { - // "[..] the Relying Party wants to create a non-discoverable credential and not require user verification - // (e.g., by setting options.authenticatorSelection.userVerification to "discouraged" in the WebAuthn API), - // the platform invokes the authenticatorMakeCredential operation using the marshalled input parameters along - // with the "uv" option key set to false and terminate these steps." - // Note: This is basically a no-op right now, since we use `get_uv_option() == Some(false)`, to determine if - // the RP is discouraging UV. But we may change that part of the API in the future, so better to be - // explicit here. - self.set_uv_option(Some(false)); - true - } + !always_uv && (!device_protected || can_make_cred_without_uv) } } @@ -353,14 +336,6 @@ impl RequestCtap1 for MakeCredentials { where Dev: io::Read + io::Write + fmt::Debug + FidoDevice, { - // TODO(MS): Mandatory sanity checks are missing: - // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#u2f-authenticatorMakeCredential-interoperability - // If any of the below conditions is not true, platform errors out with - // CTAP2_ERR_UNSUPPORTED_OPTION. - // * pubKeyCredParams must use the ES256 algorithm (-7). - // * Options must not include "rk" set to true. - // * Options must not include "uv" set to true. - let is_already_registered = self .exclude_list .iter() @@ -469,7 +444,7 @@ impl RequestCtap2 for MakeCredentials { } pub(crate) fn dummy_make_credentials_cmd() -> Result { - MakeCredentials::new( + let mut req = MakeCredentials::new( CollectedClientData { webauthn_type: WebauthnType::Create, challenge: Challenge::new(vec![0, 1, 2, 3, 4]), @@ -477,8 +452,7 @@ pub(crate) fn dummy_make_credentials_cmd() -> Result cross_origin: false, token_binding: None, } - .hash() - .expect("failed to serialize client data"), + .hash()?, RelyingPartyWrapper::Data(RelyingParty { id: String::from("make.me.blink"), ..Default::default() @@ -495,8 +469,12 @@ pub(crate) fn dummy_make_credentials_cmd() -> Result MakeCredentialsOptions::default(), MakeCredentialsExtensions::default(), None, - false, - ) + ); + // Using a zero-length pinAuth will trigger the device to blink. + // For CTAP1, this gets ignored anyways and we do a 'normal' register + // command, which also just blinks. + req.pin_uv_auth_param = Some(PinUvAuthParam::create_empty()); + Ok(req) } #[cfg(test)] @@ -648,9 +626,7 @@ pub mod test { }, Default::default(), None, - false, - ) - .expect("Failed to create MakeCredentials"); + ); let mut device = Device::new("commands/make_credentials").unwrap(); // not really used (all functions ignore it) let req_serialized = req @@ -708,9 +684,7 @@ pub mod test { }, Default::default(), None, - false, - ) - .expect("Failed to create MakeCredentials"); + ); let mut device = Device::new("commands/make_credentials").unwrap(); // not really used (all functions ignore it) let (req_serialized, _) = req diff --git a/third_party/rust/authenticator/src/ctap2/commands/mod.rs b/third_party/rust/authenticator/src/ctap2/commands/mod.rs index ffb029e81635..a6b37f530a07 100644 --- a/third_party/rust/authenticator/src/ctap2/commands/mod.rs +++ b/third_party/rust/authenticator/src/ctap2/commands/mod.rs @@ -2,6 +2,7 @@ use crate::crypto::{CryptoError, PinUvAuthToken}; use crate::ctap2::commands::client_pin::{GetPinRetries, GetUvRetries, Pin, PinError}; use crate::ctap2::commands::get_info::AuthenticatorInfo; +use crate::ctap2::server::UserVerificationRequirement; use crate::errors::AuthenticatorError; use crate::transport::errors::{ApduErrorStatus, HIDError}; use crate::transport::FidoDevice; @@ -121,9 +122,12 @@ pub(crate) trait PinUvAuthCommand: RequestCtap2 { pin_uv_auth_token: Option, ) -> Result<(), AuthenticatorError>; fn set_uv_option(&mut self, uv: Option); - fn get_uv_option(&mut self) -> Option; fn get_rp_id(&self) -> Option<&String>; - fn can_skip_user_verification(&mut self, info: &AuthenticatorInfo) -> bool; + fn can_skip_user_verification( + &mut self, + info: &AuthenticatorInfo, + uv_req: UserVerificationRequirement, + ) -> bool; } pub(crate) fn repackage_pin_errors( diff --git a/third_party/rust/authenticator/src/ctap2/server.rs b/third_party/rust/authenticator/src/ctap2/server.rs index 33326013710b..00626c55b60e 100644 --- a/third_party/rust/authenticator/src/ctap2/server.rs +++ b/third_party/rust/authenticator/src/ctap2/server.rs @@ -300,6 +300,20 @@ impl From<&KeyHandle> for PublicKeyCredentialDescriptor { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ResidentKeyRequirement { + Discouraged, + Preferred, + Required, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum UserVerificationRequirement { + Discouraged, + Preferred, + Required, +} + #[cfg(test)] mod test { use super::{ diff --git a/third_party/rust/authenticator/src/errors.rs b/third_party/rust/authenticator/src/errors.rs index 58bdd8e8a7b2..e9acc663f552 100644 --- a/third_party/rust/authenticator/src/errors.rs +++ b/third_party/rust/authenticator/src/errors.rs @@ -13,8 +13,12 @@ use std::sync::mpsc; #[derive(Debug)] pub enum UnsupportedOption { - MaxPinLength, + EmptyAllowList, HmacSecret, + MaxPinLength, + PubCredParams, + ResidentKey, + UserVerification, } #[derive(Debug)] diff --git a/third_party/rust/authenticator/src/lib.rs b/third_party/rust/authenticator/src/lib.rs index 52cfa5d1f9c1..926ab1873805 100644 --- a/third_party/rust/authenticator/src/lib.rs +++ b/third_party/rust/authenticator/src/lib.rs @@ -40,6 +40,7 @@ pub use ctap2::attestation::AttestationObject; pub use ctap2::client_data::CollectedClientData; pub use ctap2::commands::client_pin::{Pin, PinError}; pub use ctap2::commands::get_assertion::Assertion; +pub use ctap2::commands::get_info::AuthenticatorInfo; pub use ctap2::AssertionObject; pub mod errors; diff --git a/third_party/rust/authenticator/src/manager.rs b/third_party/rust/authenticator/src/manager.rs index 544bc1e82c46..3a62a9225266 100644 --- a/third_party/rust/authenticator/src/manager.rs +++ b/third_party/rust/authenticator/src/manager.rs @@ -4,11 +4,6 @@ use crate::authenticatorservice::AuthenticatorTransport; use crate::authenticatorservice::{RegisterArgs, SignArgs}; - -use crate::ctap2::client_data::ClientDataHash; -use crate::ctap2::commands::get_assertion::GetAssertion; -use crate::ctap2::commands::make_credentials::MakeCredentials; -use crate::ctap2::server::{RelyingParty, RelyingPartyWrapper}; use crate::errors::*; use crate::statecallback::StateCallback; use crate::statemachine::StateMachine; @@ -21,13 +16,13 @@ use std::time::Duration; enum QueueAction { Register { timeout: u64, - make_credentials: MakeCredentials, + register_args: RegisterArgs, status: Sender, callback: StateCallback>, }, Sign { timeout: u64, - get_assertion: GetAssertion, + sign_args: SignArgs, status: Sender, callback: StateCallback>, }, @@ -43,6 +38,11 @@ enum QueueAction { status: Sender, callback: StateCallback>, }, + InteractiveManagement { + timeout: u64, + status: Sender, + callback: StateCallback>, + }, } pub struct Manager { @@ -62,22 +62,22 @@ impl Manager { match rx.recv_timeout(Duration::from_millis(50)) { Ok(QueueAction::Register { timeout, - make_credentials, + register_args, status, callback, }) => { // This must not block, otherwise we can't cancel. - sm.register(timeout, make_credentials, status, callback); + sm.register(timeout, register_args, status, callback); } Ok(QueueAction::Sign { timeout, - get_assertion, + sign_args, status, callback, }) => { // This must not block, otherwise we can't cancel. - sm.sign(timeout, get_assertion, status, callback); + sm.sign(timeout, sign_args, status, callback); } Ok(QueueAction::Cancel) => { @@ -105,6 +105,15 @@ impl Manager { sm.set_pin(timeout, new_pin, status, callback); } + Ok(QueueAction::InteractiveManagement { + timeout, + status, + callback, + }) => { + // Manage token interactively + sm.manage(timeout, status, callback); + } + Err(RecvTimeoutError::Disconnected) => { break; } @@ -131,26 +140,13 @@ impl AuthenticatorTransport for Manager { fn register( &mut self, timeout: u64, - args: RegisterArgs, + register_args: RegisterArgs, status: Sender, callback: StateCallback>, ) -> Result<(), AuthenticatorError> { - let make_credentials = MakeCredentials::new( - ClientDataHash(args.client_data_hash), - RelyingPartyWrapper::Data(args.relying_party), - Some(args.user), - args.pub_cred_params, - args.exclude_list, - args.options, - args.extensions, - args.pin, - args.use_ctap1_fallback, - // pin_auth will be filled in Statemachine, once we have a device - )?; - let action = QueueAction::Register { timeout, - make_credentials, + register_args, status, callback, }; @@ -160,28 +156,13 @@ impl AuthenticatorTransport for Manager { fn sign( &mut self, timeout: u64, - args: SignArgs, + sign_args: SignArgs, status: Sender, callback: StateCallback>, ) -> crate::Result<()> { - let get_assertion = GetAssertion::new( - ClientDataHash(args.client_data_hash), - RelyingPartyWrapper::Data(RelyingParty { - id: args.relying_party_id, - name: None, - icon: None, - }), - args.allow_list, - args.options, - args.extensions, - args.pin, - args.alternate_rp_id, - args.use_ctap1_fallback, - )?; - let action = QueueAction::Sign { timeout, - get_assertion, + sign_args, status, callback, }; @@ -221,4 +202,17 @@ impl AuthenticatorTransport for Manager { callback, })?) } + + fn manage( + &mut self, + timeout: u64, + status: Sender, + callback: StateCallback>, + ) -> Result<(), AuthenticatorError> { + Ok(self.tx.send(QueueAction::InteractiveManagement { + timeout, + status, + callback, + })?) + } } diff --git a/third_party/rust/authenticator/src/statemachine.rs b/third_party/rust/authenticator/src/statemachine.rs index 75438339dd3a..fc568924b27f 100644 --- a/third_party/rust/authenticator/src/statemachine.rs +++ b/third_party/rust/authenticator/src/statemachine.rs @@ -2,19 +2,26 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +use crate::authenticatorservice::{RegisterArgs, SignArgs}; use crate::consts::PARAMETER_SIZE; +use crate::crypto::COSEAlgorithm; use crate::ctap2::client_data::ClientDataHash; use crate::ctap2::commands::client_pin::{ ChangeExistingPin, Pin, PinError, PinUvAuthTokenPermission, SetNewPin, }; -use crate::ctap2::commands::get_assertion::{GetAssertion, GetAssertionResult}; -use crate::ctap2::commands::make_credentials::{MakeCredentials, MakeCredentialsResult}; +use crate::ctap2::commands::get_assertion::{ + GetAssertion, GetAssertionOptions, GetAssertionResult, +}; +use crate::ctap2::commands::make_credentials::{ + MakeCredentials, MakeCredentialsOptions, MakeCredentialsResult, +}; use crate::ctap2::commands::reset::Reset; use crate::ctap2::commands::{ repackage_pin_errors, CommandError, PinUvAuthCommand, PinUvAuthResult, Request, StatusCode, }; use crate::ctap2::server::{ - PublicKeyCredentialDescriptor, RelyingParty, RelyingPartyWrapper, RpIdHash, + PublicKeyCredentialDescriptor, RelyingParty, RelyingPartyWrapper, ResidentKeyRequirement, + RpIdHash, UserVerificationRequirement, }; use crate::errors::{self, AuthenticatorError, UnsupportedOption}; use crate::statecallback::StateCallback; @@ -26,10 +33,10 @@ use crate::transport::{errors::HIDError, hid::HIDDevice, FidoDevice, Nonce}; use crate::u2fprotocol::{u2f_init_device, u2f_is_keyhandle_valid, u2f_register, u2f_sign}; use crate::u2ftypes::U2FDevice; use crate::{ - send_status, AuthenticatorTransports, KeyHandle, RegisterFlags, RegisterResult, SignFlags, - SignResult, StatusPinUv, StatusUpdate, + send_status, AuthenticatorTransports, InteractiveRequest, KeyHandle, RegisterFlags, + RegisterResult, SignFlags, SignResult, StatusPinUv, StatusUpdate, }; -use std::sync::mpsc::{channel, Sender}; +use std::sync::mpsc::{channel, RecvError, RecvTimeoutError, Sender}; use std::thread; use std::time::Duration; @@ -75,6 +82,7 @@ impl StateMachine { fn init_and_select( info: DeviceBuildParameters, selector: &Sender, + status: &Sender, ctap2_only: bool, keep_alive: &dyn Fn() -> bool, ) -> Option { @@ -101,22 +109,16 @@ impl StateMachine { return None; } - let write_only_clone = match dev.clone_device_as_write_only() { - Ok(x) => x, - Err(_) => { - // There is probably something seriously wrong here, if this happens. - // So `NotAToken()` is probably too weak a response here. - warn!("error while cloning device: {:?}", dev.id()); - selector - .send(DeviceSelectorEvent::NotAToken(dev.id())) - .ok()?; - return None; - } - }; let (tx, rx) = channel(); selector - .send(DeviceSelectorEvent::ImAToken((write_only_clone, tx))) + .send(DeviceSelectorEvent::ImAToken((dev.id(), tx))) .ok()?; + send_status( + status, + crate::StatusUpdate::DeviceAvailable { + dev_info: dev.get_device_info(), + }, + ); // We can be cancelled from the user (through keep_alive()) or from the device selector // (through a DeviceCommand::Cancel on rx). We'll combine those signals into a single @@ -125,29 +127,50 @@ impl StateMachine { // Blocking recv. DeviceSelector will tell us what to do match rx.recv() { - Ok(DeviceCommand::Blink) => match dev.block_and_blink(&keep_blinking) { - BlinkResult::DeviceSelected => { - // User selected us. Let DeviceSelector know, so it can cancel all other - // outstanding open blink-requests. - selector - .send(DeviceSelectorEvent::SelectedToken(dev.id())) - .ok()?; + Ok(DeviceCommand::Blink) => { + // Inform the user that there are multiple devices available. + // NOTE: We'll send this once per device, so the recipient should be prepared + // to receive this message multiple times. + send_status(status, crate::StatusUpdate::SelectDeviceNotice); + match dev.block_and_blink(&keep_blinking) { + BlinkResult::DeviceSelected => { + // User selected us. Let DeviceSelector know, so it can cancel all other + // outstanding open blink-requests. + selector + .send(DeviceSelectorEvent::SelectedToken(dev.id())) + .ok()?; + + send_status( + status, + crate::StatusUpdate::DeviceSelected(dev.get_device_info()), + ); + } + BlinkResult::Cancelled => { + info!("Device {:?} was not selected", dev.id()); + return None; + } } - BlinkResult::Cancelled => { - info!("Device {:?} was not selected", dev.id()); - return None; - } - }, + } Ok(DeviceCommand::Cancel) => { info!("Device {:?} was not selected", dev.id()); return None; } Ok(DeviceCommand::Removed) => { info!("Device {:?} was removed", dev.id()); + send_status( + status, + crate::StatusUpdate::DeviceUnavailable { + dev_info: dev.get_device_info(), + }, + ); return None; } Ok(DeviceCommand::Continue) => { // Just continue + send_status( + status, + crate::StatusUpdate::DeviceSelected(dev.get_device_info()), + ); } Err(_) => { warn!("Error when trying to receive messages from DeviceSelector! Exiting."); @@ -178,9 +201,9 @@ impl StateMachine { } match rx.recv() { Ok(pin) => Ok(pin), - Err(_) => { + Err(RecvError) => { // recv() can only fail, if the other side is dropping the Sender. - error!("Callback dropped the channel. Aborting."); + info!("Callback dropped the channel. Aborting."); callback.call(Err(AuthenticatorError::CancelledByUser)); Err(()) } @@ -195,7 +218,23 @@ impl StateMachine { dev: &mut Device, permission: PinUvAuthTokenPermission, skip_uv: bool, + uv_req: UserVerificationRequirement, ) -> Result { + // CTAP 2.1 is very specific that the request should either include pinUvAuthParam + // OR uv=true, but not both at the same time. We now have to decide which (if either) + // to send. We may omit both values. Will never send an explicit uv=false, because + // a) this is the default, and + // b) some CTAP 2.0 authenticators return UnsupportedOption when uv=false. + + // We ensure both pinUvAuthParam and uv are not set to start. + cmd.set_pin_uv_auth_param(None)?; + cmd.set_uv_option(None); + + // CTAP1/U2F-only devices do not support user verification, so we skip it + if !dev.supports_ctap2() { + return Ok(PinUvAuthResult::DeviceIsCtap1); + } + let info = dev .get_authenticator_info() .ok_or(AuthenticatorError::HIDError(HIDError::DeviceNotInitialized))?; @@ -213,16 +252,30 @@ impl StateMachine { // Check if the combination of device-protection and request-options // are allowing for 'discouraged', meaning no auth required. - if cmd.can_skip_user_verification(info) { + if cmd.can_skip_user_verification(info, uv_req) { return Ok(PinUvAuthResult::NoAuthRequired); } - // Device does not support any auth-method - if !pin_configured && !supports_uv { - // We'll send it to the device anyways, and let it error out (or magically work) + // Device does not support any (remaining) auth-method + if (skip_uv || !supports_uv) && !supports_pin { + if supports_uv && uv_req == UserVerificationRequirement::Required { + // We should always set the uv option in the Required case, but the CTAP 2.1 spec + // says 'Platforms MUST NOT include the "uv" option key if the authenticator does + // not support built-in user verification.' This is to work around some CTAP 2.0 + // authenticators which incorrectly error out with CTAP2_ERR_UNSUPPORTED_OPTION + // when the "uv" option is set. The RP that requested UV will (hopefully) reject our + // response in the !supports_uv case. + cmd.set_uv_option(Some(true)); + } return Ok(PinUvAuthResult::NoAuthTypeSupported); } + // Device supports PINs, but a PIN is not configured. Signal that we + // can complete the operation if the user sets a PIN first. + if (skip_uv || !supports_uv) && !pin_configured { + return Err(AuthenticatorError::PinError(PinError::PinNotSet)); + } + let (res, pin_auth_token) = if info.options.pin_uv_auth_token == Some(true) { if !skip_uv && supports_uv { // CTAP 2.1 - UV @@ -232,8 +285,12 @@ impl StateMachine { PinUvAuthResult::SuccessGetPinUvAuthTokenUsingUvWithPermissions, pin_auth_token, ) - } else if supports_pin && pin_configured { + } else { // CTAP 2.1 - PIN + // We did not take the `!skip_uv && supports_uv` branch, so we have + // `(skip_uv || !supports_uv)`. Moreover we did not exit early in the + // `(skip_uv || !supports_uv) && !pin_configured` case. So we have + // `pin_configured`. let pin_auth_token = dev.get_pin_uv_auth_token_using_pin_with_permissions( cmd.pin(), permission, @@ -243,8 +300,6 @@ impl StateMachine { PinUvAuthResult::SuccessGetPinUvAuthTokenUsingPinWithPermissions, pin_auth_token, ) - } else { - return Ok(PinUvAuthResult::NoAuthTypeSupported); } } else { // CTAP 2.0 fallback @@ -257,6 +312,8 @@ impl StateMachine { if info.supports_hmac_secret() { let _shared_secret = dev.establish_shared_secret()?; } + // CTAP 2.1, Section 6.1.1, Step 1.1.2.1.2. + cmd.set_uv_option(Some(true)); return Ok(PinUvAuthResult::UsingInternalUv); } @@ -266,10 +323,6 @@ impl StateMachine { let pin_auth_token = pin_auth_token.map_err(|e| repackage_pin_errors(dev, e))?; cmd.set_pin_uv_auth_param(Some(pin_auth_token))?; - // CTAP 2.0 spec is a bit vague here, but CTAP 2.1 is very specific, that the request - // should either include pinAuth OR uv=true, but not both at the same time. - // Do not set user_verification, if pinAuth is provided - cmd.set_uv_option(None); Ok(res) } @@ -286,22 +339,15 @@ impl StateMachine { dev: &mut Device, mut skip_uv: bool, permission: PinUvAuthTokenPermission, + uv_req: UserVerificationRequirement, status: &Sender, callback: &StateCallback>, alive: &dyn Fn() -> bool, ) -> Result { - // Starting from a blank slate. - cmd.set_pin_uv_auth_param(None).map_err(|_| ())?; - - // CTAP1/U2F-only devices do not support PinUvAuth, so we skip it - if !dev.supports_ctap2() { - return Ok(PinUvAuthResult::DeviceIsCtap1); - } - while alive() { debug!("-----------------------------------------------------------------"); debug!("Getting pinUvAuthParam"); - match Self::get_pin_uv_auth_param(cmd, dev, permission, skip_uv) { + match Self::get_pin_uv_auth_param(cmd, dev, permission, skip_uv, uv_req) { Ok(r) => { return Ok(r); } @@ -378,21 +424,23 @@ impl StateMachine { pub fn register( &mut self, timeout: u64, - params: MakeCredentials, + args: RegisterArgs, status: Sender, callback: StateCallback>, ) { - if params.use_ctap1_fallback { + if args.use_ctap1_fallback { /* Firefox uses this when security.webauthn.ctap2 is false. */ let mut flags = RegisterFlags::empty(); - if params.options.resident_key == Some(true) { + if args.resident_key_req == ResidentKeyRequirement::Required { flags |= RegisterFlags::REQUIRE_RESIDENT_KEY; } - if params.options.user_verification == Some(true) { + if args.user_verification_req == UserVerificationRequirement::Required { flags |= RegisterFlags::REQUIRE_USER_VERIFICATION; } - let application = params.rp.hash().0.to_vec(); - let key_handles = params + + let rp = RelyingPartyWrapper::Data(args.relying_party); + let application = rp.hash().as_ref().to_vec(); + let key_handles = args .exclude_list .iter() .map(|cred_desc| KeyHandle { @@ -400,7 +448,7 @@ impl StateMachine { transports: AuthenticatorTransports::empty(), }) .collect(); - let challenge = params.client_data_hash; + let challenge = ClientDataHash(args.client_data_hash); self.legacy_register( flags, @@ -422,7 +470,7 @@ impl StateMachine { cbc.clone(), status, move |info, selector, status, alive| { - let mut dev = match Self::init_and_select(info, &selector, false, alive) { + let mut dev = match Self::init_and_select(info, &selector, &status, false, alive) { None => { return; } @@ -430,33 +478,72 @@ impl StateMachine { }; info!("Device {:?} continues with the register process", dev.id()); - // TODO(baloo): not sure about this, have to ask - // We currently support none of the authenticator selection - // criteria because we can't ask tokens whether they do support - // those features. If flags are set, ignore all tokens for now. - // - // Technically, this is a ConstraintError because we shouldn't talk - // to this authenticator in the first place. But the result is the - // same anyway. - //if !flags.is_empty() { - // return; - //} - // TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me - // to modify "params" directly. - let mut makecred = params.clone(); - // First check if extensions have been requested that are not supported by the device - if let Some(true) = params.extensions.hmac_secret { - if let Some(auth) = dev.get_authenticator_info() { - if !auth.supports_hmac_secret() { + // We need a copy of the arguments for this device + let args = args.clone(); + + let mut options = MakeCredentialsOptions::default(); + + if let Some(info) = dev.get_authenticator_info() { + // Check if extensions have been requested that are not supported by the device + if let Some(true) = args.extensions.hmac_secret { + if !info.supports_hmac_secret() { callback.call(Err(AuthenticatorError::UnsupportedOption( UnsupportedOption::HmacSecret, ))); return; } } + + // Set options based on the arguments and the device info. + // The user verification option will be set in `determine_puap_if_needed`. + options.resident_key = match args.resident_key_req { + ResidentKeyRequirement::Required => Some(true), + ResidentKeyRequirement::Preferred => { + // Use a resident key if the authenticator supports it + Some(info.options.resident_key) + } + ResidentKeyRequirement::Discouraged => Some(false), + } + } else { + // Check that the request can be processed by a CTAP1 device. + // See CTAP 2.1 Section 10.2. Some additional checks are performed in + // MakeCredentials::RequestCtap1 + if args.resident_key_req == ResidentKeyRequirement::Required { + callback.call(Err(AuthenticatorError::UnsupportedOption( + UnsupportedOption::ResidentKey, + ))); + return; + } + if args.user_verification_req == UserVerificationRequirement::Required { + callback.call(Err(AuthenticatorError::UnsupportedOption( + UnsupportedOption::UserVerification, + ))); + return; + } + if !args + .pub_cred_params + .iter() + .any(|x| x.alg == COSEAlgorithm::ES256) + { + callback.call(Err(AuthenticatorError::UnsupportedOption( + UnsupportedOption::PubCredParams, + ))); + return; + } } + let mut makecred = MakeCredentials::new( + ClientDataHash(args.client_data_hash), + RelyingPartyWrapper::Data(args.relying_party), + Some(args.user), + args.pub_cred_params, + args.exclude_list, + options, + args.extensions, + args.pin, + ); + let mut skip_uv = false; while alive() { let pin_uv_auth_result = match Self::determine_puap_if_needed( @@ -464,6 +551,7 @@ impl StateMachine { &mut dev, skip_uv, PinUvAuthTokenPermission::MakeCredential, + args.user_verification_req, &status, &callback, alive, @@ -552,29 +640,30 @@ impl StateMachine { pub fn sign( &mut self, timeout: u64, - params: GetAssertion, + args: SignArgs, status: Sender, callback: StateCallback>, ) { - if params.use_ctap1_fallback { + if args.use_ctap1_fallback { /* Firefox uses this when security.webauthn.ctap2 is false. */ - let flags = match params.options.user_verification { - Some(true) => SignFlags::REQUIRE_USER_VERIFICATION, - _ => SignFlags::empty(), - }; - let mut app_ids = vec![params.rp.hash().0.to_vec()]; - if let Some(app_id) = params.alternate_rp_id { - app_ids.push( - RelyingPartyWrapper::Data(RelyingParty { - id: app_id, - ..Default::default() - }) - .hash() - .0 - .to_vec(), - ); + let mut flags = SignFlags::empty(); + if args.user_verification_req == UserVerificationRequirement::Required { + flags |= SignFlags::REQUIRE_USER_VERIFICATION; } - let key_handles = params + let mut app_ids = vec![]; + let rp_id = RelyingPartyWrapper::Data(RelyingParty { + id: args.relying_party_id, + ..Default::default() + }); + app_ids.push(rp_id.hash().as_ref().to_vec()); + if let Some(app_id) = args.alternate_rp_id { + let app_id = RelyingPartyWrapper::Data(RelyingParty { + id: app_id, + ..Default::default() + }); + app_ids.push(app_id.hash().as_ref().to_vec()); + } + let key_handles = args .allow_list .iter() .map(|cred_desc| KeyHandle { @@ -582,7 +671,7 @@ impl StateMachine { transports: AuthenticatorTransports::empty(), }) .collect(); - let challenge = params.client_data_hash; + let challenge = ClientDataHash(args.client_data_hash); self.legacy_sign( flags, @@ -605,7 +694,7 @@ impl StateMachine { callback.clone(), status, move |info, selector, status, alive| { - let mut dev = match Self::init_and_select(info, &selector, false, alive) { + let mut dev = match Self::init_and_select(info, &selector, &status, false, alive) { None => { return; } @@ -613,27 +702,61 @@ impl StateMachine { }; info!("Device {:?} continues with the signing process", dev.id()); - // TODO(MS): This is wasteful, but the current setup with read only-functions doesn't allow me - // to modify "params" directly. - let mut getassertion = params.clone(); - // First check if extensions have been requested that are not supported by the device - if params.extensions.hmac_secret.is_some() { - if let Some(auth) = dev.get_authenticator_info() { - if !auth.supports_hmac_secret() { - callback.call(Err(AuthenticatorError::UnsupportedOption( - UnsupportedOption::HmacSecret, - ))); - return; - } + + // We need a copy of the arguments for this device + let args = args.clone(); + + if let Some(info) = dev.get_authenticator_info() { + // Check if extensions have been requested that are not supported by the device + if args.extensions.hmac_secret.is_some() && !info.supports_hmac_secret() { + callback.call(Err(AuthenticatorError::UnsupportedOption( + UnsupportedOption::HmacSecret, + ))); + return; + } + } else { + // Check that the request can be processed by a CTAP1 device. + // See CTAP 2.1 Section 10.3. Some additional checks are performed in + // GetAssertion::RequestCtap1 + if args.user_verification_req == UserVerificationRequirement::Required { + callback.call(Err(AuthenticatorError::UnsupportedOption( + UnsupportedOption::UserVerification, + ))); + return; + } + if args.allow_list.is_empty() { + callback.call(Err(AuthenticatorError::UnsupportedOption( + UnsupportedOption::EmptyAllowList, + ))); + return; } } + + let mut get_assertion = GetAssertion::new( + ClientDataHash(args.client_data_hash), + RelyingPartyWrapper::Data(RelyingParty { + id: args.relying_party_id, + name: None, + icon: None, + }), + args.allow_list, + GetAssertionOptions { + user_presence: Some(args.user_presence_req), + user_verification: None, + }, + args.extensions, + args.pin, + args.alternate_rp_id, + ); + let mut skip_uv = false; while alive() { let pin_uv_auth_result = match Self::determine_puap_if_needed( - &mut getassertion, + &mut get_assertion, &mut dev, skip_uv, PinUvAuthTokenPermission::GetAssertion, + args.user_verification_req, &status, &callback, alive, @@ -645,7 +768,7 @@ impl StateMachine { }; // Third, use the shared secret in the extensions, if requested - if let Some(extension) = getassertion.extensions.hmac_secret.as_mut() { + if let Some(extension) = get_assertion.extensions.hmac_secret.as_mut() { if let Some(secret) = dev.get_shared_secret() { match extension.calculate(secret) { Ok(x) => x, @@ -658,20 +781,20 @@ impl StateMachine { } debug!("------------------------------------------------------------------"); - debug!("{getassertion:?} using {pin_uv_auth_result:?}"); + debug!("{get_assertion:?} using {pin_uv_auth_result:?}"); debug!("------------------------------------------------------------------"); - let mut resp = dev.send_msg_cancellable(&getassertion, alive); + let mut resp = dev.send_msg_cancellable(&get_assertion, alive); if resp.is_err() { // Retry with a different RP ID if one was supplied. This is intended to be // used with the AppID provided in the WebAuthn FIDO AppID extension. - if let Some(alternate_rp_id) = getassertion.alternate_rp_id { - getassertion.rp = RelyingPartyWrapper::Data(RelyingParty { + if let Some(alternate_rp_id) = get_assertion.alternate_rp_id { + get_assertion.rp = RelyingPartyWrapper::Data(RelyingParty { id: alternate_rp_id, ..Default::default() }); - getassertion.alternate_rp_id = None; - resp = dev.send_msg_cancellable(&getassertion, alive); + get_assertion.alternate_rp_id = None; + resp = dev.send_msg_cancellable(&get_assertion, alive); } } if resp.is_ok() { @@ -754,6 +877,45 @@ impl StateMachine { } } + pub fn reset_helper( + dev: &mut Device, + selector: Sender, + status: Sender, + callback: StateCallback>, + keep_alive: &dyn Fn() -> bool, + ) { + let reset = Reset {}; + info!("Device {:?} continues with the reset process", dev.id()); + debug!("------------------------------------------------------------------"); + debug!("{:?}", reset); + debug!("------------------------------------------------------------------"); + + let resp = dev.send_cbor_cancellable(&reset, keep_alive); + if resp.is_ok() { + send_status( + &status, + crate::StatusUpdate::Success { + dev_info: dev.get_device_info(), + }, + ); + // The DeviceSelector could already be dead, but it might also wait + // for us to respond, in order to cancel all other tokens in case + // we skipped the "blinking"-action and went straight for the actual + // request. + let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id())); + } + + match resp { + Ok(()) => callback.call(Ok(())), + Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {} + Err(HIDError::Command(CommandError::StatusCode(StatusCode::ChannelBusy, _))) => {} + Err(e) => { + warn!("error happened: {}", e); + callback.call(Err(AuthenticatorError::HIDError(e))); + } + } + } + pub fn reset( &mut self, timeout: u64, @@ -769,52 +931,112 @@ impl StateMachine { callback.clone(), status, move |info, selector, status, alive| { - let reset = Reset {}; - let mut dev = match Self::init_and_select(info, &selector, true, alive) { + let mut dev = match Self::init_and_select(info, &selector, &status, true, alive) { None => { return; } Some(dev) => dev, }; - - info!("Device {:?} continues with the reset process", dev.id()); - debug!("------------------------------------------------------------------"); - debug!("{:?}", reset); - debug!("------------------------------------------------------------------"); - - let resp = dev.send_cbor_cancellable(&reset, alive); - if resp.is_ok() { - send_status( - &status, - crate::StatusUpdate::Success { - dev_info: dev.get_device_info(), - }, - ); - // The DeviceSelector could already be dead, but it might also wait - // for us to respond, in order to cancel all other tokens in case - // we skipped the "blinking"-action and went straight for the actual - // request. - let _ = selector.send(DeviceSelectorEvent::SelectedToken(dev.id())); - } - - match resp { - Ok(()) => callback.call(Ok(())), - Err(HIDError::DeviceNotSupported) | Err(HIDError::UnsupportedCommand) => {} - Err(HIDError::Command(CommandError::StatusCode( - StatusCode::ChannelBusy, - _, - ))) => {} - Err(e) => { - warn!("error happened: {}", e); - callback.call(Err(AuthenticatorError::HIDError(e))); - } - } + Self::reset_helper(&mut dev, selector, status, callback.clone(), alive); }, ); self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e)))); } + pub fn set_or_change_pin_helper( + dev: &mut Device, + mut current_pin: Option, + new_pin: Pin, + status: Sender, + callback: StateCallback>, + alive: &dyn Fn() -> bool, + ) { + let mut shared_secret = match dev.establish_shared_secret() { + Ok(s) => s, + Err(e) => { + callback.call(Err(AuthenticatorError::HIDError(e))); + return; + } + }; + + let authinfo = match dev.get_authenticator_info() { + Some(i) => i.clone(), + None => { + callback.call(Err(HIDError::DeviceNotInitialized.into())); + return; + } + }; + + // If the device has a min PIN use that, otherwise default to 4 according to Spec + if new_pin.as_bytes().len() < authinfo.min_pin_length.unwrap_or(4) as usize { + callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort))); + return; + } + + // As per Spec: "Maximum PIN Length: UTF-8 representation MUST NOT exceed 63 bytes" + if new_pin.as_bytes().len() >= 64 { + callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooLong( + new_pin.as_bytes().len(), + )))); + return; + } + + // Check if a client-pin is already set, or if a new one should be created + let res = if Some(true) == authinfo.options.client_pin { + let mut res; + let mut was_invalid = false; + let mut retries = None; + loop { + // current_pin will only be Some() in the interactive mode (running `manage()`) + // In case that PIN is wrong, we want to avoid an endless-loop here with re-trying + // that wrong PIN all the time. So we `take()` it, and only test it once. + // If that PIN is wrong, we fall back to the "ask_user_for_pin"-method. + let curr_pin = match current_pin.take() { + None => { + match Self::ask_user_for_pin(was_invalid, retries, &status, &callback) { + Ok(pin) => pin, + _ => { + return; + } + } + } + Some(pin) => pin, + }; + + res = ChangeExistingPin::new(&authinfo, &shared_secret, &curr_pin, &new_pin) + .map_err(HIDError::Command) + .and_then(|msg| dev.send_cbor_cancellable(&msg, alive)) + .map_err(|e| repackage_pin_errors(dev, e)); + + if let Err(AuthenticatorError::PinError(PinError::InvalidPin(r))) = res { + was_invalid = true; + retries = r; + // We need to re-establish the shared secret for the next round. + match dev.establish_shared_secret() { + Ok(s) => { + shared_secret = s; + } + Err(e) => { + callback.call(Err(AuthenticatorError::HIDError(e))); + return; + } + }; + + continue; + } else { + break; + } + } + res + } else { + dev.send_cbor_cancellable(&SetNewPin::new(&shared_secret, &new_pin), alive) + .map_err(AuthenticatorError::HIDError) + }; + + callback.call(res); + } + pub fn set_pin( &mut self, timeout: u64, @@ -832,95 +1054,21 @@ impl StateMachine { callback.clone(), status, move |info, selector, status, alive| { - let mut dev = match Self::init_and_select(info, &selector, true, alive) { + let mut dev = match Self::init_and_select(info, &selector, &status, true, alive) { None => { return; } Some(dev) => dev, }; - let mut shared_secret = match dev.establish_shared_secret() { - Ok(s) => s, - Err(e) => { - callback.call(Err(AuthenticatorError::HIDError(e))); - return; - } - }; - - let authinfo = match dev.get_authenticator_info() { - Some(i) => i.clone(), - None => { - callback.call(Err(HIDError::DeviceNotInitialized.into())); - return; - } - }; - - // With CTAP2.1 we will have an adjustable required length for PINs - if new_pin.as_bytes().len() < 4 { - callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooShort))); - return; - } - - if new_pin.as_bytes().len() > 64 { - callback.call(Err(AuthenticatorError::PinError(PinError::PinIsTooLong( - new_pin.as_bytes().len(), - )))); - return; - } - - // Check if a client-pin is already set, or if a new one should be created - let res = if authinfo.options.client_pin.unwrap_or_default() { - let mut res; - let mut was_invalid = false; - let mut retries = None; - loop { - let current_pin = match Self::ask_user_for_pin( - was_invalid, - retries, - &status, - &callback, - ) { - Ok(pin) => pin, - _ => { - return; - } - }; - - res = ChangeExistingPin::new( - &authinfo, - &shared_secret, - ¤t_pin, - &new_pin, - ) - .map_err(HIDError::Command) - .and_then(|msg| dev.send_cbor_cancellable(&msg, alive)) - .map_err(|e| repackage_pin_errors(&mut dev, e)); - - if let Err(AuthenticatorError::PinError(PinError::InvalidPin(r))) = res { - was_invalid = true; - retries = r; - // We need to re-establish the shared secret for the next round. - match dev.establish_shared_secret() { - Ok(s) => { - shared_secret = s; - } - Err(e) => { - callback.call(Err(AuthenticatorError::HIDError(e))); - return; - } - }; - - continue; - } else { - break; - } - } - res - } else { - dev.send_cbor_cancellable(&SetNewPin::new(&shared_secret, &new_pin), alive) - .map_err(AuthenticatorError::HIDError) - }; - callback.call(res); + Self::set_or_change_pin_helper( + &mut dev, + None, + new_pin.clone(), + status, + callback.clone(), + alive, + ); }, ); self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e)))); @@ -938,7 +1086,6 @@ impl StateMachine { ) { // Abort any prior register/sign calls. self.cancel(); - let cbc = callback.clone(); let transaction = Transaction::new( @@ -1166,4 +1313,95 @@ impl StateMachine { self.transaction = Some(try_or!(transaction, |e| cbc.call(Err(e)))); } + + // Function to interactively manage a specific token. + // Difference to register/sign: These want to do something and don't care + // with which token they do it. + // This function wants to manipulate a specific token. For this, we first + // have to select one and then do something with it, based on what it + // supports (Set PIN, Change PIN, Reset, etc.). + // Hence, we first go through the discovery-phase, then provide the user + // with the AuthenticatorInfo and then let them interactively decide what to do + pub fn manage( + &mut self, + timeout: u64, + status: Sender, + callback: StateCallback>, + ) { + // Abort any prior register/sign calls. + self.cancel(); + let cbc = callback.clone(); + + let transaction = Transaction::new( + timeout, + callback.clone(), + status, + move |info, selector, status, alive| { + let mut dev = match Self::init_and_select(info, &selector, &status, true, alive) { + None => { + return; + } + Some(dev) => dev, + }; + + info!("Device {:?} selected for interactive management.", dev.id()); + + // Sending the user the info about the token + let (tx, rx) = channel(); + send_status( + &status, + crate::StatusUpdate::InteractiveManagement(( + tx, + dev.get_device_info(), + dev.get_authenticator_info().cloned(), + )), + ); + while alive() { + match rx.recv_timeout(Duration::from_millis(400)) { + Ok(InteractiveRequest::Reset) => { + Self::reset_helper(&mut dev, selector, status, callback.clone(), alive); + } + Ok(InteractiveRequest::ChangePIN(curr_pin, new_pin)) => { + Self::set_or_change_pin_helper( + &mut dev, + Some(curr_pin), + new_pin, + status, + callback.clone(), + alive, + ); + } + Ok(InteractiveRequest::SetPIN(pin)) => { + Self::set_or_change_pin_helper( + &mut dev, + None, + pin, + status, + callback.clone(), + alive, + ); + } + Err(RecvTimeoutError::Timeout) => { + if !alive() { + // We got stopped at some point + callback.call(Err(AuthenticatorError::CancelledByUser)); + break; + } + continue; + } + Err(RecvTimeoutError::Disconnected) => { + // recv() failed, because the other side is dropping the Sender. + info!( + "Callback dropped the channel, so we abort the interactive session" + ); + callback.call(Err(AuthenticatorError::CancelledByUser)); + } + } + break; + } + }, + ); + + self.transaction = Some(try_or!(transaction, move |e| cbc.call(Err(e)))); + } } diff --git a/third_party/rust/authenticator/src/status_update.rs b/third_party/rust/authenticator/src/status_update.rs index 8985cda9a03e..a7de5796dbb2 100644 --- a/third_party/rust/authenticator/src/status_update.rs +++ b/third_party/rust/authenticator/src/status_update.rs @@ -1,10 +1,15 @@ use super::{u2ftypes, Pin}; -use serde::{ - ser::{Serialize, SerializeStruct}, - Serialize as DeriveSer, Serializer, -}; +use crate::ctap2::commands::get_info::AuthenticatorInfo; +use serde::{Deserialize, Serialize as DeriveSer, Serializer}; use std::sync::mpsc::Sender; +#[derive(Debug, Deserialize, DeriveSer)] +pub enum InteractiveRequest { + Reset, + ChangePIN(Pin, Pin), + SetPIN(Pin), +} + // Simply ignoring the Sender when serializing pub(crate) fn serialize_pin_required(_: &Sender, s: S) -> Result where @@ -64,30 +69,14 @@ pub enum StatusUpdate { /// Sent, once a device was selected (either automatically or by user-interaction) /// and the register or signing process continues with this device DeviceSelected(u2ftypes::U2FDeviceInfo), -} - -impl Serialize for StatusUpdate { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: serde::Serializer, - { - let mut map = serializer.serialize_struct("StatusUpdate", 1)?; - match self { - StatusUpdate::DeviceAvailable { dev_info } => { - map.serialize_field("DeviceAvailable", &dev_info)? - } - StatusUpdate::DeviceUnavailable { dev_info } => { - map.serialize_field("DeviceUnavailable", &dev_info)? - } - StatusUpdate::Success { dev_info } => map.serialize_field("Success", &dev_info)?, - StatusUpdate::PinUvError(e) => map.serialize_field("PinUvError", &e)?, - StatusUpdate::SelectDeviceNotice => map.serialize_field("SelectDeviceNotice", &())?, - StatusUpdate::DeviceSelected(dev_info) => { - map.serialize_field("DeviceSelected", &dev_info)? - } - } - map.end() - } + /// Sent when a token was selected for interactive management + InteractiveManagement( + ( + Sender, + u2ftypes::U2FDeviceInfo, + Option, + ), + ), } pub(crate) fn send_status(status: &Sender, msg: StatusUpdate) { @@ -96,54 +85,3 @@ pub(crate) fn send_status(status: &Sender, msg: StatusUpdate) { Err(e) => error!("Couldn't send status: {:?}", e), }; } - -#[cfg(test)] -pub mod tests { - use super::*; - use crate::consts::Capability; - use serde_json::to_string; - use std::sync::mpsc::channel; - - #[test] - fn serialize_select() { - let st = StatusUpdate::SelectDeviceNotice; - let json = to_string(&st).expect("Failed to serialize"); - assert_eq!(&json, r#"{"SelectDeviceNotice":null}"#); - } - - #[test] - fn serialize_status_pin_uv() { - let (tx, _rx) = channel(); - let st = StatusUpdate::PinUvError(StatusPinUv::InvalidPin(tx.clone(), Some(3))); - let json = to_string(&st).expect("Failed to serialize"); - assert_eq!(&json, r#"{"PinUvError":{"InvalidPin":3}}"#); - - let st = StatusUpdate::PinUvError(StatusPinUv::InvalidPin(tx, None)); - let json = to_string(&st).expect("Failed to serialize"); - assert_eq!(&json, r#"{"PinUvError":{"InvalidPin":null}}"#); - - let st = StatusUpdate::PinUvError(StatusPinUv::PinBlocked); - let json = to_string(&st).expect("Failed to serialize"); - assert_eq!(&json, r#"{"PinUvError":"PinBlocked"}"#); - } - - #[test] - fn serialize_success() { - let cap = Capability::WINK | Capability::CBOR; - let dev = u2ftypes::U2FDeviceInfo { - vendor_name: String::from("ABC").into_bytes(), - device_name: String::from("DEF").into_bytes(), - version_interface: 2, - version_major: 5, - version_minor: 4, - version_build: 3, - cap_flags: cap, - }; - let st = StatusUpdate::Success { dev_info: dev }; - let json = to_string(&st).expect("Failed to serialize"); - assert_eq!( - &json, - r#"{"Success":{"vendor_name":[65,66,67],"device_name":[68,69,70],"version_interface":2,"version_major":5,"version_minor":4,"version_build":3,"cap_flags":{"bits":5}}}"# - ); - } -} diff --git a/third_party/rust/authenticator/src/transport/device_selector.rs b/third_party/rust/authenticator/src/transport/device_selector.rs index c754b5446176..a0ce4ccb57c2 100644 --- a/third_party/rust/authenticator/src/transport/device_selector.rs +++ b/third_party/rust/authenticator/src/transport/device_selector.rs @@ -1,12 +1,14 @@ -use crate::send_status; use crate::transport::hid::HIDDevice; pub use crate::transport::platform::device::Device; -use crate::u2ftypes::U2FDevice; use runloop::RunLoop; use std::collections::{HashMap, HashSet}; use std::sync::mpsc::{channel, RecvTimeoutError, Sender}; use std::time::Duration; +// This import is used, but Rust 1.68 gives a warning +#[allow(unused_imports)] +use crate::u2ftypes::U2FDevice; + pub type DeviceID = ::Id; pub type DeviceBuildParameters = ::BuildParameters; @@ -33,7 +35,7 @@ pub enum DeviceSelectorEvent { DevicesAdded(Vec), DeviceRemoved(DeviceID), NotAToken(DeviceID), - ImAToken((Device, Sender)), + ImAToken((DeviceID, Sender)), SelectedToken(DeviceID), } @@ -45,11 +47,7 @@ pub struct DeviceSelector { } impl DeviceSelector { - // Devices are hashed according to their DeviceID (usually a path), - // all other members (which can be mutable) are not used, but clippy - // can't check this. - #![allow(clippy::mutable_key_type)] - pub fn run(status: Sender) -> Self { + pub fn run() -> Self { let (selector_send, selector_rec) = channel(); // let new_device_callback = Arc::new(new_device_cb); let runloop = RunLoop::new(move |alive| { @@ -57,7 +55,8 @@ impl DeviceSelector { // Device was added, but we wait for its response, if it is a token or not // We save both a write-only copy of the device (for cancellation) and it's thread let mut waiting_for_response = HashSet::new(); - // All devices that responded with "ImAToken" + // Device IDs of devices that responded with "ImAToken" mapping to channels that are + // waiting to receive a DeviceCommand let mut tokens = HashMap::new(); while alive() { let d = Duration::from_secs(100); @@ -75,14 +74,8 @@ impl DeviceSelector { Self::cancel_all(tokens, None); break; } - DeviceSelectorEvent::SelectedToken(id) => { - if let Some(dev) = tokens.keys().find(|d| d.id() == id) { - send_status( - &status, - crate::StatusUpdate::DeviceSelected(dev.get_device_info()), - ); - } - Self::cancel_all(tokens, Some(&id)); + DeviceSelectorEvent::SelectedToken(ref id) => { + Self::cancel_all(tokens, Some(id)); break; // We are done here. The selected device continues without us. } DeviceSelectorEvent::DevicesAdded(ids) => { @@ -92,26 +85,20 @@ impl DeviceSelector { } continue; } - DeviceSelectorEvent::DeviceRemoved(id) => { + DeviceSelectorEvent::DeviceRemoved(ref id) => { debug!("Device removed event: {:?}", id); - if !waiting_for_response.remove(&id) { + if !waiting_for_response.remove(id) { // Note: We _could_ check here if we had multiple tokens and are already blinking // and the removal of this one leads to only one token left. So we could in theory // stop blinking and select it right away. At the moment, I think this is a // too surprising behavior and therefore, we let the remaining device keep on blinking // since the user could add yet another device, instead of using the remaining one. - tokens.iter().for_each(|(dev, send)| { - if dev.id() == id { - let _ = send.send(DeviceCommand::Removed); - send_status( - &status, - crate::StatusUpdate::DeviceUnavailable { - dev_info: dev.get_device_info(), - }, - ); + tokens.iter().for_each(|(dev_id, tx)| { + if dev_id == id { + let _ = tx.send(DeviceCommand::Removed); } }); - tokens.retain(|dev, _| dev.id() != id); + tokens.retain(|dev_id, _| dev_id != id); if tokens.is_empty() { blinking = false; continue; @@ -127,27 +114,20 @@ impl DeviceSelector { continue; } } - DeviceSelectorEvent::NotAToken(id) => { + DeviceSelectorEvent::NotAToken(ref id) => { debug!("Device not a token event: {:?}", id); - waiting_for_response.remove(&id); + waiting_for_response.remove(id); } - DeviceSelectorEvent::ImAToken((dev, tx)) => { - send_status( - &status, - crate::StatusUpdate::DeviceAvailable { - dev_info: dev.get_device_info(), - }, - ); - let id = dev.id(); + DeviceSelectorEvent::ImAToken((id, tx)) => { let _ = waiting_for_response.remove(&id); - tokens.insert(dev, tx.clone()); if blinking { // We are already blinking, so this new device should blink too. - if tx.send(DeviceCommand::Blink).is_err() { - // Device thread died in the meantime (which shouldn't happen) - tokens.retain(|dev, _| dev.id() != id); + if tx.send(DeviceCommand::Blink).is_ok() { + tokens.insert(id, tx.clone()); } continue; + } else { + tokens.insert(id, tx.clone()); } } } @@ -155,17 +135,13 @@ impl DeviceSelector { // All known devices told us, whether they are tokens or not and we have at least one token if waiting_for_response.is_empty() && !tokens.is_empty() { if tokens.len() == 1 { - let (dev, tx) = tokens.drain().next().unwrap(); // We just checked that it can't be empty + let (dev_id, tx) = tokens.drain().next().unwrap(); // We just checked that it can't be empty if tx.send(DeviceCommand::Continue).is_err() { // Device thread died in the meantime (which shouldn't happen). // Tokens is empty, so we just start over again continue; } - send_status( - &status, - crate::StatusUpdate::DeviceSelected(dev.get_device_info()), - ); - Self::cancel_all(tokens, Some(&dev.id())); + Self::cancel_all(tokens, Some(&dev_id)); break; // We are done here } else { blinking = true; @@ -175,7 +151,6 @@ impl DeviceSelector { // We ignore errors here for now, but should probably remove the device in such a case (even though it theoretically can't happen) let _ = tx.send(DeviceCommand::Blink); }); - send_status(&status, crate::StatusUpdate::SelectDeviceNotice); } } } @@ -190,9 +165,9 @@ impl DeviceSelector { self.sender.clone() } - fn cancel_all(tokens: HashMap>, exclude: Option<&DeviceID>) { - for (dev, tx) in tokens.iter() { - if Some(&dev.id()) != exclude { + fn cancel_all(tokens: HashMap>, exclude: Option<&DeviceID>) { + for (dev_id, tx) in tokens.iter() { + if Some(dev_id) != exclude { let _ = tx.send(DeviceCommand::Cancel); } } @@ -212,16 +187,7 @@ pub mod tests { consts::Capability, ctap2::commands::get_info::{AuthenticatorInfo, AuthenticatorOptions}, u2ftypes::U2FDeviceInfo, - StatusUpdate, }; - use std::sync::mpsc::Receiver; - - enum ExpectedUpdate { - DeviceAvailable, - DeviceUnavailable, - SelectDeviceNotice, - DeviceSelected, - } fn gen_info(id: String) -> U2FDeviceInfo { U2FDeviceInfo { @@ -257,7 +223,7 @@ pub mod tests { selector .sender .send(DeviceSelectorEvent::ImAToken(( - dev.clone_device_as_write_only().unwrap(), + dev.id(), dev.sender.clone().unwrap(), ))) .unwrap(); @@ -281,22 +247,6 @@ pub mod tests { ); } - fn recv_status(dev: &Device, status_rx: &Receiver, expected: ExpectedUpdate) { - let res = status_rx.recv().unwrap(); - // Marking it with _, as cargo warns about "unused exp", as it doesn't view the assert below as a usage - let _exp = match expected { - ExpectedUpdate::DeviceAvailable => StatusUpdate::DeviceUnavailable { - dev_info: gen_info(dev.id()), - }, - ExpectedUpdate::DeviceUnavailable => StatusUpdate::DeviceUnavailable { - dev_info: gen_info(dev.id()), - }, - ExpectedUpdate::DeviceSelected => StatusUpdate::DeviceSelected(gen_info(dev.id())), - ExpectedUpdate::SelectDeviceNotice => StatusUpdate::SelectDeviceNotice, - }; - assert!(matches!(res, _exp)); - } - fn add_devices<'a, T>(iter: T, selector: &DeviceSelector) where T: Iterator, @@ -320,8 +270,7 @@ pub mod tests { // Make those actual tokens. The rest is interpreted as non-u2f-devices make_device_with_pin(&mut devices[2]); - let (status_tx, status_rx) = channel(); - let selector = DeviceSelector::run(status_tx); + let selector = DeviceSelector::run(); // Adding all add_devices(devices.iter(), &selector); @@ -333,12 +282,10 @@ pub mod tests { send_i_am_token(&devices[2], &selector); - recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable); assert_eq!( devices[2].receiver.as_ref().unwrap().recv().unwrap(), DeviceCommand::Continue ); - recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceSelected); } // This test is mostly for testing stop() and clone_sender() @@ -346,8 +293,7 @@ pub mod tests { fn test_device_selector_stop() { let device = Device::new("device selector 1").unwrap(); - let (status_tx, _) = channel(); - let mut selector = DeviceSelector::run(status_tx); + let mut selector = DeviceSelector::run(); // Adding all selector @@ -378,15 +324,13 @@ pub mod tests { make_device_with_pin(&mut devices[4]); make_device_with_pin(&mut devices[5]); - let (status_tx, status_rx) = channel(); - let selector = DeviceSelector::run(status_tx); + let selector = DeviceSelector::run(); // Adding all, except the last one (we simulate that this one is not yet plugged in) add_devices(devices.iter().take(5), &selector); // Interleave tokens and non-tokens send_i_am_token(&devices[2], &selector); - recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable); devices.iter_mut().for_each(|d| { if !d.is_u2f() { @@ -395,7 +339,6 @@ pub mod tests { }); send_i_am_token(&devices[4], &selector); - recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable); // We added 2 devices that are tokens. They should get the blink-command now assert_eq!( @@ -406,11 +349,9 @@ pub mod tests { devices[4].receiver.as_ref().unwrap().recv().unwrap(), DeviceCommand::Blink ); - recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice); // Plug in late device send_i_am_token(&devices[5], &selector); - recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceAvailable); assert_eq!( devices[5].receiver.as_ref().unwrap().recv().unwrap(), DeviceCommand::Blink @@ -435,15 +376,13 @@ pub mod tests { make_device_simple_u2f(&mut devices[4]); make_device_simple_u2f(&mut devices[5]); - let (status_tx, status_rx) = channel(); - let selector = DeviceSelector::run(status_tx); + let selector = DeviceSelector::run(); // Adding all, except the last one (we simulate that this one is not yet plugged in) add_devices(devices.iter().take(5), &selector); // Interleave tokens and non-tokens send_i_am_token(&devices[2], &selector); - recv_status(&devices[2], &status_rx, ExpectedUpdate::DeviceAvailable); devices.iter_mut().for_each(|d| { if !d.is_u2f() { @@ -452,7 +391,6 @@ pub mod tests { }); send_i_am_token(&devices[4], &selector); - recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable); // We added 2 devices that are tokens. They should get the blink-command now assert_eq!( @@ -463,23 +401,19 @@ pub mod tests { devices[4].receiver.as_ref().unwrap().recv().unwrap(), DeviceCommand::Blink ); - recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice); // Plug in late device send_i_am_token(&devices[5], &selector); - recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceAvailable); assert_eq!( devices[5].receiver.as_ref().unwrap().recv().unwrap(), DeviceCommand::Blink ); // Remove device again remove_device(&devices[5], &selector); - recv_status(&devices[5], &status_rx, ExpectedUpdate::DeviceUnavailable); // Now we add a token that has a PIN, it should not get "Continue" but "Blink" make_device_with_pin(&mut devices[6]); send_i_am_token(&devices[6], &selector); - recv_status(&devices[6], &status_rx, ExpectedUpdate::DeviceAvailable); assert_eq!( devices[6].receiver.as_ref().unwrap().recv().unwrap(), DeviceCommand::Blink @@ -504,8 +438,7 @@ pub mod tests { make_device_with_pin(&mut devices[4]); make_device_with_pin(&mut devices[5]); - let (status_tx, status_rx) = channel(); - let selector = DeviceSelector::run(status_tx); + let selector = DeviceSelector::run(); // Adding all, except the last one (we simulate that this one is not yet plugged in) add_devices(devices.iter(), &selector); @@ -519,29 +452,24 @@ pub mod tests { }); for idx in [2, 4, 5] { - recv_status(&devices[idx], &status_rx, ExpectedUpdate::DeviceAvailable); assert_eq!( devices[idx].receiver.as_ref().unwrap().recv().unwrap(), DeviceCommand::Blink ); } - recv_status(&devices[2], &status_rx, ExpectedUpdate::SelectDeviceNotice); // Remove all tokens for idx in [2, 4, 5] { remove_device(&devices[idx], &selector); - recv_status(&devices[idx], &status_rx, ExpectedUpdate::DeviceUnavailable); } // Adding one again send_i_am_token(&devices[4], &selector); - recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceAvailable); // This should now get a "Continue" instead of "Blinking", because it's the only device assert_eq!( devices[4].receiver.as_ref().unwrap().recv().unwrap(), DeviceCommand::Continue ); - recv_status(&devices[4], &status_rx, ExpectedUpdate::DeviceSelected); } } diff --git a/third_party/rust/authenticator/src/transport/freebsd/device.rs b/third_party/rust/authenticator/src/transport/freebsd/device.rs index afe92c392a8b..7a350c067e0a 100644 --- a/third_party/rust/authenticator/src/transport/freebsd/device.rs +++ b/third_party/rust/authenticator/src/transport/freebsd/device.rs @@ -212,25 +212,6 @@ impl HIDDevice for Device { fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { self.authenticator_info = Some(authenticator_info); } - - /// This is used for cancellation of blocking read()-requests. - /// With this, we can clone the Device, pass it to another thread and call "cancel()" on that. - fn clone_device_as_write_only(&self) -> Result { - // Try to open the device. - // This can't really error out as we already did this conversion - let cstr = CString::new(self.path.as_bytes()).map_err(|_| (HIDError::DeviceError))?; - let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_WRONLY) }; - let fd = - from_unix_result(fd).map_err(|e| (HIDError::IO(Some(self.path.clone().into()), e)))?; - Ok(Self { - path: self.path.clone(), - fd, - cid: self.cid, - dev_info: self.dev_info.clone(), - secret: self.secret.clone(), - authenticator_info: self.authenticator_info.clone(), - }) - } } impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/transport/freebsd/transaction.rs b/third_party/rust/authenticator/src/transport/freebsd/transaction.rs index 35baa8f3c438..6b15f6751a2d 100644 --- a/third_party/rust/authenticator/src/transport/freebsd/transaction.rs +++ b/third_party/rust/authenticator/src/transport/freebsd/transaction.rs @@ -35,13 +35,12 @@ impl Transaction { + 'static, T: 'static, { - let status_sender = status.clone(); - let device_selector = DeviceSelector::run(status); + let device_selector = DeviceSelector::run(); let selector_sender = device_selector.clone_sender(); let thread = RunLoop::new_with_timeout( move |alive| { // Create a new device monitor. - let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); + let mut monitor = Monitor::new(new_device_cb, selector_sender, status); // Start polling for new devices. try_or!(monitor.run(alive), |_| callback diff --git a/third_party/rust/authenticator/src/transport/hid.rs b/third_party/rust/authenticator/src/transport/hid.rs index 8a072f945670..ca56f562ce7b 100644 --- a/third_party/rust/authenticator/src/transport/hid.rs +++ b/third_party/rust/authenticator/src/transport/hid.rs @@ -30,7 +30,6 @@ where fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo); fn set_shared_secret(&mut self, secret: SharedSecret); fn get_shared_secret(&self) -> Option<&SharedSecret>; - fn clone_device_as_write_only(&self) -> Result; fn supports_ctap1(&self) -> bool { // CAPABILITY_NMSG: diff --git a/third_party/rust/authenticator/src/transport/linux/device.rs b/third_party/rust/authenticator/src/transport/linux/device.rs index 1e32f8682d44..bad487c53c83 100644 --- a/third_party/rust/authenticator/src/transport/linux/device.rs +++ b/third_party/rust/authenticator/src/transport/linux/device.rs @@ -158,26 +158,6 @@ impl HIDDevice for Device { fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { self.authenticator_info = Some(authenticator_info); } - - /// This is used for cancellation of blocking read()-requests. - /// With this, we can clone the Device, pass it to another thread and call "cancel()" on that. - fn clone_device_as_write_only(&self) -> Result { - let fd = OpenOptions::new() - .write(true) - .open(&self.path) - .map_err(|e| (HIDError::IO(Some(self.path.clone()), e)))?; - - Ok(Self { - path: self.path.clone(), - fd, - in_rpt_size: self.in_rpt_size, - out_rpt_size: self.out_rpt_size, - cid: self.cid, - dev_info: self.dev_info.clone(), - secret: self.secret.clone(), - authenticator_info: self.authenticator_info.clone(), - }) - } } impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/transport/linux/transaction.rs b/third_party/rust/authenticator/src/transport/linux/transaction.rs index 35baa8f3c438..6b15f6751a2d 100644 --- a/third_party/rust/authenticator/src/transport/linux/transaction.rs +++ b/third_party/rust/authenticator/src/transport/linux/transaction.rs @@ -35,13 +35,12 @@ impl Transaction { + 'static, T: 'static, { - let status_sender = status.clone(); - let device_selector = DeviceSelector::run(status); + let device_selector = DeviceSelector::run(); let selector_sender = device_selector.clone_sender(); let thread = RunLoop::new_with_timeout( move |alive| { // Create a new device monitor. - let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); + let mut monitor = Monitor::new(new_device_cb, selector_sender, status); // Start polling for new devices. try_or!(monitor.run(alive), |_| callback diff --git a/third_party/rust/authenticator/src/transport/macos/device.rs b/third_party/rust/authenticator/src/transport/macos/device.rs index d2ef10bb5527..0e55b92e9628 100644 --- a/third_party/rust/authenticator/src/transport/macos/device.rs +++ b/third_party/rust/authenticator/src/transport/macos/device.rs @@ -204,19 +204,6 @@ impl HIDDevice for Device { fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { self.authenticator_info = Some(authenticator_info); } - - /// This is used for cancellation of blocking read()-requests. - /// With this, we can clone the Device, pass it to another thread and call "cancel()" on that. - fn clone_device_as_write_only(&self) -> Result { - Ok(Self { - device_ref: self.device_ref, - cid: self.cid, - report_rx: None, - dev_info: self.dev_info.clone(), - secret: self.secret.clone(), - authenticator_info: self.authenticator_info.clone(), - }) - } } impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/transport/macos/transaction.rs b/third_party/rust/authenticator/src/transport/macos/transaction.rs index 7387a0ac8d9c..d9709e7364ef 100644 --- a/third_party/rust/authenticator/src/transport/macos/transaction.rs +++ b/third_party/rust/authenticator/src/transport/macos/transaction.rs @@ -46,8 +46,7 @@ impl Transaction { { let (tx, rx) = channel(); let timeout = (timeout as f64) / 1000.0; - let status_sender = status.clone(); - let device_selector = DeviceSelector::run(status); + let device_selector = DeviceSelector::run(); let selector_sender = device_selector.clone_sender(); let builder = thread::Builder::new(); let thread = builder @@ -61,7 +60,7 @@ impl Transaction { obs.add_to_current_runloop(); // Create a new HID device monitor and start polling. - let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); + let mut monitor = Monitor::new(new_device_cb, selector_sender, status); try_or!(monitor.start(), |_| callback .call(Err(errors::AuthenticatorError::Platform))); diff --git a/third_party/rust/authenticator/src/transport/mock/device.rs b/third_party/rust/authenticator/src/transport/mock/device.rs index 8dfbabdbd5fa..c22e53b3bd8a 100644 --- a/third_party/rust/authenticator/src/transport/mock/device.rs +++ b/third_party/rust/authenticator/src/transport/mock/device.rs @@ -173,19 +173,6 @@ impl HIDDevice for Device { self.id.clone() } - fn clone_device_as_write_only(&self) -> Result { - Ok(Device { - id: self.id.clone(), - cid: self.cid, - reads: self.reads.clone(), - writes: self.writes.clone(), - dev_info: self.dev_info.clone(), - authenticator_info: self.authenticator_info.clone(), - sender: self.sender.clone(), - receiver: None, - }) - } - fn is_u2f(&mut self) -> bool { self.sender.is_some() } diff --git a/third_party/rust/authenticator/src/transport/netbsd/device.rs b/third_party/rust/authenticator/src/transport/netbsd/device.rs index 27fea5a0b044..c93aee8d6ad6 100644 --- a/third_party/rust/authenticator/src/transport/netbsd/device.rs +++ b/third_party/rust/authenticator/src/transport/netbsd/device.rs @@ -225,21 +225,6 @@ impl HIDDevice for Device { fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { self.authenticator_info = Some(authenticator_info); } - - /// This is used for cancellation of blocking read()-requests. - /// With this, we can clone the Device, pass it to another thread and call "cancel()" on that. - fn clone_device_as_write_only(&self) -> Result { - // Try to open the device. - let fd = Fd::open(&self.path, libc::O_WRONLY)?; - Ok(Self { - path: self.path.clone(), - fd, - cid: self.cid, - dev_info: self.dev_info.clone(), - secret: self.secret.clone(), - authenticator_info: self.authenticator_info.clone(), - }) - } } impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/transport/netbsd/transaction.rs b/third_party/rust/authenticator/src/transport/netbsd/transaction.rs index 35baa8f3c438..6b15f6751a2d 100644 --- a/third_party/rust/authenticator/src/transport/netbsd/transaction.rs +++ b/third_party/rust/authenticator/src/transport/netbsd/transaction.rs @@ -35,13 +35,12 @@ impl Transaction { + 'static, T: 'static, { - let status_sender = status.clone(); - let device_selector = DeviceSelector::run(status); + let device_selector = DeviceSelector::run(); let selector_sender = device_selector.clone_sender(); let thread = RunLoop::new_with_timeout( move |alive| { // Create a new device monitor. - let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); + let mut monitor = Monitor::new(new_device_cb, selector_sender, status); // Start polling for new devices. try_or!(monitor.run(alive), |_| callback diff --git a/third_party/rust/authenticator/src/transport/openbsd/device.rs b/third_party/rust/authenticator/src/transport/openbsd/device.rs index cc7ef61cbb94..fe4d6a642eaa 100644 --- a/third_party/rust/authenticator/src/transport/openbsd/device.rs +++ b/third_party/rust/authenticator/src/transport/openbsd/device.rs @@ -201,27 +201,6 @@ impl HIDDevice for Device { fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { self.authenticator_info = Some(authenticator_info); } - - /// This is used for cancellation of blocking read()-requests. - /// With this, we can clone the Device, pass it to another thread and call "cancel()" on that. - fn clone_device_as_write_only(&self) -> Result { - // Try to open the device. - // This can't really error out as we already did this conversion - let cstr = CString::new(self.path.as_bytes()).map_err(|_| (HIDError::DeviceError))?; - let fd = unsafe { libc::open(cstr.as_ptr(), libc::O_WRONLY) }; - let fd = - from_unix_result(fd).map_err(|e| (HIDError::IO(Some(self.path.clone().into()), e)))?; - Ok(Self { - path: self.path.clone(), - fd, - in_rpt_size: self.in_rpt_size, - out_rpt_size: self.out_rpt_size, - cid: self.cid, - dev_info: self.dev_info.clone(), - secret: self.secret.clone(), - authenticator_info: self.authenticator_info.clone(), - }) - } } impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/transport/openbsd/transaction.rs b/third_party/rust/authenticator/src/transport/openbsd/transaction.rs index 35baa8f3c438..6b15f6751a2d 100644 --- a/third_party/rust/authenticator/src/transport/openbsd/transaction.rs +++ b/third_party/rust/authenticator/src/transport/openbsd/transaction.rs @@ -35,13 +35,12 @@ impl Transaction { + 'static, T: 'static, { - let status_sender = status.clone(); - let device_selector = DeviceSelector::run(status); + let device_selector = DeviceSelector::run(); let selector_sender = device_selector.clone_sender(); let thread = RunLoop::new_with_timeout( move |alive| { // Create a new device monitor. - let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); + let mut monitor = Monitor::new(new_device_cb, selector_sender, status); // Start polling for new devices. try_or!(monitor.run(alive), |_| callback diff --git a/third_party/rust/authenticator/src/transport/stub/device.rs b/third_party/rust/authenticator/src/transport/stub/device.rs index 6fcaf16243f2..9c5a412a9540 100644 --- a/third_party/rust/authenticator/src/transport/stub/device.rs +++ b/third_party/rust/authenticator/src/transport/stub/device.rs @@ -96,10 +96,6 @@ impl HIDDevice for Device { fn get_shared_secret(&self) -> Option<&SharedSecret> { unimplemented!() } - - fn clone_device_as_write_only(&self) -> Result { - unimplemented!() - } } impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/transport/stub/transaction.rs b/third_party/rust/authenticator/src/transport/stub/transaction.rs index 21a9687ecd03..d471c94da8bd 100644 --- a/third_party/rust/authenticator/src/transport/stub/transaction.rs +++ b/third_party/rust/authenticator/src/transport/stub/transaction.rs @@ -16,7 +16,7 @@ impl Transaction { pub fn new( timeout: u64, callback: StateCallback>, - status: Sender, + _status: Sender, new_device_cb: F, ) -> crate::Result where @@ -31,7 +31,7 @@ impl Transaction { T: 'static, { // Just to silence "unused"-warnings - let mut device_selector = DeviceSelector::run(status); + let mut device_selector = DeviceSelector::run(); let _ = DeviceSelectorEvent::DevicesAdded(vec![]); let _ = DeviceSelectorEvent::DeviceRemoved(PathBuf::new()); let _ = device_selector.clone_sender(); diff --git a/third_party/rust/authenticator/src/transport/windows/device.rs b/third_party/rust/authenticator/src/transport/windows/device.rs index 002c0d671831..1037c25a206a 100644 --- a/third_party/rust/authenticator/src/transport/windows/device.rs +++ b/third_party/rust/authenticator/src/transport/windows/device.rs @@ -149,24 +149,6 @@ impl HIDDevice for Device { fn set_authenticator_info(&mut self, authenticator_info: AuthenticatorInfo) { self.authenticator_info = Some(authenticator_info); } - - /// This is used for cancellation of blocking read()-requests. - /// With this, we can clone the Device, pass it to another thread and call "cancel()" on that. - fn clone_device_as_write_only(&self) -> Result { - let file = OpenOptions::new() - .write(true) - .open(&self.path) - .map_err(|e| (HIDError::IO(Some(self.path.clone().into()), e)))?; - - Ok(Self { - path: self.path.clone(), - file, - cid: self.cid, - dev_info: self.dev_info.clone(), - secret: self.secret.clone(), - authenticator_info: self.authenticator_info.clone(), - }) - } } impl FidoDevice for Device {} diff --git a/third_party/rust/authenticator/src/transport/windows/transaction.rs b/third_party/rust/authenticator/src/transport/windows/transaction.rs index 35baa8f3c438..6b15f6751a2d 100644 --- a/third_party/rust/authenticator/src/transport/windows/transaction.rs +++ b/third_party/rust/authenticator/src/transport/windows/transaction.rs @@ -35,13 +35,12 @@ impl Transaction { + 'static, T: 'static, { - let status_sender = status.clone(); - let device_selector = DeviceSelector::run(status); + let device_selector = DeviceSelector::run(); let selector_sender = device_selector.clone_sender(); let thread = RunLoop::new_with_timeout( move |alive| { // Create a new device monitor. - let mut monitor = Monitor::new(new_device_cb, selector_sender, status_sender); + let mut monitor = Monitor::new(new_device_cb, selector_sender, status); // Start polling for new devices. try_or!(monitor.run(alive), |_| callback