feat: update yubikey to v0.8

This commit is contained in:
2023-08-20 15:24:28 +08:00
parent 9fb0da7d33
commit 7f5a5a7d3c
7 changed files with 470 additions and 641 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,4 @@
__external_gitignore/
__*.pem __*.pem
test.txt test.txt
enc.txt enc.txt
@@ -25,6 +26,7 @@ target/
# Icon must end with two \r # Icon must end with two \r
Icon Icon
# Thumbnails # Thumbnails
._* ._*

948
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "card-cli" name = "card-cli"
version = "1.5.8" version = "1.6.0"
authors = ["Hatter Jiang <jht5945@gmail.com>"] authors = ["Hatter Jiang <jht5945@gmail.com>"]
edition = "2018" edition = "2018"
@@ -25,8 +25,8 @@ chrono = "0.4"
simpledateformat = "0.1" simpledateformat = "0.1"
ring = "0.16" ring = "0.16"
openssl = "0.10" openssl = "0.10"
pem = "2.0" pem = "3.0"
yubikey = { version = "0.7", features = ["untested"] } yubikey = { version = "0.8", features = ["untested"] }
yubico_manager = "0.9" yubico_manager = "0.9"
x509 = "0.2" x509 = "0.2"
x509-parser = "0.13" x509-parser = "0.13"

View File

