diff --git a/Cargo.lock b/Cargo.lock index 7639927..36c2c50 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,56 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys", +] + [[package]] name = "apdu-core" version = "0.4.0" @@ -44,6 +94,61 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ced95c6f4a675af3da73304b9ac4ed991640c36374e4b46795c49e17cf1ed" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "cli" +version = "0.0.0" +dependencies = [ + "clap", + "pcsc", + "ykoath2", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -102,6 +207,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hmac" version = "0.12.1" @@ -111,6 +222,12 @@ dependencies = [ "digest", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "iso7816-tlv" version = "0.4.4" @@ -132,6 +249,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + [[package]] name = "ouroboros" version = "0.18.5" @@ -149,7 +272,7 @@ version = "0.18.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro2", "proc-macro2-diagnostics", "quote", @@ -280,6 +403,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -315,6 +444,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.5" @@ -330,6 +465,15 @@ dependencies = [ "wit-bindgen-rt", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index b92115a..8d157c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,23 +1,10 @@ -[dependencies] -apdu-core = "0.4.0" -getrandom = "0.3.1" -hmac = "0.12.1" -iso7816-tlv = "0.4.4" -ouroboros = "0.18.5" -pbkdf2 = {version = "0.12.2", features = ["sha1"]} -pcsc = "2.9.0" -regex = "1.11.1" -sha1 = "0.10.6" -sha2 = "0.10.8" +[workspace] +resolver = "2" -[[example]] -name = "example" -path = "./src/example.rs" +members = [ + "lib", + "cli", +] -[package] -name = "ykoath2" -version = "0.1.0" +[workspace.package] edition = "2021" -authors = ["Grimmauld "] -description = "experiments with smartcards" -license-file = "LICENSE" diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..631dfd9 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "cli" +edition = "2021" + +[dependencies] +clap = { version = "4.5", features = [ "cargo", "derive" ] } +pcsc = "2.9.0" +ykoath2 = {path = "../lib"} \ No newline at end of file diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000..d834f78 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: BSD-3-Clause + +use clap::{Args, Parser, Subcommand}; +use ykoath2::{ + constants::OathType, oath_credential::OathCredential, oath_credential_id::CredentialIDData, + OathSession, +}; +// use clap::Parser; + +#[derive(Subcommand)] +enum Commands { + #[command(name = "store", about = "Store a credential")] + Store { + #[arg(help = "Credential name")] + name: String, + #[arg(help = "Credential type: Time-based or counter-based")] + oath_type: String, + #[arg(help = "Credential issuer")] + issuer: Option, + #[arg(help = "Credential refresh period if it is time-based")] + period: Option, + }, + + #[command(name = "tokens", about = "List all credentials for a device")] + Tokens, + + #[command(name = "list", about = "List all connected devices")] + List, +} + +#[derive(Parser)] +#[clap(version, about, long_about = None)] +struct Cli { + #[command(subcommand)] + command: Commands, + #[command(flatten)] + args: Arguments, +} + +#[derive(Args, Debug)] +struct Arguments { + #[arg(name = "device", short, long, global = true, help = "Yubikey device")] + device: Option, +} + +fn main() { + let cli = Cli::parse(); + // Create a HID API context for detecting devices + let context = pcsc::Context::establish(pcsc::Scope::User).unwrap(); + let mut readers_buf = [0; 2048]; + let devices = context + .list_readers(&mut readers_buf) + .unwrap() + .into_iter() + .map(|r| r.to_str().unwrap()) + .collect::>(); + + // Show message if no YubiKey(s) + if devices.is_empty() { + println!("No yubikeys detected"); + std::process::exit(0); + } + + match cli.command { + Commands::Tokens => { + let Some(selected_device) = cli.args.device else { + println!("A device is required to store a credential."); + std::process::exit(1); + }; + if devices.iter().find(|d| **d == selected_device).is_none() { + println!("{selected_device} was not found."); + std::process::exit(1); + } + let session = OathSession::new(&selected_device).unwrap(); + for code in session.list_oath_codes().unwrap() { + let oath_type = if code.oath_type() == OathType::Hotp { + "hotp" + } else { + "totp" + }; + println!( + "Name: {}, Issuer: {}, Type: {}", + code.name(), + code.issuer().unwrap_or_default(), + oath_type + ); + } + } + Commands::Store { + name, + oath_type, + issuer, + period, + } => { + let Some(selected_device) = cli.args.device else { + println!("A device is required to store a credential."); + std::process::exit(1); + }; + if devices.iter().find(|d| **d == selected_device).is_none() { + println!("{selected_device} was not found."); + std::process::exit(1); + } + let session = OathSession::new(&selected_device).unwrap(); + session + .put_credential( + OathCredential::new( + &selected_device, + CredentialIDData::new( + &name, + ykoath2::constants::OathType::Totp, + issuer.as_deref(), + None, + ), + false, + ), + b"some secret", + ykoath2::constants::HashAlgo::Sha256, + 6, + None, + ) + .unwrap(); + } + Commands::List => { + // Print device info for all the YubiKeys we detected + for device in devices { + let session = OathSession::new(device).unwrap(); + println!("Device: {device}."); + println!( + "Version: {:#?}.", + session + .version() + .iter() + .map(|v| v.to_string()) + .collect::>() + .join(".") + ); + } + } + } +} diff --git a/lib/Cargo.toml b/lib/Cargo.toml new file mode 100644 index 0000000..b92115a --- /dev/null +++ b/lib/Cargo.toml @@ -0,0 +1,23 @@ +[dependencies] +apdu-core = "0.4.0" +getrandom = "0.3.1" +hmac = "0.12.1" +iso7816-tlv = "0.4.4" +ouroboros = "0.18.5" +pbkdf2 = {version = "0.12.2", features = ["sha1"]} +pcsc = "2.9.0" +regex = "1.11.1" +sha1 = "0.10.6" +sha2 = "0.10.8" + +[[example]] +name = "example" +path = "./src/example.rs" + +[package] +name = "ykoath2" +version = "0.1.0" +edition = "2021" +authors = ["Grimmauld "] +description = "experiments with smartcards" +license-file = "LICENSE" diff --git a/src/constants.rs b/lib/src/constants.rs similarity index 100% rename from src/constants.rs rename to lib/src/constants.rs diff --git a/src/example.rs b/lib/src/example.rs similarity index 100% rename from src/example.rs rename to lib/src/example.rs diff --git a/src/lib.rs b/lib/src/lib.rs similarity index 100% rename from src/lib.rs rename to lib/src/lib.rs diff --git a/src/oath_credential.rs b/lib/src/oath_credential.rs similarity index 100% rename from src/oath_credential.rs rename to lib/src/oath_credential.rs diff --git a/src/oath_credential_id.rs b/lib/src/oath_credential_id.rs similarity index 93% rename from src/oath_credential_id.rs rename to lib/src/oath_credential_id.rs index 0ce06f9..21a78fc 100644 --- a/src/oath_credential_id.rs +++ b/lib/src/oath_credential_id.rs @@ -38,6 +38,20 @@ impl Display for CredentialIDData { } impl CredentialIDData { + pub fn new( + name: &str, + oath_type: OathType, + issuer: Option<&str>, + period: Option, + ) -> Self { + Self { + name: name.to_owned(), + oath_type, + issuer: issuer.map(ToOwned::to_owned), + period, + } + } + /// reads id data from tlv data /// `id_bytes` refers to the byte buffer containing issuer, name and period /// `oath_type_tag` refers to the tlv tag containing the oath type information diff --git a/src/refreshable_oath_credential.rs b/lib/src/refreshable_oath_credential.rs similarity index 100% rename from src/refreshable_oath_credential.rs rename to lib/src/refreshable_oath_credential.rs diff --git a/src/transaction.rs b/lib/src/transaction.rs similarity index 100% rename from src/transaction.rs rename to lib/src/transaction.rs