diff --git a/Cargo.lock b/Cargo.lock index f3bf5ba..bbfec9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -49,7 +49,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -59,7 +59,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c677ab05e09154296dd37acecd46420c17b9713e8366facafa8fc0885167cf4c" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -131,6 +131,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.4.0" @@ -248,6 +254,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + [[package]] name = "const-oid" version = "0.9.5" @@ -308,6 +327,16 @@ dependencies = [ "x509-parser", ] +[[package]] +name = "ctrlc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a011bbe2c35ce9c1f143b7af6f94f29a167beb4cd1d29e6740ce836f723120e" +dependencies = [ + "nix", + "windows-sys 0.48.0", +] + [[package]] name = "curve25519-dalek" version = "4.0.0" @@ -371,6 +400,18 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" +[[package]] +name = "dialoguer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59c6f2989294b9a498d3ad5491a79c6deb604617378e1cdc4bfc1c1361fe2f87" +dependencies = [ + "console", + "shell-words", + "tempfile", + "zeroize", +] + [[package]] name = "digest" version = "0.10.7" @@ -447,6 +488,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "errno" version = "0.3.2" @@ -455,7 +502,7 @@ checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" dependencies = [ "errno-dragonfly", "libc", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -468,6 +515,12 @@ dependencies = [ "libc", ] +[[package]] +name = "fastrand" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" + [[package]] name = "ff" version = "0.13.0" @@ -489,12 +542,15 @@ name = "fido_ssh_maker" version = "0.1.0" dependencies = [ "anyhow", - "bitflags", + "bitflags 2.4.0", "clap", "ctap-hid-fido2", - "rpassword", + "ctrlc", + "dialoguer", + "gethostname", "ssh-encoding", "ssh-key", + "whoami", ] [[package]] @@ -508,6 +564,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.1", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -593,7 +659,7 @@ checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -656,6 +722,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "nix" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "static_assertions", +] + [[package]] name = "nom" version = "7.1.3" @@ -912,6 +990,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -937,17 +1024,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rpassword" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322" -dependencies = [ - "libc", - "rtoolbox", - "winapi", -] - [[package]] name = "rsa" version = "0.9.2" @@ -971,16 +1047,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rtoolbox" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "rustc_version" version = "0.4.0" @@ -1005,11 +1071,11 @@ version = "0.38.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" dependencies = [ - "bitflags", + "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -1065,6 +1131,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shell-words" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" + [[package]] name = "signature" version = "2.1.0" @@ -1138,6 +1210,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" @@ -1203,6 +1281,19 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "thiserror" version = "1.0.44" @@ -1363,6 +1454,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1385,13 +1486,37 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -1400,51 +1525,93 @@ version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index a28f57d..43cc08f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ anyhow = "1.0.72" bitflags = "2.4.0" clap = { version = "4.3.21", features = ["derive"] } ctap-hid-fido2 = "3.5.0" -rpassword = "7.2.0" +ctrlc = "3.4.0" +dialoguer = "0.10.4" +gethostname = "0.4.3" ssh-encoding = { version = "0.2.0" } ssh-key = { version = "0.6.0", features = ["ed25519"] } +whoami = "1.4.1" diff --git a/src/main.rs b/src/main.rs index a917c75..f036819 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ use std::{ + fmt::Display, fs::Permissions, + io::Write, os::unix::prelude::PermissionsExt, - path::{Path, PathBuf}, fmt::Display, io::Write, + path::{Path, PathBuf}, + process, }; use anyhow::anyhow; @@ -9,8 +12,10 @@ use bitflags::bitflags; use clap::{Parser, ValueEnum}; use ctap_hid_fido2::{ fidokey::{CredentialSupportedKeyType, MakeCredentialArgsBuilder}, - verifier, LibCfg, get_fidokey_devices, FidoKeyHid, + get_fidokey_devices, verifier, FidoKeyHid, LibCfg, }; +use dialoguer::{console, Confirm, Password, Select}; +use gethostname::gethostname; use ssh_encoding::{Decode, Encode, LineEnding}; use ssh_key::{private, PrivateKey}; @@ -28,12 +33,9 @@ bitflags! { #[derive(Parser, Debug)] #[command(author, version, about, long_about = None, arg_required_else_help = true)] struct Args { - /// SSH key comment - #[arg(short = 'c', long, default_value = "")] - comment: String, - /// Overwrite existing key files - #[arg(short = 'f', long)] - force: bool, + /// SSH key comment (default USER@HOSTNAME) + #[arg(short = 'c', long)] + comment: Option, /// Type of key to generate #[arg(short = 't', long = "type")] key_type: KeyTypeArg, @@ -81,44 +83,43 @@ fn main() -> anyhow::Result<()> { let attest_info_path = PathBuf::from(format!("{}_attest.bin", &args.key_name)); let attest_challenge_path = PathBuf::from(format!("{}_attest_chall.bin", &args.key_name)); - if !args.force && (privkey_path.exists() || pubkey_path.exists() || args.write_attestation && (attest_info_path.exists() || attest_challenge_path.exists())) { - return Err(anyhow!("Key files exist, use -f to overwrite.")); - } + ctrlc::set_handler(move || { + let _ = console::Term::stdout().show_cursor(); + println!(); + process::exit(0); + })?; println!("Generating public/private {} key pair", args.key_type); + if privkey_path.exists() + || pubkey_path.exists() + || (args.write_attestation && (attest_info_path.exists() || attest_challenge_path.exists())) + { + if !Confirm::new() + .with_prompt("Key files exist, overwrite?") + .default(false) + .interact()? + { + return Err(anyhow!("Generation aborted")); + } + } let challenge = verifier::create_challenge(); - - let devices = get_fidokey_devices(); + let devices = get_fidokey_devices(); let device = if devices.len() > 1 { - println!("Availible devices:"); - for (i, device) in devices.iter().enumerate() { - println!("\t{}: {}", i + 1, device.product_string); - } - let mut buf = String::new(); - loop { - print!("Device number: "); - std::io::stdout().flush()?; - buf.clear(); - std::io::stdin().read_line(&mut buf)?; - if let Ok(num) = buf.trim().parse::() { - if num == 0 { - println!("Invalid number"); - continue; - } - if let Some(device) = devices.get(num - 1) { - break device; - } else { - println!("Invalid number"); - continue; - } - } else { - println!("Invalid number"); - continue; - } - } + let idx = Select::new() + .with_prompt("Select FIDO2 key") + .items( + devices + .iter() + .map(|dev| format!("{}", dev.product_string)) + .collect::>() + .as_slice(), + ) + .default(0) + .interact()?; + &devices[idx] } else { &devices[0] }; @@ -126,23 +127,28 @@ fn main() -> anyhow::Result<()> { let mut libcfg = LibCfg::init(); libcfg.keep_alive_msg = "Touch the authenticator now.".into(); let device = FidoKeyHid::new(&[device.param.clone()], &libcfg)?; - let device_has_pin = device.get_info().map_or(false, |info| info.options.contains(&("clientPin".into(), true))); + let device_has_pin = device.get_info().map_or(false, |info| { + info.options.contains(&("clientPin".into(), true)) + }); let pin = if device_has_pin { - rpassword::prompt_password("Enter FIDO2 PIN: ")? + Password::new() + .with_prompt("FIDO2 PIN") + .report(false) + .interact()? } else { String::new() }; let make_credential_args = if device_has_pin { MakeCredentialArgsBuilder::new("ssh:", &challenge) - .pin(&pin) - .key_type(args.key_type.into()) - .build() + .pin(&pin) + .key_type(args.key_type.into()) + .build() } else { MakeCredentialArgsBuilder::new("ssh:", &challenge) - .without_pin_and_uv() - .key_type(args.key_type.into()) - .build() + .without_pin_and_uv() + .key_type(args.key_type.into()) + .build() }; let attestation = device.make_credential_with_args(&make_credential_args)?; @@ -165,14 +171,23 @@ fn main() -> anyhow::Result<()> { privkey_bytes.push(flags.bits()); verify_result.credential_id.encode(&mut privkey_bytes)?; "".encode(&mut privkey_bytes)?; + let comment = if let Some(comment) = args.comment { + comment + } else { + format!( + "{}@{}", + whoami::username(), + gethostname().to_string_lossy().into_owned() + ) + }; let privkey = match args.key_type { KeyTypeArg::EcdsaSk => PrivateKey::new( private::SkEcdsaSha2NistP256::decode(&mut privkey_bytes.as_slice())?.into(), - args.comment, + comment, )?, KeyTypeArg::Ed25519Sk => PrivateKey::new( private::SkEd25519::decode(&mut privkey_bytes.as_slice())?.into(), - args.comment, + comment, )?, }; @@ -184,17 +199,28 @@ fn main() -> anyhow::Result<()> { 0u32.encode(&mut ssh_attest)?; "".encode(&mut ssh_attest)?; - std::fs::write(&privkey_path, &*privkey.to_openssh(LineEnding::default())?)?; std::fs::set_permissions(&privkey_path, Permissions::from_mode(0o600))?; - println!("Your identification has been saved in {}", privkey_path.to_string_lossy()); + println!( + "Your identification has been saved in {}", + privkey_path.to_string_lossy() + ); std::fs::write(&pubkey_path, privkey.public_key().to_openssh()?)?; - println!("Your public key has been saved in {}", pubkey_path.to_string_lossy()); + println!( + "Your public key has been saved in {}", + pubkey_path.to_string_lossy() + ); if args.write_attestation { std::fs::write(&attest_info_path, &ssh_attest)?; - println!("Your FIDO attestation certificate has been saved in {}", attest_info_path.to_string_lossy()); + println!( + "Your FIDO attestation certificate has been saved in {}", + attest_info_path.to_string_lossy() + ); std::fs::write(&attest_challenge_path, &challenge)?; - println!("Your FIDO attestation challenge has been saved in {}", attest_challenge_path.to_string_lossy()); + println!( + "Your FIDO attestation challenge has been saved in {}", + attest_challenge_path.to_string_lossy() + ); } Ok(())