From 0ad8c12c384ad81013a98c4a950306071936a281 Mon Sep 17 00:00:00 2001 From: Hatter Jiang Date: Sun, 14 May 2023 23:26:13 +0800 Subject: [PATCH] feat: v1.5.7, update piv --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd_challconfig.rs | 2 +- src/cmd_pivecsign.rs | 47 +++++++++++++++++++++++++++++++----------- src/cmd_pivmeta.rs | 23 +++++++-------------- src/cmd_pivrsasign.rs | 4 ++-- src/main.rs | 1 + src/pivutil.rs | 39 +++++++++++++++++++++++++++++++++++ 8 files changed, 88 insertions(+), 32 deletions(-) create mode 100644 src/pivutil.rs diff --git a/Cargo.lock b/Cargo.lock index e8f7f60..55dafe3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -342,7 +342,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.5.6" +version = "1.5.7" dependencies = [ "authenticator", "base64 0.13.1", diff --git a/Cargo.toml b/Cargo.toml index 0f8f14e..882fca4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.5.6" +version = "1.5.7" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_challconfig.rs b/src/cmd_challconfig.rs index 50ce2ec..61ed9a6 100644 --- a/src/cmd_challconfig.rs +++ b/src/cmd_challconfig.rs @@ -11,7 +11,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "chall-config" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("YubiKey challenge-response HMAC config") + SubCommand::with_name(self.name()).about("YubiKey challenge-response HMAC configuration") .arg(Arg::with_name("secret").short("s").long("secret").takes_value(true).help("Secret")) .arg(Arg::with_name("secret-hex").short("x").long("secret-hex").takes_value(true).help("Secret HEX")) .arg(Arg::with_name("button-press").long("button-press").help("Require button press")) diff --git a/src/cmd_pivecsign.rs b/src/cmd_pivecsign.rs index 3660730..7d239b6 100644 --- a/src/cmd_pivecsign.rs +++ b/src/cmd_pivecsign.rs @@ -1,13 +1,14 @@ use std::collections::BTreeMap; -use std::str::FromStr; use clap::{App, Arg, ArgMatches, SubCommand}; use rust_util::util_clap::{Command, CommandError}; use rust_util::util_msg; use x509_parser::nom::AsBytes; +use yubikey::piv::{AlgorithmId, ManagementAlgorithmId, metadata, sign_data}; use yubikey::YubiKey; -use yubikey::piv::{AlgorithmId, RetiredSlotId, sign_data, SlotId}; + use crate::digest::sha256; +use crate::pivutil; pub struct CommandImpl; @@ -15,10 +16,11 @@ impl Command for CommandImpl { fn name(&self) -> &str { "piv-ecsign" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("PIV EC Sign subcommand") + SubCommand::with_name(self.name()).about("PIV EC Sign(with SHA256) subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user pin")) - .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ...")) + .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) .arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash")) + .arg(Arg::with_name("algorithm").short("a").long("algorithm").takes_value(true).help("Algorithm, p256 or p384")) .arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input")) .arg(Arg::with_name("json").long("json").help("JSON output")) } @@ -31,33 +33,54 @@ impl Command for CommandImpl { let pin_opt = sub_arg_matches.value_of("pin"); - let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ..."); + let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e"); let hash_hex = if let Some(input) = sub_arg_matches.value_of("input") { hex::encode(sha256(input)) } else { opt_value_result!(sub_arg_matches.value_of("hash-hex"), "--hash-hex must assigned").to_string() }; + let (algorithm, algorithm_str) = match sub_arg_matches.value_of("algorithm") { + None | Some("p256") => (AlgorithmId::EccP256, "ecdsa_p256_with_sha256"), + Some("p384") => (AlgorithmId::EccP384, "ecdsa_p384_with_sha256"), + Some(unknown_algorithm) => return simple_error!("Unknown algorithm {}, e.g. p256 or p384", unknown_algorithm), + }; let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); - let retired_slot_id = opt_result!(RetiredSlotId::from_str(slot), "Slot not found: {}"); - debugging!("Slot id: {}", retired_slot_id); - let slot_id = SlotId::Retired(retired_slot_id); + let slot_id = pivutil::get_slot_id(slot)?; if let Some(pin) = pin_opt { opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); } - let hash_bytes = opt_result!(hex::decode(&hash_hex), "Parse epk failed: {}"); + let hash_bytes = opt_result!(hex::decode(&hash_hex), "Parse hash in hex failed: {}"); - let signed_data = opt_result!(sign_data(&mut yk, &hash_bytes, AlgorithmId::EccP256, slot_id), "Sign piv failed: {}"); + if let Ok(slot_metadata) = metadata(&mut yk, slot_id) { + match slot_metadata.algorithm { + ManagementAlgorithmId::PinPuk | ManagementAlgorithmId::ThreeDes => { + return simple_error!("Slot not supports PIV sign: {:?}", slot_metadata.algorithm); + } + ManagementAlgorithmId::Asymmetric(slot_algorithm) => { + if AlgorithmId::Rsa1024 == slot_algorithm || AlgorithmId::Rsa2048 == algorithm { + return simple_error!("Slot supports PIV RSA sign: {:?}, but requires ECDSA", slot_metadata.algorithm); + } + if slot_algorithm != algorithm { + return simple_error!("Slot supported PIV sign not match: {:?}", slot_metadata.algorithm); + } + } + } + } + + let signed_data = opt_result!(sign_data(&mut yk, &hash_bytes, algorithm, slot_id), "Sign PIV failed: {}"); if json_output { - json.insert("slot", slot.to_string()); + json.insert("slot", slot_id.to_string()); + json.insert("algorithm", algorithm_str.to_string()); json.insert("hash_hex", hex::encode(&hash_bytes)); json.insert("signed_data_hex", hex::encode(&signed_data.as_bytes())); json.insert("signed_data_base64", base64::encode(&signed_data.as_bytes())); } else { - information!("Slot: {}", slot); + information!("Slot: {:?}", slot_id); + information!("Algorithm: {}", algorithm_str); information!("Hash hex: {}", hex::encode(&hash_bytes)); information!("Signed data base64: {}", base64::encode(&signed_data.as_bytes())); information!("Signed data hex: {}", hex::encode(&signed_data.as_bytes())); diff --git a/src/cmd_pivmeta.rs b/src/cmd_pivmeta.rs index df5f0b8..5889614 100644 --- a/src/cmd_pivmeta.rs +++ b/src/cmd_pivmeta.rs @@ -1,5 +1,4 @@ use std::collections::BTreeMap; -use std::str::FromStr; use clap::{App, Arg, ArgMatches, SubCommand}; use hex::ToHex; @@ -10,7 +9,9 @@ use x509::SubjectPublicKeyInfo; use x509_parser::parse_x509_certificate; use yubikey::{Key, PinPolicy, TouchPolicy, YubiKey}; use yubikey::certificate::PublicKeyInfo; -use yubikey::piv::{AlgorithmId, ManagementAlgorithmId, metadata, Origin, RetiredSlotId, SlotId}; +use yubikey::piv::{AlgorithmId, ManagementAlgorithmId, metadata, Origin}; + +use crate::pivutil; use crate::pkiutil::bytes_to_pem; pub struct CommandImpl; @@ -20,7 +21,7 @@ impl Command for CommandImpl { fn subcommand<'a>(&self) -> App<'a, 'a> { SubCommand::with_name(self.name()).about("PIV meta subcommand") - .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ...")) + .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) .arg(Arg::with_name("json").long("json").help("JSON output")) } @@ -30,21 +31,11 @@ impl Command for CommandImpl { let mut json = BTreeMap::<&'_ str, String>::new(); - let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ..."); + 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 = match slot { - "9a" => SlotId::Authentication, - "9c" => SlotId::Signature, - "9d" => SlotId::KeyManagement, - "9e" => SlotId::CardAuthentication, - _ => { - let retired_slot_id = opt_result!(RetiredSlotId::from_str(slot), "Slot not found: {}"); - debugging!("Slot id: {}", retired_slot_id); - SlotId::Retired(retired_slot_id) - } - }; + let slot_id = pivutil::get_slot_id(slot)?; json.insert("slot", slot.to_string()); if let Ok(meta) = metadata(&mut yk, slot_id) { @@ -141,10 +132,12 @@ impl Command for CommandImpl { json.insert("issuer", k.certificate().issuer().to_string()); json.insert("serial", k.certificate().serial().to_string()); json.insert("certificate_hex", k.certificate().encode_hex::()); + json.insert("certificate_pem", bytes_to_pem("CERTIFICATE", k.certificate().as_ref())); let x509_certificate = parse_x509_certificate(k.certificate().as_ref()).unwrap().1; let public_key_bytes = x509_certificate.public_key().raw; json.insert("public_key_hex", hex::encode(public_key_bytes)); + json.insert("public_key_pem", bytes_to_pem("PUBLIC KEY", public_key_bytes)); if !json_output { information!("Subject: {}", k.certificate().subject()); diff --git a/src/cmd_pivrsasign.rs b/src/cmd_pivrsasign.rs index 96e7a7a..ec5fe2c 100644 --- a/src/cmd_pivrsasign.rs +++ b/src/cmd_pivrsasign.rs @@ -13,7 +13,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "piv-sign" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("PIV Sign subcommand") + SubCommand::with_name(self.name()).about("PIV RSA Sign(with SHA256) subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").help("OpenPGP card user pin")) .arg(Arg::with_name("sha256").short("2").long("sha256").takes_value(true).help("Digest SHA256 HEX")) .arg(Arg::with_name("json").long("json").help("JSON output")) @@ -47,7 +47,7 @@ impl Command for CommandImpl { hash_with_oid.extend_from_slice(&sha256_der_prefix); hash_with_oid.extend_from_slice(&hash); let hash_padding = pkcs1_padding_for_sign(&hash_with_oid, 2048).unwrap(); - rust_util::util_msg::when(MessageType::DEBUG, || { + util_msg::when(MessageType::DEBUG, || { debugging!("Hash: {}", hex::encode(&hash)); debugging!("Hash with OID: {}", hex::encode(&hash_with_oid)); debugging!("PKCS1 padding: {}", hex::encode(&hash_padding)); diff --git a/src/main.rs b/src/main.rs index cd28982..40d0b9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,7 @@ use rust_util::util_clap::{Command, CommandError}; mod sshutil; mod fido; mod digest; +mod pivutil; mod rsautil; mod pkiutil; mod pgpcardutil; diff --git a/src/pivutil.rs b/src/pivutil.rs new file mode 100644 index 0000000..3611f03 --- /dev/null +++ b/src/pivutil.rs @@ -0,0 +1,39 @@ +use std::str::FromStr; + +use rust_util::XResult; +use yubikey::piv::RetiredSlotId; +use yubikey::piv::SlotId; + +pub fn get_slot_id(slot: &str) -> XResult { + Ok(match slot { + "9a" => SlotId::Authentication, + "9c" => SlotId::Signature, + "9d" => SlotId::KeyManagement, + "9e" => SlotId::CardAuthentication, + "r1" | "R1" => SlotId::Retired(RetiredSlotId::R1), + "r2" | "R2" => SlotId::Retired(RetiredSlotId::R2), + "r3" | "R3" => SlotId::Retired(RetiredSlotId::R3), + "r4" | "R4" => SlotId::Retired(RetiredSlotId::R4), + "r5" | "R5" => SlotId::Retired(RetiredSlotId::R5), + "r6" | "R6" => SlotId::Retired(RetiredSlotId::R6), + "r7" | "R7" => SlotId::Retired(RetiredSlotId::R7), + "r8" | "R8" => SlotId::Retired(RetiredSlotId::R8), + "r9" | "R9" => SlotId::Retired(RetiredSlotId::R9), + "r10" | "R10" => SlotId::Retired(RetiredSlotId::R10), + "r11" | "R11" => SlotId::Retired(RetiredSlotId::R11), + "r12" | "R12" => SlotId::Retired(RetiredSlotId::R12), + "r13" | "R13" => SlotId::Retired(RetiredSlotId::R13), + "r14" | "R14" => SlotId::Retired(RetiredSlotId::R14), + "r15" | "R15" => SlotId::Retired(RetiredSlotId::R15), + "r16" | "R16" => SlotId::Retired(RetiredSlotId::R16), + "r17" | "R17" => SlotId::Retired(RetiredSlotId::R17), + "r18" | "R18" => SlotId::Retired(RetiredSlotId::R18), + "r19" | "R19" => SlotId::Retired(RetiredSlotId::R19), + "r20" | "R20" => SlotId::Retired(RetiredSlotId::R20), + _ => { + let retired_slot_id = opt_result!(RetiredSlotId::from_str(slot), "Slot not found: {}"); + debugging!("Retried slot id: {}", retired_slot_id); + SlotId::Retired(retired_slot_id) + } + }) +} \ No newline at end of file