summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/examples
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /third_party/rust/authenticator/examples
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/authenticator/examples')
-rw-r--r--third_party/rust/authenticator/examples/ctap2.rs294
-rw-r--r--third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs366
-rw-r--r--third_party/rust/authenticator/examples/interactive_management.rs204
-rw-r--r--third_party/rust/authenticator/examples/reset.rs140
-rw-r--r--third_party/rust/authenticator/examples/set_pin.rs160
-rw-r--r--third_party/rust/authenticator/examples/test_exclude_list.rs315
6 files changed, 1479 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/examples/ctap2.rs b/third_party/rust/authenticator/examples/ctap2.rs
new file mode 100644
index 0000000000..20154e9b1e
--- /dev/null
+++ b/third_party/rust/authenticator/examples/ctap2.rs
@@ -0,0 +1,294 @@
+/* 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, GetAssertionExtensions, HmacSecretExtension,
+ MakeCredentialsExtensions, RegisterArgs, SignArgs,
+ },
+ ctap2::server::{
+ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
+ ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
+ },
+ statecallback::StateCallback,
+ COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
+};
+use getopts::Options;
+use sha2::{Digest, Sha256};
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, thread};
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+fn main() {
+ env_logger::init();
+
+ let args: Vec<String> = 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("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()),
+ };
+ 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 fallback = matches.opt_present("fallback");
+
+ let timeout_ms = match matches.opt_get_default::<u64>("timeout", 25) {
+ Ok(timeout_s) => {
+ println!("Using {}s as the timeout", &timeout_s);
+ timeout_s * 1_000
+ }
+ Err(e) => {
+ println!("{e}");
+ print_usage(&program, opts);
+ return;
+ }
+ };
+
+ println!("Asking a security key to register now...");
+ let challenge_str = format!(
+ "{}{}",
+ r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
+ r#" "version": "U2F_V2", "appId": "http://example.com"}"#
+ );
+ let mut challenge = Sha256::new();
+ challenge.update(challenge_str.as_bytes());
+ let chall_bytes: [u8; 32] = challenge.finalize().into();
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ 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}")
+ }
+ Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
+ println!("STATUS: device 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)) => {
+ println!("STATUS: Continuing with device: {dev_info}");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
+ println!(
+ "Wrong UV! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
+ println!("Too many failed UV-attempts.");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let user = User {
+ id: "user_id".as_bytes().to_vec(),
+ icon: None,
+ name: Some("A. User".to_string()),
+ display_name: None,
+ };
+ let origin = "https://example.com".to_string();
+ let ctap_args = RegisterArgs {
+ client_data_hash: chall_bytes,
+ relying_party: RelyingParty {
+ id: "example.com".to_string(),
+ name: None,
+ icon: None,
+ },
+ origin: origin.clone(),
+ user,
+ pub_cred_params: vec![
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::ES256,
+ },
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::RS256,
+ },
+ ],
+ exclude_list: vec![PublicKeyCredentialDescriptor {
+ id: vec![
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
+ 0x1c, 0x1d, 0x1e, 0x1f,
+ ],
+ transports: vec![Transport::USB, Transport::NFC],
+ }],
+ user_verification_req: UserVerificationRequirement::Preferred,
+ resident_key_req: ResidentKeyRequirement::Discouraged,
+ extensions: MakeCredentialsExtensions {
+ hmac_secret: if matches.opt_present("hmac_secret") {
+ Some(true)
+ } else {
+ None
+ },
+ ..Default::default()
+ },
+ pin: None,
+ use_ctap1_fallback: fallback,
+ };
+
+ let attestation_object;
+ loop {
+ let (register_tx, register_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ register_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.register(timeout_ms, ctap_args, status_tx.clone(), callback) {
+ panic!("Couldn't register: {:?}", e);
+ };
+
+ let register_result = register_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match register_result {
+ Ok(RegisterResult::CTAP1(_, _)) => panic!("Requested CTAP2, but got CTAP1 results!"),
+ Ok(RegisterResult::CTAP2(a)) => {
+ println!("Ok!");
+ attestation_object = a;
+ break;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+
+ println!("Register result: {:?}", &attestation_object);
+
+ println!();
+ println!("*********************************************************************");
+ println!("Asking a security key to sign now, with the data from the register...");
+ println!("*********************************************************************");
+
+ let allow_list;
+ if let Some(cred_data) = attestation_object.auth_data.credential_data {
+ allow_list = vec![PublicKeyCredentialDescriptor {
+ id: cred_data.credential_id,
+ transports: vec![Transport::USB],
+ }];
+ } else {
+ allow_list = Vec::new();
+ }
+
+ let ctap_args = SignArgs {
+ client_data_hash: chall_bytes,
+ origin,
+ relying_party_id: "example.com".to_string(),
+ allow_list,
+ user_verification_req: UserVerificationRequirement::Preferred,
+ user_presence_req: true,
+ extensions: GetAssertionExtensions {
+ hmac_secret: if matches.opt_present("hmac_secret") {
+ Some(HmacSecretExtension::new(
+ vec![
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0x10, 0x11, 0x12, 0x13,
+ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25,
+ 0x26, 0x27, 0x28, 0x29, 0x30, 0x31, 0x32, 0x33, 0x34,
+ ],
+ None,
+ ))
+ } else {
+ None
+ },
+ },
+ pin: None,
+ alternate_rp_id: None,
+ use_ctap1_fallback: fallback,
+ };
+
+ loop {
+ let (sign_tx, sign_rx) = channel();
+
+ let callback = StateCallback::new(Box::new(move |rv| {
+ sign_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.sign(timeout_ms, ctap_args, status_tx, callback) {
+ panic!("Couldn't sign: {:?}", e);
+ }
+
+ let sign_result = sign_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+
+ match sign_result {
+ Ok(SignResult::CTAP1(..)) => panic!("Requested CTAP2, but got CTAP1 sign results!"),
+ Ok(SignResult::CTAP2(assertion_object)) => {
+ println!("Assertion Object: {assertion_object:?}");
+ println!("Done.");
+ break;
+ }
+ Err(e) => panic!("Signing failed: {:?}", e),
+ }
+ }
+}
diff --git a/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs
new file mode 100644
index 0000000000..ea5aa5bf3c
--- /dev/null
+++ b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs
@@ -0,0 +1,366 @@
+/* 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, RegisterArgs, SignArgs},
+ ctap2::server::{
+ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
+ ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
+ },
+ statecallback::StateCallback,
+ COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
+};
+use getopts::Options;
+use sha2::{Digest, Sha256};
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, thread};
+
+fn print_usage(program: &str, opts: Options) {
+ println!("------------------------------------------------------------------------");
+ println!("This program registers 3x the same origin with different users and");
+ println!("requests 'discoverable credentials' for them.");
+ println!("After that, we try to log in to that origin and list all credentials found.");
+ println!("------------------------------------------------------------------------");
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+fn register_user(manager: &mut AuthenticatorService, username: &str, timeout_ms: u64) {
+ println!();
+ println!("*********************************************************************");
+ println!("Asking a security key to register now with user: {username}");
+ println!("*********************************************************************");
+
+ println!("Asking a security key to register now...");
+ let challenge_str = format!(
+ "{}{}{}{}",
+ r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
+ r#" "version": "U2F_V2", "appId": "http://example.com", "username": ""#,
+ username,
+ r#""}"#
+ );
+ let mut challenge = Sha256::new();
+ challenge.update(challenge_str.as_bytes());
+ let chall_bytes = challenge.finalize().into();
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ 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}")
+ }
+ Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
+ println!("STATUS: device 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)) => {
+ println!("STATUS: Continuing with device: {dev_info}");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
+ println!(
+ "Wrong UV! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
+ println!("Too many failed UV-attempts.");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let user = User {
+ id: username.as_bytes().to_vec(),
+ icon: None,
+ name: Some(username.to_string()),
+ display_name: None,
+ };
+ let origin = "https://example.com".to_string();
+ let ctap_args = RegisterArgs {
+ client_data_hash: chall_bytes,
+ relying_party: RelyingParty {
+ id: "example.com".to_string(),
+ name: None,
+ icon: None,
+ },
+ origin,
+ user,
+ pub_cred_params: vec![
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::ES256,
+ },
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::RS256,
+ },
+ ],
+ exclude_list: vec![PublicKeyCredentialDescriptor {
+ id: vec![],
+ transports: vec![Transport::USB, Transport::NFC],
+ }],
+ user_verification_req: UserVerificationRequirement::Required,
+ resident_key_req: ResidentKeyRequirement::Required,
+ extensions: Default::default(),
+ pin: None,
+ use_ctap1_fallback: false,
+ };
+
+ let attestation_object;
+ loop {
+ let (register_tx, register_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ register_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.register(timeout_ms, ctap_args, status_tx, callback) {
+ panic!("Couldn't register: {:?}", e);
+ };
+
+ let register_result = register_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match register_result {
+ Ok(RegisterResult::CTAP1(_, _)) => panic!("Requested CTAP2, but got CTAP1 results!"),
+ Ok(RegisterResult::CTAP2(a)) => {
+ println!("Ok!");
+ attestation_object = a;
+ break;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+
+ println!("Register result: {:?}", &attestation_object);
+}
+
+fn main() {
+ env_logger::init();
+
+ let args: Vec<String> = 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::<u64>("timeout", 15) {
+ Ok(timeout_s) => {
+ println!("Using {}s as the timeout", &timeout_s);
+ timeout_s * 1_000
+ }
+ Err(e) => {
+ println!("{e}");
+ print_usage(&program, opts);
+ return;
+ }
+ };
+
+ for username in &["A. User", "A. Nother", "Dr. Who"] {
+ register_user(&mut manager, username, timeout_ms)
+ }
+
+ println!();
+ println!("*********************************************************************");
+ println!("Asking a security key to sign now, with the data from the register...");
+ println!("*********************************************************************");
+
+ // Discovering creds:
+ let allow_list = Vec::new();
+ let origin = "https://example.com".to_string();
+ let challenge_str = format!(
+ "{}{}",
+ r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
+ r#" "version": "U2F_V2", "appId": "http://example.com" "#,
+ );
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ 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}")
+ }
+ Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
+ println!("STATUS: device 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)) => {
+ println!("STATUS: Continuing with device: {dev_info}");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
+ println!(
+ "Wrong UV! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
+ println!("Too many failed UV-attempts.");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let mut challenge = Sha256::new();
+ challenge.update(challenge_str.as_bytes());
+ let chall_bytes = challenge.finalize().into();
+ let ctap_args = SignArgs {
+ client_data_hash: chall_bytes,
+ origin,
+ relying_party_id: "example.com".to_string(),
+ allow_list,
+ user_verification_req: UserVerificationRequirement::Required,
+ user_presence_req: true,
+ extensions: Default::default(),
+ pin: None,
+ alternate_rp_id: None,
+ use_ctap1_fallback: false,
+ };
+
+ loop {
+ let (sign_tx, sign_rx) = channel();
+
+ let callback = StateCallback::new(Box::new(move |rv| {
+ sign_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.sign(timeout_ms, ctap_args, status_tx, callback) {
+ panic!("Couldn't sign: {:?}", e);
+ }
+
+ let sign_result = sign_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+
+ match sign_result {
+ Ok(SignResult::CTAP1(..)) => panic!("Requested CTAP2, but got CTAP1 sign results!"),
+ Ok(SignResult::CTAP2(assertion_object)) => {
+ println!("Assertion Object: {assertion_object:?}");
+ println!("-----------------------------------------------------------------");
+ println!("Found credentials:");
+ println!(
+ "{:?}",
+ assertion_object
+ .0
+ .iter()
+ .map(|x| x.user.clone().unwrap().name.unwrap()) // Unwrapping here, as these shouldn't fail
+ .collect::<Vec<_>>()
+ );
+ println!("-----------------------------------------------------------------");
+ println!("Done.");
+ break;
+ }
+ Err(e) => panic!("Signing failed: {:?}", e),
+ }
+ }
+}
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 0000000000..714279fbbf
--- /dev/null
+++ b/third_party/rust/authenticator/examples/interactive_management.rs
@@ -0,0 +1,204 @@
+/* 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<StatusUpdate>) {
+ 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::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ Ok(StatusUpdate::PinUvError(..)) => {
+ println!("STATUS: Pin Error!");
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ }
+}
+
+fn main() {
+ env_logger::init();
+
+ let args: Vec<String> = 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::<u64>("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::<StatusUpdate>();
+ thread::spawn(move || interactive_status_callback(status_rx));
+
+ let (manage_tx, manage_rx) = channel();
+ let state_callback =
+ StateCallback::<Result<ResetResult, AuthenticatorError>>::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/reset.rs b/third_party/rust/authenticator/examples/reset.rs
new file mode 100644
index 0000000000..1194cb2b06
--- /dev/null
+++ b/third_party/rust/authenticator/examples/reset.rs
@@ -0,0 +1,140 @@
+/* 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,
+ ctap2::commands::StatusCode,
+ errors::{AuthenticatorError, CommandError, HIDError},
+ statecallback::StateCallback,
+ StatusUpdate,
+};
+use getopts::Options;
+use std::env;
+use std::sync::mpsc::{channel, RecvError};
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+fn main() {
+ env_logger::init();
+
+ let args: Vec<String> = 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::<u64>("timeout", 25) {
+ Ok(timeout_s) => {
+ println!("Using {}s as the timeout", &timeout_s);
+ timeout_s * 1_000
+ }
+ Err(e) => {
+ println!("{e}");
+ print_usage(&program, opts);
+ return;
+ }
+ };
+
+ println!(
+ "NOTE: Please unplug all devices, type in 'yes' and plug in the device that should be reset."
+ );
+ loop {
+ let mut s = String::new();
+ println!("ATTENTION: Resetting a device will wipe all credentials! Do you wish to continue? [yes/N]");
+ std::io::stdin()
+ .read_line(&mut s)
+ .expect("Did not enter a correct string");
+ let trimmed = s.trim();
+ if trimmed.is_empty() || trimmed == "N" || trimmed == "n" {
+ println!("Exiting without reset.");
+ return;
+ }
+ if trimmed == "y" {
+ println!("Please type in the whole word 'yes'");
+ continue;
+ }
+ if trimmed == "yes" {
+ break;
+ }
+ }
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ let (reset_tx, reset_rx) = channel();
+ let rs_tx = reset_tx;
+ let callback = StateCallback::new(Box::new(move |rv| {
+ let _ = rs_tx.send(rv);
+ }));
+
+ if let Err(e) = manager.reset(timeout_ms, status_tx, callback) {
+ panic!("Couldn't register: {:?}", e);
+ };
+
+ loop {
+ match status_rx.recv() {
+ Ok(StatusUpdate::SelectDeviceNotice) => {
+ println!("ERROR: Please unplug all other tokens that should not be reset!");
+ // Needed to give the tokens enough time to start blinking
+ // otherwise we may cancel pre-maturely and this binary will hang
+ std::thread::sleep(std::time::Duration::from_millis(200));
+ manager.cancel().unwrap();
+ return;
+ }
+ Ok(StatusUpdate::DeviceSelected(dev_info)) => {
+ println!("STATUS: Continuing with device: {dev_info}");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ break;
+ }
+ Ok(StatusUpdate::PinUvError(..)) => panic!("Reset should never ask for a PIN!"),
+ Ok(_) => { /* Ignore all other updates */ }
+ Err(RecvError) => {
+ println!("RecvError");
+ return;
+ }
+ }
+ }
+
+ let reset_result = reset_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match reset_result {
+ Ok(()) => {
+ println!("Token successfully reset!");
+ }
+ Err(AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
+ StatusCode::NotAllowed,
+ _,
+ )))) => {
+ println!("Resetting is only allowed within the first 10 seconds after powering up.");
+ println!("Please unplug your device, plug it back in and try again.");
+ }
+ Err(e) => panic!("Reset failed: {:?}", e),
+ };
+}
diff --git a/third_party/rust/authenticator/examples/set_pin.rs b/third_party/rust/authenticator/examples/set_pin.rs
new file mode 100644
index 0000000000..18304648b8
--- /dev/null
+++ b/third_party/rust/authenticator/examples/set_pin.rs
@@ -0,0 +1,160 @@
+/* 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,
+ statecallback::StateCallback,
+ Pin, StatusPinUv, StatusUpdate,
+};
+use getopts::Options;
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, thread};
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+fn main() {
+ env_logger::init();
+
+ let args: Vec<String> = 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::<u64>("timeout", 25) {
+ 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 new_pin = rpassword::prompt_password_stderr("Enter new PIN: ").expect("Failed to read PIN");
+ let repeat_new_pin =
+ rpassword::prompt_password_stderr("Enter it again: ").expect("Failed to read PIN");
+ if new_pin != repeat_new_pin {
+ println!("PINs did not match!");
+ return;
+ }
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ 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}")
+ }
+ Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
+ println!("STATUS: device 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)) => {
+ println!("STATUS: Continuing with device: {dev_info}");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
+ println!(
+ "Wrong UV! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
+ println!("Too many failed UV-attempts.");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let (reset_tx, reset_rx) = channel();
+ let rs_tx = reset_tx;
+ let callback = StateCallback::new(Box::new(move |rv| {
+ let _ = rs_tx.send(rv);
+ }));
+
+ if let Err(e) = manager.set_pin(timeout_ms, Pin::new(&new_pin), status_tx, callback) {
+ panic!("Couldn't call set_pin: {:?}", e);
+ };
+
+ let reset_result = reset_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match reset_result {
+ Ok(()) => {
+ println!("PIN successfully set!");
+ }
+ Err(e) => panic!("Setting PIN failed: {:?}", e),
+ };
+}
diff --git a/third_party/rust/authenticator/examples/test_exclude_list.rs b/third_party/rust/authenticator/examples/test_exclude_list.rs
new file mode 100644
index 0000000000..ad2b33feba
--- /dev/null
+++ b/third_party/rust/authenticator/examples/test_exclude_list.rs
@@ -0,0 +1,315 @@
+/* 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, GetAssertionExtensions, RegisterArgs, SignArgs},
+ ctap2::commands::StatusCode,
+ ctap2::server::{
+ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty,
+ ResidentKeyRequirement, Transport, User, UserVerificationRequirement,
+ },
+ errors::{AuthenticatorError, CommandError, HIDError, UnsupportedOption},
+ statecallback::StateCallback,
+ COSEAlgorithm, Pin, RegisterResult, SignResult, StatusPinUv, StatusUpdate,
+};
+
+use getopts::Options;
+use sha2::{Digest, Sha256};
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, thread};
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {program} [options]");
+ print!("{}", opts.usage(&brief));
+}
+
+fn main() {
+ env_logger::init();
+
+ let args: Vec<String> = env::args().collect();
+ let program = args[0].clone();
+
+ let mut opts = Options::new();
+ 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");
+
+ manager.add_u2f_usb_hid_platform_transports();
+
+ let timeout_ms = match matches.opt_get_default::<u64>("timeout", 25) {
+ Ok(timeout_s) => {
+ println!("Using {}s as the timeout", &timeout_s);
+ timeout_s * 1_000
+ }
+ Err(e) => {
+ println!("{e}");
+ print_usage(&program, opts);
+ return;
+ }
+ };
+
+ println!("Asking a security key to register now...");
+ let challenge_str = format!(
+ "{}{}",
+ r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
+ r#" "version": "U2F_V2", "appId": "http://example.com"}"#
+ );
+ let mut challenge = Sha256::new();
+ challenge.update(challenge_str.as_bytes());
+ let chall_bytes = challenge.finalize().into();
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ 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}")
+ }
+ Ok(StatusUpdate::DeviceUnavailable { dev_info }) => {
+ println!("STATUS: device 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)) => {
+ println!("STATUS: Continuing with device: {dev_info}");
+ }
+ Ok(StatusUpdate::PresenceRequired) => {
+ println!("STATUS: waiting for user presence");
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinRequired(sender))) => {
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidPin(sender, attempts))) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ let raw_pin =
+ rpassword::prompt_password_stderr("Enter PIN: ").expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinAuthBlocked)) => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::PinBlocked)) => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::InvalidUv(attempts))) => {
+ println!(
+ "Wrong UV! {}",
+ attempts.map_or("Try again.".to_string(), |a| format!(
+ "You have {a} attempts left."
+ ))
+ );
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(StatusPinUv::UvBlocked)) => {
+ println!("Too many failed UV-attempts.");
+ continue;
+ }
+ Ok(StatusUpdate::PinUvError(e)) => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let user = User {
+ id: "user_id".as_bytes().to_vec(),
+ icon: None,
+ name: Some("A. User".to_string()),
+ display_name: None,
+ };
+ let origin = "https://example.com".to_string();
+ let mut ctap_args = RegisterArgs {
+ client_data_hash: chall_bytes,
+ relying_party: RelyingParty {
+ id: "example.com".to_string(),
+ name: None,
+ icon: None,
+ },
+ origin: origin.clone(),
+ user,
+ pub_cred_params: vec![
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::ES256,
+ },
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::RS256,
+ },
+ ],
+ exclude_list: vec![],
+ user_verification_req: UserVerificationRequirement::Preferred,
+ resident_key_req: ResidentKeyRequirement::Discouraged,
+ extensions: Default::default(),
+ pin: None,
+ use_ctap1_fallback: false,
+ };
+
+ let mut registered_key_handle = None;
+ loop {
+ let (register_tx, register_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ register_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.register(timeout_ms, ctap_args.clone(), status_tx.clone(), callback)
+ {
+ panic!("Couldn't register: {:?}", e);
+ };
+
+ let register_result = register_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match register_result {
+ Ok(RegisterResult::CTAP1(_, _)) => panic!("Requested CTAP2, but got CTAP1 results!"),
+ Ok(RegisterResult::CTAP2(a)) => {
+ println!("Ok!");
+ println!("Registering again with the key_handle we just got back. This should result in a 'already registered' error.");
+ let key_handle = a.auth_data.credential_data.unwrap().credential_id.clone();
+ let pub_key = PublicKeyCredentialDescriptor {
+ id: key_handle,
+ transports: vec![Transport::USB],
+ };
+ ctap_args.exclude_list = vec![pub_key.clone()];
+ registered_key_handle = Some(pub_key);
+ continue;
+ }
+ Err(AuthenticatorError::CredentialExcluded) => {
+ println!("Got an 'already registered' error, as expected.");
+ if ctap_args.exclude_list.len() > 1 {
+ println!("Quitting.");
+ break;
+ }
+ println!("Extending the list to contain more invalid handles.");
+ let registered_handle = ctap_args.exclude_list[0].clone();
+ ctap_args.exclude_list = vec![];
+ for ii in 0..10 {
+ ctap_args.exclude_list.push(PublicKeyCredentialDescriptor {
+ id: vec![ii; 50],
+ transports: vec![Transport::USB],
+ });
+ }
+ ctap_args.exclude_list.push(registered_handle);
+ continue;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+
+ // Signing
+ let mut ctap_args = SignArgs {
+ client_data_hash: chall_bytes,
+ origin,
+ relying_party_id: "example.com".to_string(),
+ allow_list: vec![],
+ extensions: GetAssertionExtensions::default(),
+ pin: None,
+ alternate_rp_id: None,
+ use_ctap1_fallback: false,
+ user_verification_req: UserVerificationRequirement::Preferred,
+ user_presence_req: true,
+ };
+
+ let mut no_cred_errors_done = false;
+ loop {
+ let (sign_tx, sign_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ sign_tx.send(rv).unwrap();
+ }));
+
+ if let Err(e) = manager.sign(timeout_ms, ctap_args.clone(), status_tx.clone(), callback) {
+ panic!("Couldn't sign: {:?}", e);
+ };
+
+ let sign_result = sign_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ match sign_result {
+ Ok(SignResult::CTAP1(..)) => panic!("Requested CTAP2, but got CTAP1 results!"),
+ Ok(SignResult::CTAP2(..)) => {
+ if !no_cred_errors_done {
+ panic!("Should have errored out with NoCredentials, but it succeeded.");
+ }
+ println!("Successfully signed!");
+ if ctap_args.allow_list.len() > 1 {
+ println!("Quitting.");
+ break;
+ }
+ println!("Signing again with a long allow_list that needs pre-flighting.");
+ let registered_handle = registered_key_handle.as_ref().unwrap().clone();
+ ctap_args.allow_list = vec![];
+ for ii in 0..10 {
+ ctap_args.allow_list.push(PublicKeyCredentialDescriptor {
+ id: vec![ii; 50],
+ transports: vec![Transport::USB],
+ });
+ }
+ ctap_args.allow_list.push(registered_handle);
+ continue;
+ }
+ Err(AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
+ StatusCode::NoCredentials,
+ None,
+ ))))
+ | Err(AuthenticatorError::UnsupportedOption(UnsupportedOption::EmptyAllowList)) => {
+ if ctap_args.allow_list.is_empty() {
+ // Try again with a list of false creds. We should end up here again.
+ println!(
+ "Got an 'no credentials' error, as expected with an empty allow-list."
+ );
+ println!("Extending the list to contain only fake handles.");
+ ctap_args.allow_list = vec![];
+ for ii in 0..10 {
+ ctap_args.allow_list.push(PublicKeyCredentialDescriptor {
+ id: vec![ii; 50],
+ transports: vec![Transport::USB],
+ });
+ }
+ } else {
+ println!(
+ "Got an 'no credentials' error, as expected with an all-fake allow-list."
+ );
+ println!("Extending the list to contain one valid handle.");
+ let registered_handle = registered_key_handle.as_ref().unwrap().clone();
+ ctap_args.allow_list = vec![registered_handle];
+ no_cred_errors_done = true;
+ }
+ continue;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+ println!("Done")
+}