diff --git a/Cargo.lock b/Cargo.lock index ea53011..975a2b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -491,6 +491,7 @@ dependencies = [ "anyhow", "clap", "ctap-hid-fido2", + "rpassword", "ssh-encoding", "ssh-key", ] @@ -935,6 +936,17 @@ 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" @@ -958,6 +970,16 @@ 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" diff --git a/Cargo.toml b/Cargo.toml index 763ffbf..cfb2c1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,6 @@ edition = "2021" anyhow = "1.0.72" clap = { version = "4.3.21", features = ["derive"] } ctap-hid-fido2 = "3.5.0" +rpassword = "7.2.0" ssh-encoding = { version = "0.2.0" } ssh-key = { version = "0.6.0", features = ["ed25519"] } diff --git a/src/main.rs b/src/main.rs index 6817f18..a21da45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ use std::{ fs::Permissions, os::unix::prelude::PermissionsExt, - path::{Path, PathBuf}, fmt::Display, + path::{Path, PathBuf}, fmt::Display, io::Write, }; use anyhow::anyhow; use clap::{Parser, ValueEnum}; use ctap_hid_fido2::{ - fidokey::{CredentialSupportedKeyType, MakeCredentialArgsBuilder}, + fidokey::{CredentialSupportedKeyType, MakeCredentialArgsBuilder, CredentialExtension, credential_management::credential_management_params::CredentialProtectionPolicy}, verifier, FidoKeyHidFactory, LibCfg, }; use ssh_encoding::{Decode, Encode, LineEnding}; @@ -23,10 +23,15 @@ struct Args { /// Overwrite existing key files #[arg(short = 'f', long)] force: bool, + /// Type of key to generate #[arg(short = 't', long = "type")] key_type: KeyTypeArg, + /// Write attestation info for later verification #[arg(long = "write-attestation")] write_attestation: bool, + /// Require user verification on use (PIN/biometrics) + #[arg(long = "user-verify")] + user_verify: bool, /// Name of key to generate key_name: String, } @@ -71,14 +76,32 @@ fn main() -> anyhow::Result<()> { println!("Generating public/private {} key pair", args.key_type); + let challenge = verifier::create_challenge(); - let make_credential_args = MakeCredentialArgsBuilder::new("ssh:", &challenge) - .without_pin_and_uv() - .key_type(args.key_type.into()) - .build(); let mut libcfg = LibCfg::init(); libcfg.keep_alive_msg = "Touch the authenticator now.".into(); let device = FidoKeyHidFactory::create(&libcfg)?; + + let device_has_pin = device.get_info()?.options.contains(&("clientPin".into(), true)); + let pin = if device_has_pin { + rpassword::prompt_password("Enter FIDO2 PIN:")? + } else { + String::new() + }; + let make_credential_args = if device_has_pin { + MakeCredentialArgsBuilder::new("ssh:", &challenge) + .pin(&pin) + .extensions(&[CredentialExtension::CredProtect(Some(CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList))]) + .key_type(args.key_type.into()) + .build() + } else { + MakeCredentialArgsBuilder::new("ssh:", &challenge) + .without_pin_and_uv() + .extensions(&[CredentialExtension::CredProtect(Some(CredentialProtectionPolicy::UserVerificationOptionalWithCredentialIdList))]) + .key_type(args.key_type.into()) + .build() + }; + let attestation = device.make_credential_with_args(&make_credential_args)?; let verify_result = verifier::verify_attestation("ssh:", &challenge, &attestation); if !verify_result.is_success {