diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7976addd..ea20437d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,14 +53,21 @@ jobs: - run: cargo build --workspace --exclude webauthn-authenticator-rs # Don't run clippy on Windows, we only need to run it on Linux - # - # FIXME: clippy in rust 1.69.0 fails a clap macro expansion - # https://github.com/rust-lang/rust-clippy/issues/10421 - - if: runner.os != 'windows' && matrix.rust_version != 'stable' + - if: runner.os != 'windows' run: cargo clippy --no-deps --workspace --exclude webauthn-authenticator-rs --all-targets - run: cargo test --workspace --exclude webauthn-authenticator-rs + # Some clap errors manifest as panics at runtime. Running tools with + # --help should be enough to find an issue. + - run: cargo run --bin cable-tunnel-server-backend -- --help + - run: cargo run --bin cable-tunnel-server-frontend -- --help + # fido-key-manager requires elevation on Windows, which cargo can't + # handle. + - if: runner.os != 'windows' + run: cargo run --bin fido-key-manager -- --help + - run: cargo run --bin fido-mds-tool -- --help + authenticator: name: webauthn-authenticator-rs test strategy: @@ -119,14 +126,20 @@ jobs: # Don't run clippy on Windows unless it is using a Windows-specific # feature which wasn't checked on Linux. - # - # FIXME: clippy in rust 1.69.0 fails a clap macro expansion - # https://github.com/rust-lang/rust-clippy/issues/10421 - - if: (contains(matrix.features, 'windows') || runner.os != 'windows') && matrix.rust_version != 'stable' + - if: contains(matrix.features, 'windows') || runner.os != 'windows' run: cargo clippy --no-deps -p webauthn-authenticator-rs --all-targets ${{ matrix.features }} - run: cargo test -p webauthn-authenticator-rs ${{ matrix.features }} + # Some clap errors manifest as panics at runtime. Running tools with + # --help should be enough to find an issue. + - run: cargo run -p webauthn-authenticator-rs --example authenticate ${{ matrix.features }} -- --help + - if: contains(matrix.features, 'cable') + run: cargo run -p webauthn-authenticator-rs --example cable_domain ${{ matrix.features }} -- --help + - if: contains(matrix.features, 'cable') + run: cargo run -p webauthn-authenticator-rs --example cable_tunnel ${{ matrix.features }} -- --help + - run: cargo run -p webauthn-authenticator-rs --example softtoken ${{ matrix.features }} -- --help + docs: name: Documentation strategy: diff --git a/Cargo.toml b/Cargo.toml index 74d86018..43405ebf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ webauthn-rs-proto = { path = "./webauthn-rs-proto" } async-std = { version = "1.6", features = ["attributes"] } base64 = "0.21" -clap = { version = "^3.2", features = ["derive", "env"] } +clap = { version = "^4.2.4", features = ["derive", "env"] } compact_jwt = "0.2.3" futures = "^0.3.25" hex = "0.4.3" diff --git a/cable-tunnel-server/common/src/tls.rs b/cable-tunnel-server/common/src/tls.rs index dc1f55d0..46adada9 100644 --- a/cable-tunnel-server/common/src/tls.rs +++ b/cable-tunnel-server/common/src/tls.rs @@ -41,7 +41,7 @@ impl From for TlsConfigError { pub struct ServerTransportProtocol { /// Runs an unencrypted HTTP server, rather than HTTPS. This is not suitable /// for use with ordinary caBLE clients. - #[clap(long, conflicts_with_all(&["tls-public-key", "tls-private-key"]))] + #[clap(long, conflicts_with_all(&["tls_public_key", "tls_private_key"]))] insecure_http_server: bool, /// Path to the server's public key (certificate) in PEM format. @@ -106,7 +106,7 @@ impl ServerTransportProtocol { #[derive(Debug, Clone, Args)] pub struct BackendClientOptions { /// Uses unencrypted HTTP to connect to backend tasks, rather than HTTPS. - #[clap(long, conflicts_with_all(&["trusted-ca", "domain"]))] + #[clap(long, conflicts_with_all(&["trusted_ca", "domain"]))] insecure_http_backend: bool, /// Public key of the root CA to trust when connecting to a backend task, diff --git a/fido-key-manager/src/main.rs b/fido-key-manager/src/main.rs index 84ba0dd0..173d0685 100644 --- a/fido-key-manager/src/main.rs +++ b/fido-key-manager/src/main.rs @@ -69,8 +69,8 @@ pub struct RenameFingerprintOpt { #[derive(Debug, Args)] pub struct RemoveFingerprintOpt { - /// The template ID - #[clap(min_values(1), required(true))] + /// The template ID(s) to remove + #[clap(required = true)] pub id: Vec, } diff --git a/fido-mds-tool/src/main.rs b/fido-mds-tool/src/main.rs index 621952a6..f03b980a 100644 --- a/fido-mds-tool/src/main.rs +++ b/fido-mds-tool/src/main.rs @@ -26,7 +26,7 @@ pub struct CommonOpt { #[clap(short, long)] pub debug: bool, /// Path to the MDS file - #[clap(parse(from_os_str), short = 'p', long = "path")] + #[clap(short, long)] pub path: PathBuf, } diff --git a/webauthn-authenticator-rs/Cargo.toml b/webauthn-authenticator-rs/Cargo.toml index 86254c55..40b01528 100644 --- a/webauthn-authenticator-rs/Cargo.toml +++ b/webauthn-authenticator-rs/Cargo.toml @@ -93,3 +93,7 @@ openssl.workspace = true [[example]] name = "cable_domain" required-features = ["cable"] + +[[example]] +name = "cable_tunnel" +required-features = ["cable"] diff --git a/webauthn-authenticator-rs/examples/authenticate/main.rs b/webauthn-authenticator-rs/examples/authenticate.rs similarity index 100% rename from webauthn-authenticator-rs/examples/authenticate/main.rs rename to webauthn-authenticator-rs/examples/authenticate.rs diff --git a/webauthn-authenticator-rs/examples/cable_domain.rs b/webauthn-authenticator-rs/examples/cable_domain.rs index c716633b..9b7a0287 100644 --- a/webauthn-authenticator-rs/examples/cable_domain.rs +++ b/webauthn-authenticator-rs/examples/cable_domain.rs @@ -3,7 +3,7 @@ //! ```sh //! cargo run --example cable_domain --features cable -- --help //! ``` -use clap::{ArgGroup, CommandFactory, Parser, ValueHint}; +use clap::{error::ErrorKind, ArgGroup, CommandFactory, Parser, ValueHint}; use std::net::ToSocketAddrs; use webauthn_authenticator_rs::cable::get_domain; @@ -23,7 +23,7 @@ rather than an error." #[clap(group( ArgGroup::new("ids") .required(true) - .args(&["tunnel-server-ids", "all"]) + .args(&["tunnel_server_ids", "all"]) ))] pub struct CliParser { /// One or more tunnel server IDs. @@ -54,9 +54,7 @@ pub struct CliParser { /// Returns [None] on resolution failure or no results. fn resolver(hostname: &str) -> Option { (hostname, 443).to_socket_addrs().ok().and_then(|addrs| { - let mut o: String = addrs - .map(|addr| format!("{},", addr.ip().to_string())) - .collect(); + let mut o: String = addrs.map(|addr| format!("{},", addr.ip())).collect(); o.pop(); if o.is_empty() { @@ -97,7 +95,7 @@ fn main() { Some(d) => println!("{d}"), None => CliParser::command() .error( - clap::ErrorKind::ValueValidation, + ErrorKind::ValueValidation, format!("unknown tunnel server ID: {domain_id}"), ) .exit(), diff --git a/webauthn-authenticator-rs/examples/cable_tunnel/core.rs b/webauthn-authenticator-rs/examples/cable_tunnel.rs similarity index 97% rename from webauthn-authenticator-rs/examples/cable_tunnel/core.rs rename to webauthn-authenticator-rs/examples/cable_tunnel.rs index 0894f014..1dbd5503 100644 --- a/webauthn-authenticator-rs/examples/cable_tunnel/core.rs +++ b/webauthn-authenticator-rs/examples/cable_tunnel.rs @@ -1,3 +1,7 @@ +//! `cable_tunnel` shares a [Token] over a caBLE connection. +#[macro_use] +extern crate tracing; + use bluetooth_hci::{ host::{ uart::{CommandHeader, Hci as UartHci, Packet}, @@ -34,7 +38,7 @@ use webauthn_authenticator_rs::{ #[clap(group( ArgGroup::new("url") .required(true) - .args(&["cable-url", "qr-image"]) + .args(&["cable_url", "qr_image"]) ))] pub struct CliParser { /// Serial port where Bluetooth HCI controller is connected to. @@ -178,7 +182,9 @@ impl Advertiser for SerialHciAdvertiser { } #[tokio::main] -pub(super) async fn main() { +async fn main() { + let _ = tracing_subscriber::fmt::try_init(); + let opt = CliParser::parse(); let cable_url = if let Some(u) = opt.cable_url { u diff --git a/webauthn-authenticator-rs/examples/cable_tunnel/main.rs b/webauthn-authenticator-rs/examples/cable_tunnel/main.rs deleted file mode 100644 index ea06baa6..00000000 --- a/webauthn-authenticator-rs/examples/cable_tunnel/main.rs +++ /dev/null @@ -1,17 +0,0 @@ -//! `cable_tunnel` shares a [Token] over a caBLE connection. - -#[macro_use] -extern crate tracing; - -#[cfg(feature = "cable")] -mod core; - -fn main() { - let _ = tracing_subscriber::fmt::try_init(); - - #[cfg(feature = "cable")] - core::main(); - - #[cfg(not(feature = "cable"))] - error!("This example requires the feature \"cable\" to be enabled."); -} diff --git a/webauthn-authenticator-rs/examples/nfc_token_info/core.rs b/webauthn-authenticator-rs/examples/nfc_token_info/core.rs deleted file mode 100644 index 21a92298..00000000 --- a/webauthn-authenticator-rs/examples/nfc_token_info/core.rs +++ /dev/null @@ -1,28 +0,0 @@ -use webauthn_authenticator_rs::{ctap2::CtapAuthenticator, transport::*, ui::Cli}; - -async fn access_card(card: T) { - info!("Card detected ..."); - - let auth = CtapAuthenticator::new(card, &Cli {}).await; - - match auth { - Some(x) => { - info!("Using token: {:?}", x); - } - None => unimplemented!(), - } -} - -pub(crate) async fn event_loop() { - let mut reader = AnyTransport::new().await.unwrap(); - info!("Using reader: {:?}", reader); - - match reader.tokens() { - Ok(mut tokens) => { - while let Some(card) = tokens.pop() { - access_card(card).await; - } - } - Err(e) => panic!("Error: {e:?}"), - } -} diff --git a/webauthn-authenticator-rs/examples/nfc_token_info/core_bak.rs b/webauthn-authenticator-rs/examples/nfc_token_info/core_bak.rs deleted file mode 100644 index 590427d5..00000000 --- a/webauthn-authenticator-rs/examples/nfc_token_info/core_bak.rs +++ /dev/null @@ -1,122 +0,0 @@ -use pcsc::*; -use std::ffi::CStr; - -use webauthn_authenticator_rs::nfc::apdu::*; - -fn access_card(ctx: &Context, reader: &CStr) { - info!("Card detected ..."); - // Connect to the card. - let card = match ctx.connect(reader, ShareMode::Shared, Protocols::ANY) { - Ok(card) => card, - Err(Error::NoSmartcard) => { - info!("A smartcard is not present in the reader."); - return; - } - Err(err) => { - error!("Failed to connect to card: {}", err); - return; - } - }; - - // Select ctap2.1 via an APDU command. - debug!("Sending APDU: {:x?}", &APPLET_SELECT_CMD); - let mut rapdu_buf = [0; MAX_SHORT_BUFFER_SIZE]; - let rapdu = match card.transmit(&APPLET_SELECT_CMD, &mut rapdu_buf) { - Ok(rapdu) => rapdu, - Err(err) => { - error!("Failed to transmit APDU command to card: {}", err); - return; - } - }; - - if rapdu == &APPLET_U2F_V2 { - info!("Selected U2F_V2 applet"); - - let mut rapdu_buf = [0; MAX_SHORT_BUFFER_SIZE]; - debug!("Sending APDU: {:x?}", &AUTHENTICATOR_GET_INFO_APDU); - let rapdu = match card.transmit(&AUTHENTICATOR_GET_INFO_APDU, &mut rapdu_buf) { - Ok(rapdu) => rapdu, - Err(err) => { - error!("Failed to transmit APDU command to card: {}", err); - return; - } - }; - trace!("got raw APDU response: {:?}", rapdu); - - let agir = AuthenticatorGetInfoResponse::try_from(rapdu).unwrap(); - trace!("got response: {:?}", agir); - info!("versions: {:?}", agir.versions); - info!("extensions: {:?}", agir.extensions); - info!("aaguid: {:?}", agir.aaguid); - info!("options: {:?}", agir.options); - info!("max_msg_size: {:?}", agir.max_msg_size); - info!("pin_protocols: {:?}", agir.pin_protocols); - info!("max_cred_count_in_list: {:?}", agir.max_cred_count_in_list); - info!("max_cred_id_len: {:?}", agir.max_cred_id_len); - info!("transports: {:?}", agir.transports); - info!("algorithms: {:?}", agir.algorithms) - } else { - error!("UNKNOWN APDU response: {:x?}", rapdu); - } -} - -pub(crate) fn event_loop() { - // https://docs.rs/tracing-subscriber/latest/tracing_subscriber/fmt/index.html - let ctx = match Context::establish(Scope::User) { - Ok(ctx) => ctx, - Err(err) => { - error!("Failed to establish context: {}", err); - std::process::exit(1); - } - }; - - // List available readers. - let mut readers_buf = [0; 2048]; - let mut readers = match ctx.list_readers(&mut readers_buf) { - Ok(readers) => readers, - Err(err) => { - error!("Failed to list readers: {}", err); - std::process::exit(1); - } - }; - - // Use the first reader. - let reader = match readers.next() { - Some(reader) => reader, - None => { - info!("No readers are connected."); - return; - } - }; - info!("Using reader: {:?}", reader); - - let mut reader_states = vec![ - // I think this is for when a new reader is connected. - // ReaderState::new(PNP_NOTIFICATION(), State::UNAWARE), - ReaderState::new(reader, State::UNAWARE), - ]; - - loop { - for read_state in &mut reader_states { - read_state.sync_current_state(); - } - - if let Err(e) = ctx.get_status_change(None, &mut reader_states) { - error!("Failed to detect card: {:?}", e); - std::process::exit(1); - } else { - // Check every reader ... - for read_state in &reader_states { - trace!("reader_state: {:?}", read_state.event_state()); - let state = read_state.event_state(); - if state.contains(State::PRESENT) { - access_card(&ctx, reader); - } else if state.contains(State::EMPTY) { - info!("Card removed"); - } else { - warn!("Unknown state change -> {:?}", state); - } - } - } - } -} diff --git a/webauthn-authenticator-rs/examples/nfc_token_info/main.rs b/webauthn-authenticator-rs/examples/nfc_token_info/main.rs deleted file mode 100644 index ebf09f0c..00000000 --- a/webauthn-authenticator-rs/examples/nfc_token_info/main.rs +++ /dev/null @@ -1,10 +0,0 @@ -#[macro_use] -extern crate tracing; - -mod core; - -#[tokio::main] -async fn main() { - tracing_subscriber::fmt::init(); - core::event_loop().await; -} diff --git a/webauthn-authenticator-rs/examples/softtoken/main.rs b/webauthn-authenticator-rs/examples/softtoken.rs similarity index 100% rename from webauthn-authenticator-rs/examples/softtoken/main.rs rename to webauthn-authenticator-rs/examples/softtoken.rs