Files
card-cli/src/cmd_piv_meta.rs
2025-03-29 00:04:25 +08:00

142 lines
6.7 KiB
Rust

use std::collections::BTreeMap;
use clap::{App, ArgMatches, SubCommand};
use p256::pkcs8::der::Encode;
use rust_util::util_clap::{Command, CommandError};
use rust_util::util_msg;
use rust_util::util_msg::MessageType;
use x509_parser::parse_x509_certificate;
use yubikey::{Key, YubiKey};
use yubikey::piv::{AlgorithmId, metadata};
use crate::{cmdutil, pivutil, util};
use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
use crate::pkiutil::bytes_to_pem;
use crate::sshutil::SshVecWriter;
use crate::util::base64_encode;
pub struct CommandImpl;
impl Command for CommandImpl {
fn name(&self) -> &str { "piv-meta" }
fn subcommand<'a>(&self) -> App<'a, 'a> {
SubCommand::with_name(self.name()).about("PIV meta subcommand")
.arg(cmdutil::build_slot_arg())
.arg(cmdutil::build_json_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
let json_output = cmdutil::check_json_output(sub_arg_matches);
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 ... 95, 9a, 9c, 9d, 9e");
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let slot_id = pivutil::get_slot_id(slot)?;
json.insert("slot", pivutil::to_slot_hex(&slot_id));
if let Ok(meta) = metadata(&mut yk, slot_id) {
debugging!("PIV meta: {:?}", meta);
let algorithm_str = meta.algorithm.to_str();
if json_output {
json.insert("algorithm", algorithm_str.to_string());
} else {
information!("Algorithm: {}", algorithm_str);
}
if let Some((pin_policy, touch_policy)) = meta.policy {
let pin_policy_str = pin_policy.to_str();
let touch_policy_str = touch_policy.to_str();
if json_output {
json.insert("pin_policy", pin_policy_str.to_string());
json.insert("touch_policy", touch_policy_str.to_string());
} else {
information!("PIN policy: {}", pin_policy_str);
information!("Touch policy: {}", touch_policy_str);
}
}
let origin_str = meta.origin.to_str();
if json_output {
json.insert("origin", origin_str.to_string());
} else {
information!("Origin: {}", origin_str);
}
} else {
warning!("Get slot: {} meta data failed", slot);
}
match Key::list(&mut yk) {
Err(e) => warning!("List keys failed: {}", e),
Ok(keys) => for k in &keys {
let cert = &k.certificate().cert.tbs_certificate;
let slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
if slot_equals(&slot_id, &slot_str) {
if let Ok(algorithm_id) = get_algorithm_id_by_certificate(k.certificate()) {
let algorithm_str = algorithm_id.to_str();
json.insert("algorithm", algorithm_str.to_string());
let public_key_bit_string = &cert.subject_public_key_info.subject_public_key;
match algorithm_id {
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
let ec_bit_len = iff!(matches!(algorithm_id, AlgorithmId::EccP256), 256, 384);
let pk_point_hex = public_key_bit_string.raw_bytes();
json.insert("pk_point_hex", hex::encode(pk_point_hex));
if pk_point_hex[0] == 0x04 {
json.insert(
"pk_point_hex_compressed",
format!("02{}", hex::encode(&pk_point_hex[1..(pk_point_hex.len() / 2) + 1])),
);
}
let mut ssh_public_key = vec![];
ssh_public_key.write_string(format!("ecdsa-sha2-nistp{}", ec_bit_len).as_bytes());
ssh_public_key.write_string(format!("nistp{}", ec_bit_len).as_bytes());
ssh_public_key.write_string(pk_point_hex);
let ssh_public_key_str = format!(
"ecdsa-sha2-nistp{} {} PIV:{}", ec_bit_len, base64_encode(ssh_public_key), slot_id);
json.insert("ssh_public_key", ssh_public_key_str.to_string());
}
_ => {}
}
}
let serial_lower = cert.serial_number.to_string().to_lowercase();
json.insert("serial", if serial_lower.starts_with("00:") { serial_lower.chars().skip(3).collect() } else { serial_lower });
let cert_der = k.certificate().cert.to_der()?;
json.insert("certificate_hex", hex::encode(&cert_der));
json.insert("certificate_pem", bytes_to_pem("CERTIFICATE", cert_der.as_slice()));
let x509_certificate = parse_x509_certificate(cert_der.as_slice()).unwrap().1;
let public_key_bytes = x509_certificate.public_key().raw;
json.insert("subject", x509_certificate.subject.to_string());
json.insert("issuer", x509_certificate.issuer.to_string());
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: {}", x509_certificate.subject.to_string());
information!("Certificate: {}", bytes_to_pem("CERTIFICATE", cert_der.as_slice()));
information!("Public key: {}", bytes_to_pem("PUBLIC KEY", public_key_bytes));
}
} else {
util_msg::when(MessageType::DEBUG, || {
let cert_der = cert.to_der().unwrap();
debugging!("Slot: {:x}", Into::<u8>::into(k.slot()));
let public_key_bytes = cert.subject_public_key_info.subject_public_key.raw_bytes();
debugging!("Certificate: {}", hex::encode(&cert_der));
debugging!("Public key: {}", hex::encode(public_key_bytes));
});
}
},
}
if json_output {
util::print_pretty_json(&json);
}
Ok(None)
}
}