summaryrefslogtreecommitdiffstats
path: root/third_party/rust/authenticator/examples
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/rust/authenticator/examples
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
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/ctap1.rs203
-rw-r--r--third_party/rust/authenticator/examples/ctap2.rs290
-rw-r--r--third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs354
-rw-r--r--third_party/rust/authenticator/examples/main.rs199
-rw-r--r--third_party/rust/authenticator/examples/reset.rs137
-rw-r--r--third_party/rust/authenticator/examples/set_pin.rs144
-rw-r--r--third_party/rust/authenticator/examples/test_exclude_list.rs211
7 files changed, 1538 insertions, 0 deletions
diff --git a/third_party/rust/authenticator/examples/ctap1.rs b/third_party/rust/authenticator/examples/ctap1.rs
new file mode 100644
index 0000000000..444a2ef573
--- /dev/null
+++ b/third_party/rust/authenticator/examples/ctap1.rs
@@ -0,0 +1,203 @@
+/* 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, CtapVersion, RegisterArgsCtap1, SignArgsCtap1},
+ statecallback::StateCallback,
+ AuthenticatorTransports, KeyHandle, RegisterFlags, RegisterResult, SignFlags, SignResult,
+ StatusUpdate,
+};
+use getopts::Options;
+use sha2::{Digest, Sha256};
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, io, thread};
+
+fn u2f_get_key_handle_from_register_response(register_response: &[u8]) -> io::Result<Vec<u8>> {
+ if register_response[0] != 0x05 {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Reserved byte not set correctly",
+ ));
+ }
+
+ let key_handle_len = register_response[66] as usize;
+ let mut public_key = register_response.to_owned();
+ let mut key_handle = public_key.split_off(67);
+ let _attestation = key_handle.split_off(key_handle_len);
+
+ Ok(key_handle)
+}
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {} [options]", program);
+ 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");
+ #[cfg(feature = "webdriver")]
+ opts.optflag("w", "webdriver", "enable WebDriver virtual bus");
+
+ 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(CtapVersion::CTAP2)
+ .expect("The auth service should initialize safely");
+
+ if !matches.opt_present("no-u2f-usb-hid") {
+ manager.add_u2f_usb_hid_platform_transports();
+ }
+
+ #[cfg(feature = "webdriver")]
+ {
+ if matches.opt_present("webdriver") {
+ manager.add_webdriver_virtual_bus();
+ }
+ }
+
+ 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;
+ }
+ };
+
+ println!("Asking a security key to register now...");
+ let challenge_str = format!(
+ "{}{}",
+ r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
+ r#" "version": "U2F_V2", "appId": "http://demo.yubico.com"}"#
+ );
+ let mut challenge = Sha256::new();
+ challenge.update(challenge_str.as_bytes());
+ let chall_bytes = challenge.finalize().to_vec();
+
+ let mut application = Sha256::new();
+ application.update(b"http://demo.yubico.com");
+ let app_bytes = application.finalize().to_vec();
+
+ let flags = RegisterFlags::empty();
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ thread::spawn(move || loop {
+ match status_rx.recv() {
+ 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::PinError(..))
+ | Ok(StatusUpdate::SelectDeviceNotice)
+ | Ok(StatusUpdate::DeviceSelected(..)) => {
+ panic!("STATUS: This can't happen for CTAP1!");
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let (register_tx, register_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ register_tx.send(rv).unwrap();
+ }));
+
+ let ctap_args = RegisterArgsCtap1 {
+ flags,
+ challenge: chall_bytes.clone(),
+ application: app_bytes.clone(),
+ key_handles: vec![],
+ };
+ manager
+ .register(timeout_ms, ctap_args.into(), status_tx.clone(), callback)
+ .expect("Couldn't register");
+
+ let register_result = register_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ let (register_data, device_info) = match register_result {
+ Ok(RegisterResult::CTAP1(r, d)) => (r, d),
+ Ok(RegisterResult::CTAP2(..)) => panic!("Did not request CTAP2, but got CTAP2 results!"),
+ Err(e) => panic!("Registration failed {:?}", e),
+ };
+
+ println!("Register result: {}", base64::encode(&register_data));
+ println!("Device info: {}", &device_info);
+ println!("");
+ println!("*********************************************************************");
+ println!("Asking a security key to sign now, with the data from the register...");
+ println!("*********************************************************************");
+
+ let credential = u2f_get_key_handle_from_register_response(&register_data).unwrap();
+ let key_handle = KeyHandle {
+ credential,
+ transports: AuthenticatorTransports::empty(),
+ };
+
+ let flags = SignFlags::empty();
+ 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,
+ SignArgsCtap1 {
+ flags,
+ challenge: chall_bytes,
+ app_ids: vec![app_bytes],
+ key_handles: vec![key_handle],
+ }
+ .into(),
+ status_tx,
+ callback,
+ ) {
+ panic!("Couldn't register: {:?}", e);
+ }
+
+ let sign_result = sign_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ if let SignResult::CTAP1(_, handle_used, sign_data, device_info) =
+ sign_result.expect("Sign failed")
+ {
+ println!("Sign result: {}", base64::encode(&sign_data));
+ println!("Key handle used: {}", base64::encode(&handle_used));
+ println!("Device info: {}", &device_info);
+ println!("Done.");
+ } else {
+ panic!("Expected CTAP version 1 for sign result!");
+ }
+}
diff --git a/third_party/rust/authenticator/examples/ctap2.rs b/third_party/rust/authenticator/examples/ctap2.rs
new file mode 100644
index 0000000000..2067a0e921
--- /dev/null
+++ b/third_party/rust/authenticator/examples/ctap2.rs
@@ -0,0 +1,290 @@
+/* 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, CtapVersion, GetAssertionExtensions, GetAssertionOptions,
+ HmacSecretExtension, MakeCredentialsExtensions, MakeCredentialsOptions, RegisterArgsCtap2,
+ SignArgsCtap2,
+ },
+ ctap2::server::{
+ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User,
+ },
+ errors::PinError,
+ statecallback::StateCallback,
+ COSEAlgorithm, Pin, RegisterResult, SignResult, 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: {} [options]", program);
+ 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");
+ 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(CtapVersion::CTAP2)
+ .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!("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().to_vec();
+
+ // TODO(MS): Needs to be added to RegisterArgsCtap2
+ // let flags = RegisterFlags::empty();
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ thread::spawn(move || loop {
+ match status_rx.recv() {
+ 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::PinError(error, sender)) => match error {
+ PinError::PinRequired => {
+ 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;
+ }
+ PinError::InvalidPin(attempts) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or(format!("Try again."), |a| format!(
+ "You have {} attempts left.",
+ a
+ ))
+ );
+ 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;
+ }
+ PinError::PinAuthBlocked => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ PinError::PinBlocked => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ 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 = RegisterArgsCtap2 {
+ challenge: chall_bytes.clone(),
+ relying_party: RelyingParty {
+ id: "example.com".to_string(),
+ name: None,
+ icon: None,
+ },
+ origin: origin.clone(),
+ user: user.clone(),
+ 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],
+ }],
+ options: MakeCredentialsOptions {
+ resident_key: None,
+ user_verification: None,
+ },
+ extensions: MakeCredentialsExtensions {
+ hmac_secret: if matches.opt_present("hmac_secret") {
+ Some(true)
+ } else {
+ None
+ },
+ ..Default::default()
+ },
+ pin: None,
+ };
+
+ let attestation_object;
+ let client_data;
+ 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().into(),
+ 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, c)) => {
+ println!("Ok!");
+ attestation_object = a;
+ client_data = c;
+ break;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+
+ println!("Register result: {:?}", &attestation_object);
+ println!("Collected client data: {:?}", &client_data);
+
+ 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.clone(),
+ transports: vec![Transport::USB],
+ }];
+ } else {
+ allow_list = Vec::new();
+ }
+
+ let ctap_args = SignArgsCtap2 {
+ challenge: chall_bytes,
+ origin,
+ relying_party_id: "example.com".to_string(),
+ allow_list,
+ options: GetAssertionOptions::default(),
+ 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,
+ };
+
+ 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().into(),
+ 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 sign results!"),
+ Ok(SignResult::CTAP2(assertion_object, _client_data)) => {
+ 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..ebbbd766ed
--- /dev/null
+++ b/third_party/rust/authenticator/examples/ctap2_discoverable_creds.rs
@@ -0,0 +1,354 @@
+/* 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, CtapVersion, GetAssertionOptions, MakeCredentialsOptions,
+ RegisterArgsCtap2, SignArgsCtap2,
+ },
+ ctap2::server::{
+ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User,
+ },
+ errors::PinError,
+ statecallback::StateCallback,
+ COSEAlgorithm, Pin, RegisterResult, SignResult, 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: {} [options]", program);
+ 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().to_vec();
+
+ // TODO(MS): Needs to be added to RegisterArgsCtap2
+ // let flags = RegisterFlags::empty();
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ thread::spawn(move || loop {
+ match status_rx.recv() {
+ 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::PinError(error, sender)) => match error {
+ PinError::PinRequired => {
+ 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;
+ }
+ PinError::InvalidPin(attempts) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or(format!("Try again."), |a| format!(
+ "You have {} attempts left.",
+ a
+ ))
+ );
+ 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;
+ }
+ PinError::PinAuthBlocked => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ PinError::PinBlocked => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ 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 = RegisterArgsCtap2 {
+ challenge: chall_bytes.clone(),
+ relying_party: RelyingParty {
+ id: "example.com".to_string(),
+ name: None,
+ icon: None,
+ },
+ origin: origin.clone(),
+ user: user.clone(),
+ pub_cred_params: vec![
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::ES256,
+ },
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::RS256,
+ },
+ ],
+ exclude_list: vec![PublicKeyCredentialDescriptor {
+ id: vec![],
+ transports: vec![Transport::USB, Transport::NFC],
+ }],
+ options: MakeCredentialsOptions {
+ resident_key: Some(true),
+ user_verification: Some(true),
+ },
+ extensions: Default::default(),
+ pin: None,
+ };
+
+ let attestation_object;
+ let client_data;
+ 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().into(),
+ 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, c)) => {
+ println!("Ok!");
+ attestation_object = a;
+ client_data = c;
+ break;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+
+ println!("Register result: {:?}", &attestation_object);
+ println!("Collected client data: {:?}", &client_data);
+}
+
+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(CtapVersion::CTAP2)
+ .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 vec!["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::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::PinError(error, sender)) => match error {
+ PinError::PinRequired => {
+ 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;
+ }
+ PinError::InvalidPin(attempts) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or(format!("Try again."), |a| format!(
+ "You have {} attempts left.",
+ a
+ ))
+ );
+ 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;
+ }
+ PinError::PinAuthBlocked => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ PinError::PinBlocked => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ 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().to_vec();
+ let ctap_args = SignArgsCtap2 {
+ challenge: chall_bytes,
+ origin,
+ relying_party_id: "example.com".to_string(),
+ allow_list,
+ options: GetAssertionOptions::default(),
+ extensions: Default::default(),
+ pin: None,
+ };
+
+ 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().into(),
+ 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 sign results!"),
+ Ok(SignResult::CTAP2(assertion_object, _client_data)) => {
+ 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/main.rs b/third_party/rust/authenticator/examples/main.rs
new file mode 100644
index 0000000000..decc877b34
--- /dev/null
+++ b/third_party/rust/authenticator/examples/main.rs
@@ -0,0 +1,199 @@
+/* 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, CtapVersion, RegisterArgsCtap1, SignArgsCtap1},
+ statecallback::StateCallback,
+ AuthenticatorTransports, KeyHandle, RegisterFlags, RegisterResult, SignFlags, SignResult,
+ StatusUpdate,
+};
+use getopts::Options;
+use sha2::{Digest, Sha256};
+use std::sync::mpsc::{channel, RecvError};
+use std::{env, io, thread};
+
+fn u2f_get_key_handle_from_register_response(register_response: &[u8]) -> io::Result<Vec<u8>> {
+ if register_response[0] != 0x05 {
+ return Err(io::Error::new(
+ io::ErrorKind::InvalidData,
+ "Reserved byte not set correctly",
+ ));
+ }
+
+ let key_handle_len = register_response[66] as usize;
+ let mut public_key = register_response.to_owned();
+ let mut key_handle = public_key.split_off(67);
+ let _attestation = key_handle.split_off(key_handle_len);
+
+ Ok(key_handle)
+}
+
+fn print_usage(program: &str, opts: Options) {
+ let brief = format!("Usage: {} [options]", program);
+ 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");
+ #[cfg(feature = "webdriver")]
+ opts.optflag("w", "webdriver", "enable WebDriver virtual bus");
+
+ 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(CtapVersion::CTAP1)
+ .expect("The auth service should initialize safely");
+
+ if !matches.opt_present("no-u2f-usb-hid") {
+ manager.add_u2f_usb_hid_platform_transports();
+ }
+
+ #[cfg(feature = "webdriver")]
+ {
+ if matches.opt_present("webdriver") {
+ manager.add_webdriver_virtual_bus();
+ }
+ }
+
+ 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;
+ }
+ };
+
+ println!("Asking a security key to register now...");
+ let challenge_str = format!(
+ "{}{}",
+ r#"{"challenge": "1vQ9mxionq0ngCnjD-wTsv1zUSrGRtFqG2xP09SbZ70","#,
+ r#" "version": "U2F_V2", "appId": "http://demo.yubico.com"}"#
+ );
+ let mut challenge = Sha256::new();
+ challenge.update(challenge_str.as_bytes());
+ let chall_bytes = challenge.finalize().to_vec();
+
+ let mut application = Sha256::new();
+ application.update(b"http://demo.yubico.com");
+ let app_bytes = application.finalize().to_vec();
+
+ let flags = RegisterFlags::empty();
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ thread::spawn(move || loop {
+ match status_rx.recv() {
+ 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::PinError(..))
+ | Ok(StatusUpdate::SelectDeviceNotice)
+ | Ok(StatusUpdate::DeviceSelected(..)) => {
+ panic!("STATUS: This can't happen for CTAP1!");
+ }
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let (register_tx, register_rx) = channel();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ register_tx.send(rv).unwrap();
+ }));
+
+ let ctap_args = RegisterArgsCtap1 {
+ flags,
+ challenge: chall_bytes.clone(),
+ application: app_bytes.clone(),
+ key_handles: vec![],
+ };
+ manager
+ .register(timeout_ms, ctap_args.into(), status_tx.clone(), callback)
+ .expect("Couldn't register");
+
+ let register_result = register_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ let (register_data, device_info) = match register_result {
+ Ok(RegisterResult::CTAP1(r, d)) => (r, d),
+ Ok(RegisterResult::CTAP2(..)) => panic!("Did not request CTAP2, but got CTAP2 results!"),
+ Err(_) => panic!("Registration failed"),
+ };
+
+ println!("Register result: {}", base64::encode(&register_data));
+ println!("Device info: {}", &device_info);
+ println!("Asking a security key to sign now, with the data from the register...");
+ let credential = u2f_get_key_handle_from_register_response(&register_data).unwrap();
+ let key_handle = KeyHandle {
+ credential,
+ transports: AuthenticatorTransports::empty(),
+ };
+
+ let flags = SignFlags::empty();
+ 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,
+ SignArgsCtap1 {
+ flags,
+ challenge: chall_bytes,
+ app_ids: vec![app_bytes],
+ key_handles: vec![key_handle],
+ }
+ .into(),
+ status_tx,
+ callback,
+ ) {
+ panic!("Couldn't register: {:?}", e);
+ }
+
+ let sign_result = sign_rx
+ .recv()
+ .expect("Problem receiving, unable to continue");
+ if let SignResult::CTAP1(_, handle_used, sign_data, device_info) =
+ sign_result.expect("Sign failed")
+ {
+ println!("Sign result: {}", base64::encode(&sign_data));
+ println!("Key handle used: {}", base64::encode(&handle_used));
+ println!("Device info: {}", &device_info);
+ println!("Done.");
+ } else {
+ panic!("Expected CTAP version 1 for sign result!");
+ }
+}
diff --git a/third_party/rust/authenticator/examples/reset.rs b/third_party/rust/authenticator/examples/reset.rs
new file mode 100644
index 0000000000..96bc6e8535
--- /dev/null
+++ b/third_party/rust/authenticator/examples/reset.rs
@@ -0,0 +1,137 @@
+/* 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, CtapVersion},
+ 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: {} [options]", program);
+ 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(CtapVersion::CTAP2)
+ .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.clone();
+ let callback = StateCallback::new(Box::new(move |rv| {
+ let _ = rs_tx.send(rv);
+ }));
+
+ if let Err(e) = manager.reset(timeout_ms, status_tx.clone(), 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);
+ break;
+ }
+ Ok(StatusUpdate::PinError(..)) => 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..b5fd810e09
--- /dev/null
+++ b/third_party/rust/authenticator/examples/set_pin.rs
@@ -0,0 +1,144 @@
+/* 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, CtapVersion},
+ statecallback::StateCallback,
+ Pin, PinError, 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: {} [options]", program);
+ 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(CtapVersion::CTAP2)
+ .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::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::PinError(error, sender)) => match error {
+ PinError::PinRequired => {
+ let raw_pin = rpassword::prompt_password_stderr("Enter current PIN: ")
+ .expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ PinError::InvalidPin(attempts) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or(format!("Try again."), |a| format!(
+ "You have {} attempts left.",
+ a
+ ))
+ );
+ let raw_pin = rpassword::prompt_password_stderr("Enter current PIN: ")
+ .expect("Failed to read PIN");
+ sender.send(Pin::new(&raw_pin)).expect("Failed to send PIN");
+ continue;
+ }
+ PinError::PinAuthBlocked => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ PinError::PinBlocked => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ e => {
+ panic!("Unexpected error: {:?}", e)
+ }
+ },
+ Err(RecvError) => {
+ println!("STATUS: end");
+ return;
+ }
+ }
+ });
+
+ let (reset_tx, reset_rx) = channel();
+ let rs_tx = reset_tx.clone();
+ 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.clone(), 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..c5b019287b
--- /dev/null
+++ b/third_party/rust/authenticator/examples/test_exclude_list.rs
@@ -0,0 +1,211 @@
+/* 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, CtapVersion, MakeCredentialsOptions, RegisterArgsCtap2,
+ },
+ ctap2::commands::StatusCode,
+ ctap2::server::{
+ PublicKeyCredentialDescriptor, PublicKeyCredentialParameters, RelyingParty, Transport, User,
+ },
+ errors::{AuthenticatorError, CommandError, HIDError, PinError},
+ statecallback::StateCallback,
+ COSEAlgorithm, Pin, RegisterResult, 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: {} [options]", program);
+ 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(CtapVersion::CTAP2)
+ .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().to_vec();
+
+ // TODO(MS): Needs to be added to RegisterArgsCtap2
+ // let flags = RegisterFlags::empty();
+
+ let (status_tx, status_rx) = channel::<StatusUpdate>();
+ thread::spawn(move || loop {
+ match status_rx.recv() {
+ 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::PinError(error, sender)) => match error {
+ PinError::PinRequired => {
+ 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;
+ }
+ PinError::InvalidPin(attempts) => {
+ println!(
+ "Wrong PIN! {}",
+ attempts.map_or(format!("Try again."), |a| format!(
+ "You have {} attempts left.",
+ a
+ ))
+ );
+ 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;
+ }
+ PinError::PinAuthBlocked => {
+ panic!("Too many failed attempts in one row. Your device has been temporarily blocked. Please unplug it and plug in again.")
+ }
+ PinError::PinBlocked => {
+ panic!("Too many failed attempts. Your device has been blocked. Reset it.")
+ }
+ 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 = RegisterArgsCtap2 {
+ challenge: chall_bytes.clone(),
+ relying_party: RelyingParty {
+ id: "example.com".to_string(),
+ name: None,
+ icon: None,
+ },
+ origin: origin.clone(),
+ user: user.clone(),
+ pub_cred_params: vec![
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::ES256,
+ },
+ PublicKeyCredentialParameters {
+ alg: COSEAlgorithm::RS256,
+ },
+ ],
+ exclude_list: vec![],
+ options: MakeCredentialsOptions {
+ resident_key: None,
+ user_verification: None,
+ },
+ extensions: Default::default(),
+ pin: 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().into(),
+ 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, _c)) => {
+ println!("Ok!");
+ println!("Registering again with the key_handle we just got back. This should result in a 'already registered' error.");
+ let registered_key_handle =
+ a.auth_data.credential_data.unwrap().credential_id.clone();
+ ctap_args.exclude_list = vec![PublicKeyCredentialDescriptor {
+ id: registered_key_handle.clone(),
+ transports: vec![Transport::USB],
+ }];
+ continue;
+ }
+ Err(AuthenticatorError::HIDError(HIDError::Command(CommandError::StatusCode(
+ StatusCode::CredentialExcluded,
+ None,
+ )))) => {
+ println!("Got an 'already registered' error. Quitting.");
+ break;
+ }
+ Err(e) => panic!("Registration failed: {:?}", e),
+ };
+ }
+ println!("Done")
+}