From ddc3ff98a2e5c9b64ab8e61c7153a4455523ac68 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Thu, 22 Aug 2024 22:53:49 +0800 Subject: [PATCH] feat: v1.9.12, add ssh-pub-key support --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd_sshpubkey.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 ++ 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 src/cmd_sshpubkey.rs diff --git a/Cargo.lock b/Cargo.lock index 64b5a9d..ac83e3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -406,7 +406,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.9.11" +version = "1.9.12" dependencies = [ "authenticator", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index 0d30e73..1974c64 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.9.11" +version = "1.9.12" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_sshpubkey.rs b/src/cmd_sshpubkey.rs new file mode 100644 index 0000000..29ced66 --- /dev/null +++ b/src/cmd_sshpubkey.rs @@ -0,0 +1,75 @@ +use base64::engine::general_purpose::STANDARD; +use base64::Engine; +use clap::{App, Arg, ArgMatches, SubCommand}; +use rust_util::util_clap::{Command, CommandError}; +use yubikey::piv::AlgorithmId; +use yubikey::{Key, YubiKey}; + +use crate::pivutil; +use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr}; +use crate::sshutil::SshVecWriter; + +pub struct CommandImpl; + +impl Command for CommandImpl { + fn name(&self) -> &str { "ssh-pub-key" } + + fn subcommand<'a>(&self) -> App<'a, 'a> { + SubCommand::with_name(self.name()).about("SSH public key subcommand") + .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) + } + + fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError { + let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); + let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); + let slot_id = pivutil::get_slot_id(slot)?; + + let mut algorithm_id_opt = None; + let mut ec_key_point = vec![]; + match Key::list(&mut yk) { + Err(e) => warning!("List keys failed: {}", e), + Ok(keys) => for k in &keys { + let slot_str = format!("{:x}", Into::::into(k.slot())); + if slot_equals(&slot_id, &slot_str) { + let cert = &k.certificate().cert.tbs_certificate; + let certificate = k.certificate(); + if let Ok(algorithm_id) = get_algorithm_id_by_certificate(certificate) { + match algorithm_id { + AlgorithmId::EccP256 | AlgorithmId::EccP384 => { + let public_key_bit_string = &cert.subject_public_key_info.subject_public_key; + ec_key_point.extend_from_slice(public_key_bit_string.raw_bytes()); + algorithm_id_opt = Some(algorithm_id); + } + _ => return simple_error!("Not P256/384 key: {}", algorithm_id.to_str()), + } + } + } + } + } + let algorithm_id = match algorithm_id_opt { + None => return simple_error!("Slot key not found!"), + Some(algorithm_id) => algorithm_id, + }; + + let ssh_algorithm = match algorithm_id { + AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 => panic!("Not supported."), + AlgorithmId::EccP256 => "nistp256", + AlgorithmId::EccP384 => "nistp384", + }; + + information!("SSH algorithm: {}", ssh_algorithm); + information!("ECDSA public key: {}", hex::encode(&ec_key_point)); + println!(); + + let mut ssh_pub_key = vec![]; + ssh_pub_key.write_string(&format!("ecdsa-sha2-{}", ssh_algorithm).as_bytes()); + let mut ecc_key_blob = vec![]; + ecc_key_blob.write_string(ssh_algorithm.as_bytes()); + ecc_key_blob.write_string(&ec_key_point); + ssh_pub_key.write_bytes(&ecc_key_blob); + + println!("ecdsa-sha2-{} {} Yubikey-PIV-{}", ssh_algorithm, STANDARD.encode(&ssh_pub_key), slot_id); + + Ok(None) + } +} diff --git a/src/main.rs b/src/main.rs index ead34b1..98afd09 100644 --- a/src/main.rs +++ b/src/main.rs @@ -42,6 +42,7 @@ mod cmd_challconfig; mod cmd_sshagent; mod cmd_sshparsesign; mod cmd_sshpivsign; +mod cmd_sshpubkey; mod cmd_pgpageaddress; mod cmd_signjwt; mod cmd_signfile; @@ -103,6 +104,7 @@ fn inner_main() -> CommandError { Box::new(cmd_sshagent::CommandImpl), Box::new(cmd_sshparsesign::CommandImpl), Box::new(cmd_sshpivsign::CommandImpl), + Box::new(cmd_sshpubkey::CommandImpl), Box::new(cmd_pgpageaddress::CommandImpl), Box::new(cmd_signjwt::CommandImpl), Box::new(cmd_signfile::CommandImpl),