@@ -6,10 +6,12 @@ use digest::Digest;
use rust_util::util_clap::{Command, CommandError}; use rust_util::util_clap::{Command, CommandError};
use rust_util::XResult; use rust_util::XResult;
use sha2::Sha256; use sha2::Sha256;
use spki::der::Encode;
use x509_parser::parse_x509_certificate; use x509_parser::parse_x509_certificate;
use yubikey::{Certificate, YubiKey}; use yubikey::{Certificate, YubiKey};
use yubikey::piv::SlotId; use yubikey::piv::SlotId;
use crate::pivutil::get_algorithm_id;
use crate::pkiutil::{bytes_to_pem, get_pki_algorithm}; use crate::pkiutil::{bytes_to_pem, get_pki_algorithm};
pub struct CommandImpl; pub struct CommandImpl;
@@ -78,13 +80,17 @@ fn print_cert_info(yubikey: &mut YubiKey, slot: SlotId, detail_output: bool) ->
return simple_error!("error reading certificate in slot {:?}: {}", slot, e); return simple_error!("error reading certificate in slot {:?}: {}", slot, e);
} }
}; };
let buf = cert.as_ref(); let buf_vec = cert.cert.to_der()?;
let buf: &[u8] = buf_vec.as_ref();
if !buf.is_empty() { if !buf.is_empty() {
information!("{}", "-".repeat(88)); information!("{}", "-".repeat(88));
let certificate_fingerprint_sha256 = Sha256::digest(buf); let certificate_fingerprint_sha256 = Sha256::digest(buf);
let slot_id: u8 = slot.into(); let slot_id: u8 = slot.into();
success!("Slot: {:?}, id: {:x}, algorithm: {:?}", slot, slot_id, cert.subject_pki().algorithm()); let algorithm_id = get_algorithm_id(&cert.cert.tbs_certificate.subject_public_key_info)
.map(|aid| format!("{:?}", aid))
.unwrap_or_else(|e| format!("Error: {}", e));
success!("Slot: {:?}, id: {:x}, algorithm: {}", slot, slot_id, algorithm_id);
if detail_output { if detail_output {
information!("{}", bytes_to_pem("CERTIFICATE", buf)); information!("{}", bytes_to_pem("CERTIFICATE", buf));

View File

@@ -10,9 +10,10 @@ use rand::rngs::OsRng;
use rust_util::util_clap::{Command, CommandError}; use rust_util::util_clap::{Command, CommandError};
use rust_util::util_msg; use rust_util::util_msg;
use yubikey::{PinPolicy, YubiKey}; use yubikey::{PinPolicy, YubiKey};
use yubikey::certificate::PublicKeyInfo;
use yubikey::piv::{AlgorithmId, decrypt_data, metadata, RetiredSlotId, SlotId}; use yubikey::piv::{AlgorithmId, decrypt_data, metadata, RetiredSlotId, SlotId};
use crate::pivutil::get_algorithm_id;
pub struct CommandImpl; pub struct CommandImpl;
impl Command for CommandImpl { impl Command for CommandImpl {
@@ -108,20 +109,19 @@ impl Command for CommandImpl {
} }
} }
if let Some(public_key) = &meta.public { if let Some(public_key) = &meta.public {
match public_key { let algorithm_id = opt_result!(get_algorithm_id(&public_key), "Get algorithm id failed: {}");
PublicKeyInfo::Rsa { algorithm, pubkey } => { match algorithm_id {
failure_and_exit!("RSA not supported, {:?}, {:?}", algorithm, pubkey); AlgorithmId::Rsa1024 | AlgorithmId::Rsa2048 | AlgorithmId::EccP384 => {
failure_and_exit!("Not supported algorithm: {:?}", algorithm_id);
} }
PublicKeyInfo::EcP256(pubkey) => { AlgorithmId::EccP256 => {
if json_output { match algorithm_id {
json.insert("pk_point_hex", hex::encode(pubkey.as_bytes())); AlgorithmId::EccP256 => if let Some(public) = &meta.public {
} else { json.insert("pk_point_hex", hex::encode(public.subject_public_key.raw_bytes()));
information!("EC-P256, {}", hex::encode(pubkey.as_bytes())); }
_ => {}
} }
} }
PublicKeyInfo::EcP384(pubkey) => {
failure_and_exit!("EC-P384 not supported, {}", hex::encode(pubkey.as_bytes()));
}
} }
} }
} else { } else {

View File

@@ -1,18 +1,16 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use clap::{App, Arg, ArgMatches, SubCommand}; use clap::{App, Arg, ArgMatches, SubCommand};
use hex::ToHex; use p256::pkcs8::der::Encode;
use rust_util::util_clap::{Command, CommandError}; use rust_util::util_clap::{Command, CommandError};
use rust_util::util_msg; use rust_util::util_msg;
use rust_util::util_msg::MessageType; use rust_util::util_msg::MessageType;
use x509::SubjectPublicKeyInfo;
use x509_parser::parse_x509_certificate; use x509_parser::parse_x509_certificate;
use yubikey::{Key, PinPolicy, TouchPolicy, YubiKey}; use yubikey::{Key, PinPolicy, TouchPolicy, YubiKey};
use yubikey::certificate::PublicKeyInfo;
use yubikey::piv::{AlgorithmId, ManagementAlgorithmId, metadata, Origin}; use yubikey::piv::{AlgorithmId, ManagementAlgorithmId, metadata, Origin};
use crate::pivutil; use crate::pivutil;
use crate::pivutil::slot_equals; use crate::pivutil::{get_algorithm_id, slot_equals};
use crate::pkiutil::bytes_to_pem; use crate::pkiutil::bytes_to_pem;
pub struct CommandImpl; pub struct CommandImpl;
@@ -87,28 +85,6 @@ impl Command for CommandImpl {
} else { } else {
information!("Origin: {}", origin_str); information!("Origin: {}", origin_str);
} }
if let Some(public_key) = &meta.public {
match public_key {
PublicKeyInfo::Rsa { algorithm, pubkey } => {
failure_and_exit!("RSA not supported, {:?}, {:?}", algorithm, pubkey);
}
PublicKeyInfo::EcP256(pubkey) => {
if json_output {
json.insert("pk_point_hex", hex::encode(pubkey.as_bytes()));
} else {
information!("EC-P256, {}", hex::encode(pubkey.as_bytes()));
}
}
PublicKeyInfo::EcP384(pubkey) => {
if json_output {
json.insert("pk_point_hex", hex::encode(pubkey.as_bytes()));
} else {
information!("EC-P384, {}", hex::encode(pubkey.as_bytes()));
}
}
}
}
} else { } else {
warning!("Get slot: {} meta data failed", slot); warning!("Get slot: {} meta data failed", slot);
} }
@@ -116,42 +92,51 @@ impl Command for CommandImpl {
match Key::list(&mut yk) { match Key::list(&mut yk) {
Err(e) => warning!("List keys failed: {}", e), Err(e) => warning!("List keys failed: {}", e),
Ok(keys) => for k in &keys { Ok(keys) => for k in &keys {
let cert = &k.certificate().cert.tbs_certificate;
let slot_str = format!("{:x}", Into::<u8>::into(k.slot())); let slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
if slot_equals(&slot_id, &slot_str) { if slot_equals(&slot_id, &slot_str) {
if !json.contains_key("pk_point_hex") { if let Ok(algorithm_id) = get_algorithm_id(&k.certificate().cert.tbs_certificate.subject_public_key_info) {
let public_key_hex = &k.certificate().subject_pki().public_key(); let algorithm_str = match algorithm_id {
json.insert("pk_point_hex", hex::encode(&public_key_hex));
let algorithm_str = match k.certificate().subject_pki().algorithm() {
AlgorithmId::Rsa1024 => "rsa1024", AlgorithmId::Rsa1024 => "rsa1024",
AlgorithmId::Rsa2048 => "rsa2048", AlgorithmId::Rsa2048 => "rsa2048",
AlgorithmId::EccP256 => "p256", AlgorithmId::EccP256 => "p256",
AlgorithmId::EccP384 => "p384", AlgorithmId::EccP384 => "p384",
}; };
json.insert("algorithm", algorithm_str.to_string()); json.insert("algorithm", algorithm_str.to_string());
}
json.insert("subject", k.certificate().subject().to_string());
json.insert("issuer", k.certificate().issuer().to_string());
json.insert("serial", k.certificate().serial().to_string());
json.insert("certificate_hex", k.certificate().encode_hex::<String>());
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_bit_string = &cert.subject_public_key_info.subject_public_key;
match algorithm_id {
AlgorithmId::EccP256 | AlgorithmId::EccP384 => {
json.insert("pk_point_hex", hex::encode(public_key_bit_string.raw_bytes()));
}
_ => {}
}
}
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; 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_hex", hex::encode(public_key_bytes));
json.insert("public_key_pem", bytes_to_pem("PUBLIC KEY", public_key_bytes)); json.insert("public_key_pem", bytes_to_pem("PUBLIC KEY", public_key_bytes));
if !json_output { if !json_output {
information!("Subject: {}", k.certificate().subject()); information!("Subject: {}", x509_certificate.subject.to_string());
information!("Certificate: {}", bytes_to_pem("CERTIFICATE", k.certificate().as_ref())); information!("Certificate: {}", bytes_to_pem("CERTIFICATE", cert_der.as_slice()));
information!("Public key: {}", bytes_to_pem("PUBLIC KEY", public_key_bytes)); information!("Public key: {}", bytes_to_pem("PUBLIC KEY", public_key_bytes));
} }
} else { } else {
util_msg::when(MessageType::DEBUG, || { util_msg::when(MessageType::DEBUG, || {
let cert_der = cert.to_der().unwrap();
debugging!("Slot: {:x}", Into::<u8>::into(k.slot())); debugging!("Slot: {:x}", Into::<u8>::into(k.slot()));
let cert_hex = k.certificate().encode_hex::<String>(); let public_key_bytes = cert.subject_public_key_info.subject_public_key.raw_bytes();
let public_key_hex = &k.certificate().subject_pki().public_key(); debugging!("Certificate: {}", hex::encode(&cert_der));
debugging!("Certificate: {}", &cert_hex); debugging!("Public key: {}", hex::encode(public_key_bytes));
debugging!("Public key: {}", hex::encode(&public_key_hex));
}); });
} }
}, },

View File

@@ -1,9 +1,55 @@
use std::str::FromStr; use std::str::FromStr;
use rust_util::XResult; use rust_util::XResult;
use yubikey::piv::RetiredSlotId; use spki::der::{Decode, Encode};
use spki::{ObjectIdentifier, SubjectPublicKeyInfoOwned};
use x509_parser::prelude::FromDer;
use x509_parser::public_key::RSAPublicKey;
use yubikey::piv::{AlgorithmId, RetiredSlotId};
use yubikey::piv::SlotId; use yubikey::piv::SlotId;
const RSA: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.113549.1.1.1");
const ECC: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.2.1");
// NIST recommended curves
// secp192r1 {1.2.840.10045.3.1.1}
// secp224r1 {1.3.132.0.33}
// secp256r1 {1.2.840.10045.3.1.7}
// secp384r1 {1.3.132.0.34}
// secp521r1 {1.3.132.0.35}
const ECC_P256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
const ECC_P384: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.3.132.0.34");
pub fn get_algorithm_id(public_key_info: &SubjectPublicKeyInfoOwned) -> XResult<AlgorithmId> {
if public_key_info.algorithm.oid == RSA {
let rsa_public_key = opt_result!(
RSAPublicKey::from_der(public_key_info.subject_public_key.raw_bytes()), "Parse public key failed: {}");
let starts_with_0 = rsa_public_key.1.modulus.starts_with(&[0]);
let public_key_bits = (rsa_public_key.1.modulus.len() - if starts_with_0 { 1 } else { 0 }) * 8;
if public_key_bits == 1024 {
return Ok(AlgorithmId::Rsa1024);
}
if public_key_bits == 2048 {
return Ok(AlgorithmId::Rsa2048);
}
return simple_error!("Unknown rsa bits: {}", public_key_bits);
}
if public_key_info.algorithm.oid == ECC {
if let Some(any) = &public_key_info.algorithm.parameters {
let any_parameter_der = opt_result!(any.to_der(), "Bad any parameter: {}");
let any_parameter_oid = opt_result!(ObjectIdentifier::from_der(&any_parameter_der), "Bad any parameter der: {}");
if any_parameter_oid == ECC_P256 {
return Ok(AlgorithmId::EccP256);
}
if any_parameter_oid == ECC_P384 {
return Ok(AlgorithmId::EccP384);
}
return simple_error!("Unknown any parameter oid: {}", any_parameter_oid);
}
}
simple_error!("Unknown algorithm: {}", public_key_info.algorithm.oid)
}
pub fn slot_equals(slot_id: &SlotId, slot: &str) -> bool { pub fn slot_equals(slot_id: &SlotId, slot: &str) -> bool {
get_slot_id(slot).map(|sid| &sid == slot_id).unwrap_or(false) get_slot_id(slot).map(|sid| &sid == slot_id).unwrap_or(false)
} }