feat: v1.12.2

This commit is contained in:
2025-05-01 21:46:04 +08:00
parent 9a749b63eb
commit cec27e0f88
23 changed files with 265 additions and 77 deletions

2
Cargo.lock generated
View File

@@ -508,7 +508,7 @@ dependencies = [
[[package]]
name = "card-cli"
version = "1.12.1"
version = "1.12.2"
dependencies = [
"aes-gcm-stream",
"authenticator 0.3.1",

View File

@@ -1,6 +1,6 @@
[package]
name = "card-cli"
version = "1.12.1"
version = "1.12.2"
authors = ["Hatter Jiang <jht5945@gmail.com>"]
edition = "2018"

View File

@@ -1,7 +1,7 @@
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage};
use crate::pivutil::slot_equals;
use crate::util::base64_encode;
use crate::{cmdutil, seutil, util};
use crate::yubikeyutil::find_key_or_error;
use crate::{cmdutil, ecdsautil, hmacutil, seutil, util, yubikeyutil};
use clap::{App, ArgMatches, SubCommand};
use ecdsa::elliptic_curve::pkcs8::der::Encode;
use rust_util::util_clap::{Command, CommandError};
@@ -9,7 +9,6 @@ use rust_util::XResult;
use serde_json::Value;
use std::collections::BTreeMap;
use x509_parser::parse_x509_certificate;
use yubikey::{Key, YubiKey};
pub struct CommandImpl;
@@ -22,13 +21,15 @@ impl Command for CommandImpl {
SubCommand::with_name(self.name())
.about("External public key subcommand")
.arg(cmdutil::build_parameter_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
let parameter = sub_arg_matches.value_of("parameter").unwrap();
let serial_opt = sub_arg_matches.value_of("serial");
let mut json = BTreeMap::new();
match fetch_public_key(parameter) {
match fetch_public_key(parameter, &serial_opt) {
Ok(public_key_bytes) => {
json.insert("success", Value::Bool(true));
json.insert("public_key_base64", base64_encode(&public_key_bytes).into());
@@ -44,7 +45,7 @@ impl Command for CommandImpl {
}
}
fn fetch_public_key(parameter: &str) -> XResult<Vec<u8>> {
fn fetch_public_key(parameter: &str, serial_opt: &Option<&str>) -> XResult<Vec<u8>> {
let key_uri = parse_key_uri(parameter)?;
match key_uri {
KeyUri::SecureEnclaveKey(key) => {
@@ -57,18 +58,27 @@ fn fetch_public_key(parameter: &str) -> XResult<Vec<u8>> {
}
}
KeyUri::YubikeyPivKey(key) => {
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let keys = opt_result!(Key::list(&mut yk), "List keys failed: {}");
for k in &keys {
let slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
if slot_equals(&key.slot, &slot_str) {
let cert_der = k.certificate().cert.to_der()?;
let mut yk = yubikeyutil::open_yubikey_with_serial(serial_opt)?;
if let Some(key) = find_key_or_error(&mut yk, &key.slot)? {
let cert_der = key.certificate().cert.to_der()?;
let x509_certificate = parse_x509_certificate(cert_der.as_slice()).unwrap().1;
let public_key_bytes = x509_certificate.public_key().raw;
return Ok(public_key_bytes.to_vec());
}
}
simple_error!("Slot {} not found", key.slot)
}
KeyUri::YubikeyHmacEncSoftKey(key) => {
let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?;
let p256_public_key = ecdsautil::parse_p256_private_key_to_public_key(&private_key).ok();
let p384_public_key = ecdsautil::parse_p384_private_key_to_public_key(&private_key).ok();
if let Some(p256_public_key) = p256_public_key {
return Ok(p256_public_key);
}
if let Some(p384_public_key) = p384_public_key {
return Ok(p384_public_key);
}
simple_error!("Invalid hmac enc private key")
}
}
}

View File

@@ -2,7 +2,7 @@ use crate::cmd_sign_jwt::digest_by_jwt_algorithm;
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage, YubikeyPivKey};
use crate::pivutil::ToStr;
use crate::util::{base64_decode, base64_encode};
use crate::{cmdutil, pivutil, seutil, util};
use crate::{cmdutil, ecdsautil, hmacutil, pivutil, seutil, util, yubikeyutil};
use clap::{App, ArgMatches, SubCommand};
use jwt::AlgorithmType;
use rust_util::util_clap::{Command, CommandError};
@@ -10,7 +10,6 @@ use rust_util::XResult;
use serde_json::Value;
use std::collections::BTreeMap;
use yubikey::piv::{sign_data, AlgorithmId};
use yubikey::YubiKey;
pub struct CommandImpl;
@@ -26,6 +25,7 @@ impl Command for CommandImpl {
.arg(cmdutil::build_parameter_arg())
.arg(cmdutil::build_message_arg())
.arg(cmdutil::build_pin_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
@@ -64,7 +64,7 @@ fn sign(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
seutil::secure_enclave_p256_sign(&key.private_key, &message_bytes)
}
KeyUri::YubikeyPivKey(key) => {
let mut yk = opt_result!(YubiKey::open(), "Find YubiKey failed: {}");
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
let pin_opt = pivutil::check_read_pin(&mut yk, key.slot, sub_arg_matches);
// FIXME Check Yubikey slot algorithm
@@ -84,6 +84,28 @@ fn sign(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
);
Ok(signed_data.to_vec())
}
KeyUri::YubikeyHmacEncSoftKey(key) => {
let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?;
let p256_private_key_d = ecdsautil::parse_p256_private_key(&private_key).ok();
let p384_private_key_d = ecdsautil::parse_p384_private_key(&private_key).ok();
let (jwt_algorithm, private_key_d) = match (p256_private_key_d, p384_private_key_d) {
(Some(p256_private_key_d), None) => (AlgorithmType::Es256, p256_private_key_d),
(None, Some(p384_private_key_d)) => (AlgorithmType::Es384, p384_private_key_d),
_ => return simple_error!("Invalid private key: {}", private_key),
};
let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?;
let signed_data = match jwt_algorithm {
AlgorithmType::Es256 => ecdsautil::sign_p256_der(&private_key_d, &raw_in)?,
AlgorithmType::Es384 => ecdsautil::sign_p384_der(&private_key_d, &raw_in)?,
_ => return simple_error!("SHOULD NOT HAPPEN: {:?}", jwt_algorithm),
};
Ok(signed_data)
}
}
}

View File

@@ -18,6 +18,7 @@ impl Command for CommandImpl {
Arg::with_name("ciphertext")
.long("ciphertext")
.takes_value(true)
.required(true)
.help("Ciphertext"),
)
.arg(cmdutil::build_json_arg())

View File

@@ -18,6 +18,7 @@ impl Command for CommandImpl {
Arg::with_name("plaintext")
.long("plaintext")
.takes_value(true)
.required(true)
.help("Plaintext"),
)
.arg(cmdutil::build_json_arg())

View File

@@ -1,8 +1,10 @@
use crate::keychain::{KeychainKey, KeychainKeyValue};
use crate::{cmdutil, ecdsautil, hmacutil, util};
use crate::{cmdutil, ecdsautil, hmacutil, util, yubikeyutil};
use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
use std::collections::BTreeMap;
use yubikey::piv::AlgorithmId;
use crate::keyutil::{KeyUri, YubikeyHmacEncSoftKey};
use crate::util::base64_encode;
pub struct CommandImpl;
@@ -83,8 +85,22 @@ impl Command for CommandImpl {
let mut json = BTreeMap::<&'_ str, String>::new();
match keychain_key_uri {
None => {
json.insert("private_key_base64", pkcs8_base64);
json.insert("private_key_base64", pkcs8_base64.clone());
json.insert("private_key_pem", secret_key_pem);
let algorithm_id = match key_type.as_str() {
"p256" => Some(AlgorithmId::EccP256),
"p384" => Some(AlgorithmId::EccP384),
_ => None,
};
if let (true, Some(algorithm_id)) = (with_hmac_encrypt, algorithm_id) {
let yk = yubikeyutil::open_yubikey()?;
let yubikey_hmac_enc_soft_key = YubikeyHmacEncSoftKey {
key_name: format!("yubikey{}-{}", yk.version().major, yk.serial().0),
algorithm: algorithm_id,
hmac_enc_private_key: pkcs8_base64,
};
json.insert("key_uri", KeyUri::YubikeyHmacEncSoftKey(yubikey_hmac_enc_soft_key).to_string());
}
}
Some(keychain_key_uri) => {
json.insert("keychain_key_uri", keychain_key_uri);

View File

@@ -2,8 +2,8 @@ use std::collections::BTreeMap;
use clap::{App, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
use yubikey::YubiKey;
use crate::{cmdutil, util};
use serde_json::Value;
use crate::{cmdutil, util, yubikeyutil};
pub struct CommandImpl;
@@ -13,29 +13,33 @@ impl Command for CommandImpl {
fn subcommand<'a>(&self) -> App<'a, 'a> {
SubCommand::with_name(self.name()).about("YubiKey list")
.arg(cmdutil::build_json_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
let json_output = cmdutil::check_json_output(sub_arg_matches);
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
if json_output {
let mut json = BTreeMap::<&'_ str, String>::new();
json.insert("name", yk.name().to_string());
json.insert("version", yk.version().to_string());
json.insert("serial", yk.serial().0.to_string());
let mut json = BTreeMap::<&'_ str, Value>::new();
json.insert("name", yk.name().into());
json.insert("version", yk.version().to_string().into());
json.insert("serial", yk.serial().0.into());
if let Ok(pin_retries) = yk.get_pin_retries() {
json.insert("pin_retries", pin_retries.to_string());
json.insert("pin_retries", pin_retries.into());
}
if let Ok(chuid) = yk.chuid() {
json.insert("chuid", chuid.to_string());
json.insert("chuid", chuid.to_string().into());
}
if let Ok(ccuid) = yk.cccid() {
json.insert("ccuid", ccuid.to_string());
json.insert("ccuid", ccuid.to_string().into());
}
if let Ok(piv_keys) = yk.piv_keys() {
json.insert("keys", piv_keys.iter().map(|k| format!("{}", k.slot())).collect::<Vec<_>>().join(", "));
let key_list = piv_keys.iter().map(|k| Value::String(format!("{}", k.slot()))).collect::<Vec<_>>();
json.insert("key_list", key_list.into());
let keys = piv_keys.iter().map(|k| format!("{}", k.slot())).collect::<Vec<_>>().join(", ");
json.insert("keys", keys.into());
}
util::print_pretty_json(&json);

View File

@@ -10,7 +10,7 @@ use spki::der::Encode;
use x509_parser::parse_x509_certificate;
use yubikey::{Certificate, YubiKey};
use yubikey::piv::SlotId;
use crate::{cmdutil, yubikeyutil};
use crate::pivutil::get_algorithm_id;
use crate::pkiutil::{bytes_to_pem, get_pki_algorithm};
@@ -23,6 +23,7 @@ impl Command for CommandImpl {
SubCommand::with_name(self.name()).about("PIV subcommand")
.arg(Arg::with_name("detail").long("detail").help("Detail output"))
.arg(Arg::with_name("show-config").long("show-config").help("Show config output"))
.arg(cmdutil::build_serial_arg())
// .arg(Arg::with_name("json").long("json").help("JSON output"))
}
@@ -30,7 +31,7 @@ impl Command for CommandImpl {
let detail_output = sub_arg_matches.is_present("detail");
let show_config = sub_arg_matches.is_present("show-config");
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
success!("Name: {}", yk.name());
information!("Version: {}", yk.version());
information!("Serial: {}", yk.serial());

View File

@@ -3,9 +3,8 @@ use std::collections::BTreeMap;
use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
use yubikey::piv::AlgorithmId;
use yubikey::YubiKey;
use crate::{cmdutil, pinutil, pivutil, util};
use crate::{cmdutil, pinutil, pivutil, util, yubikeyutil};
use crate::util::{read_stdin, try_decode};
pub struct CommandImpl;
@@ -21,6 +20,7 @@ impl Command for CommandImpl {
.arg(Arg::with_name("ciphertext").long("ciphertext").short("c").takes_value(true).help("Encrypted data (HEX or Base64)"))
.arg(Arg::with_name("stdin").long("stdin").help("Standard input (Ciphertext)"))
.arg(cmdutil::build_json_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
@@ -38,7 +38,7 @@ impl Command for CommandImpl {
return simple_error!("Argument --ciphertext must be assigned");
};
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
if let Some(pin) = &pin_opt {
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
}

View File

@@ -4,10 +4,10 @@ use std::fs;
use clap::{App, Arg, ArgMatches, SubCommand};
use rand::rngs::OsRng;
use rust_util::util_clap::{Command, CommandError};
use yubikey::{PinPolicy, YubiKey};
use yubikey::PinPolicy;
use yubikey::piv::{AlgorithmId, decrypt_data, metadata};
use crate::{cmdutil, ecdhutil, pinutil, pivutil, util};
use crate::{cmdutil, ecdhutil, pinutil, pivutil, util, yubikeyutil};
use crate::pivutil::get_algorithm_id;
pub struct CommandImpl;
@@ -28,6 +28,7 @@ impl Command for CommandImpl {
.arg(Arg::with_name("public-key-file").long("public-key-file").takes_value(true).help("Public key"))
.arg(Arg::with_name("public-key-point-hex").long("public-key-point-hex").takes_value(true).help("Public key point hex"))
.arg(cmdutil::build_json_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
@@ -74,7 +75,7 @@ impl Command for CommandImpl {
let slot = opt_value_result!(sub_arg_matches.value_of("slot"), "--slot must assigned, e.g. 82, 83 ...");
let epk = opt_value_result!(sub_arg_matches.value_of("epk"), "--epk must assigned");
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
let slot_id = pivutil::get_slot_id(slot)?;
debugging!("Slot id: {}", slot_id);
if let Ok(meta) = metadata(&mut yk, slot_id) {

View File

@@ -4,10 +4,9 @@ use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
use x509_parser::nom::AsBytes;
use yubikey::piv::{metadata, sign_data, AlgorithmId, ManagementAlgorithmId};
use yubikey::YubiKey;
use crate::util::base64_encode;
use crate::{argsutil, cmdutil, pivutil, util};
use crate::{argsutil, cmdutil, pivutil, util, yubikeyutil};
use crate::digestutil::DigestAlgorithm;
pub struct CommandImpl;
@@ -25,6 +24,7 @@ impl Command for CommandImpl {
.arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input"))
.arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash"))
.arg(cmdutil::build_json_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
@@ -40,7 +40,7 @@ impl Command for CommandImpl {
};
let hash_bytes = argsutil::get_digest_or_hash(sub_arg_matches, digest_algorithm)?;
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
let slot_id = pivutil::get_slot_id(slot)?;
let pin_opt = pivutil::check_read_pin(&mut yk, slot_id, sub_arg_matches);

View File

@@ -1,9 +1,9 @@
use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
use yubikey::{PinPolicy, piv, TouchPolicy, YubiKey};
use yubikey::{PinPolicy, piv, TouchPolicy};
use yubikey::piv::{AlgorithmId, SlotId};
use crate::{cmdutil, pinutil};
use crate::{cmdutil, pinutil, yubikeyutil};
pub struct CommandImpl;
@@ -14,6 +14,7 @@ impl Command for CommandImpl {
SubCommand::with_name(self.name()).about("PIV generate subcommand")
.arg(cmdutil::build_pin_arg())
.arg(Arg::with_name("force").long("force").help("Force generate"))
.arg(cmdutil::build_serial_arg())
// .arg(cmdutil::build_json_arg())
}
@@ -28,7 +29,7 @@ impl Command for CommandImpl {
failure_and_exit!("--force must be assigned");
}
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
let public_key_info = opt_result!(piv::generate(&mut yk,SlotId::Signature, AlgorithmId::Rsa2048,

View File

@@ -6,10 +6,10 @@ 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::Key;
use yubikey::piv::{AlgorithmId, metadata};
use crate::{cmdutil, pivutil, util};
use crate::{cmdutil, pivutil, util, yubikeyutil};
use crate::keyutil::{KeyUri, YubikeyPivKey};
use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
use crate::pkiutil::bytes_to_pem;
@@ -25,6 +25,7 @@ impl Command for CommandImpl {
SubCommand::with_name(self.name()).about("PIV meta subcommand")
.arg(cmdutil::build_slot_arg())
.arg(cmdutil::build_json_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
@@ -34,7 +35,7 @@ impl Command for CommandImpl {
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 mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
let slot_id = pivutil::get_slot_id(slot)?;
json.insert("slot", pivutil::to_slot_hex(&slot_id));

View File

@@ -2,10 +2,10 @@ use std::collections::BTreeMap;
use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
use yubikey::{piv, YubiKey};
use yubikey::piv;
use yubikey::piv::{AlgorithmId, SlotId};
use crate::{cmdutil, pinutil, pivutil, rsautil, util};
use crate::{cmdutil, pinutil, pivutil, rsautil, util, yubikeyutil};
use crate::util::base64_encode;
pub struct CommandImpl;
@@ -20,6 +20,7 @@ impl Command for CommandImpl {
.arg(cmdutil::build_no_pin_arg())
.arg(Arg::with_name("sha256").short("2").long("sha256").takes_value(true).help("Digest SHA256 HEX"))
.arg(cmdutil::build_json_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
@@ -29,7 +30,7 @@ impl Command for CommandImpl {
let sha256_hex_opt = sub_arg_matches.value_of("sha256").map(|s| s.to_string());
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
if let Some(pin) = &pin_opt {
opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}");
}

View File

@@ -9,7 +9,7 @@ use tabled::{Table, Tabled};
use x509_parser::parse_x509_certificate;
use yubikey::piv::{metadata, SlotId};
use yubikey::{Certificate, YubiKey};
use crate::{cmdutil, util};
use crate::{cmdutil, util, yubikeyutil};
use crate::pivutil::{get_algorithm_id_by_certificate, ToStr, ORDERED_SLOTS};
const NA: &str = "N/A";
@@ -38,6 +38,7 @@ impl Command for CommandImpl {
.arg(Arg::with_name("all").long("all").help("Show all"))
.arg(Arg::with_name("ordered").long("ordered").help("Show ordered"))
.arg(cmdutil::build_json_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
@@ -48,7 +49,7 @@ impl Command for CommandImpl {
let show_ordered = sub_arg_matches.is_present("ordered");
let mut output = Map::new();
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
let mut yk = yubikeyutil::open_yubikey_with_args(sub_arg_matches)?;
success!("Name: {}", yk.name());
information!("Version: {}", yk.version());

View File

@@ -2,14 +2,11 @@ use std::collections::BTreeMap;
use clap::{App, Arg, ArgMatches, SubCommand};
use openssl::rsa::{Padding, Rsa};
use rust_util::XResult;
use rust_util::util_clap::{Command, CommandError};
use yubikey::{Key, YubiKey};
use yubikey::piv::{AlgorithmId, SlotId};
use yubikey::piv::AlgorithmId;
use crate::{argsutil, cmdutil, ecdsautil, pivutil, util};
use crate::{argsutil, cmdutil, ecdsautil, pivutil, util, yubikeyutil};
use crate::ecdsautil::EcdsaAlgorithm;
use crate::pivutil::slot_equals;
pub struct CommandImpl;
@@ -24,6 +21,7 @@ impl Command for CommandImpl {
.arg(Arg::with_name("input").short("i").long("input").takes_value(true).help("Input"))
.arg(Arg::with_name("hash-hex").short("x").long("hash-hex").takes_value(true).help("Hash"))
.arg(cmdutil::build_json_arg())
.arg(cmdutil::build_serial_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
@@ -42,7 +40,7 @@ impl Command for CommandImpl {
let slot_id = pivutil::get_slot_id(slot)?;
json.insert("slot", pivutil::to_slot_hex(&slot_id));
if let Some(key) = find_key(&slot_id)? {
if let Some(key) = yubikeyutil::open_and_find_key(&slot_id, sub_arg_matches)? {
let certificate = key.certificate();
let tbs_certificate = &certificate.cert.tbs_certificate;
if let Ok(algorithm_id) = pivutil::get_algorithm_id_by_certificate(certificate) {
@@ -104,17 +102,3 @@ impl Command for CommandImpl {
Ok(None)
}
}
fn find_key(slot_id: &SlotId) -> XResult<Option<Key>> {
let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}");
match Key::list(&mut yk) {
Err(e) => warning!("List keys failed: {}", e),
Ok(keys) => for k in keys {
let slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
if slot_equals(slot_id, &slot_str) {
return Ok(Some(k));
}
},
}
Ok(None)
}

View File

@@ -85,7 +85,7 @@ pub fn print_se_key(
if let Some(public_key_jwk) = public_key_jwk {
json.insert("public_key_jwk", base64_encode(public_key_jwk));
}
json.insert("key", key_uri.to_string());
json.insert("key_uri", key_uri.to_string());
util::print_pretty_json(&json);
} else {

View File

@@ -9,6 +9,11 @@ pub fn build_slot_arg() -> Arg<'static, 'static> {
.help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")
}
pub fn build_serial_arg() -> Arg<'static, 'static> {
Arg::with_name("serial").long("serial").takes_value(true).help("Serial number")
}
pub fn build_pin_arg() -> Arg<'static, 'static> {
Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")
}

View File

@@ -76,6 +76,35 @@ pub fn generate_p384_keypair() -> XResult<(String, String, String, Vec<u8>, JwkE
Ok((secret_key_der_base64, secret_key_pem, public_key_pem, public_key_der, jwk_ec_key))
}
macro_rules! parse_ecdsa_private_key_to_public_key {
($algo: tt, $parse_ecdsa_private_key: tt) => ({
use $algo::pkcs8::DecodePrivateKey;
use $algo::SecretKey;
let secret_key = match SecretKey::from_pkcs8_pem($parse_ecdsa_private_key) {
Ok(secret_key) => secret_key,
Err(_) => match try_decode($parse_ecdsa_private_key) {
Ok(private_key_der) => match SecretKey::from_pkcs8_der(&private_key_der) {
Ok(secret_key) => secret_key,
Err(e) => return simple_error!("Invalid PKCS#8 private key {}, error: {}", $parse_ecdsa_private_key, e),
}
Err(_) => return simple_error!("Invalid PKCS#8 private key: {}", $parse_ecdsa_private_key),
}
};
let public_key_document = opt_result!(secret_key.public_key().to_public_key_der(), "Conver to public key failed: {}");
Ok(public_key_document.to_vec())
})
}
pub fn parse_p256_private_key_to_public_key(private_key_pkcs8: &str) -> XResult<Vec<u8>> {
parse_ecdsa_private_key_to_public_key!(p256, private_key_pkcs8)
}
pub fn parse_p384_private_key_to_public_key(private_key_pkcs8: &str) -> XResult<Vec<u8>> {
parse_ecdsa_private_key_to_public_key!(p384, private_key_pkcs8)
}
macro_rules! parse_ecdsa_private_key {
($algo: tt, $parse_ecdsa_private_key: tt) => ({
use $algo::pkcs8::DecodePrivateKey;
@@ -113,6 +142,16 @@ pub fn sign_p256_rs(private_key_d: &[u8], pre_hash: &[u8]) -> XResult<Vec<u8>> {
Ok(signature.to_bytes().to_vec())
}
pub fn sign_p256_der(private_key_d: &[u8], pre_hash: &[u8]) -> XResult<Vec<u8>> {
use p256::ecdsa::{SigningKey, Signature};
use p256::ecdsa::signature::hazmat::PrehashSigner;
let signing_key = SigningKey::from_slice(private_key_d)?;
let signature: Signature = signing_key.sign_prehash(pre_hash)?;
Ok(signature.to_der().as_bytes().to_vec())
}
pub fn sign_p384_rs(private_key_d: &[u8], pre_hash: &[u8]) -> XResult<Vec<u8>> {
use p384::ecdsa::{SigningKey, Signature};
use p384::ecdsa::signature::hazmat::PrehashSigner;
@@ -123,6 +162,16 @@ pub fn sign_p384_rs(private_key_d: &[u8], pre_hash: &[u8]) -> XResult<Vec<u8>> {
Ok(signature.to_bytes().to_vec())
}
pub fn sign_p384_der(private_key_d: &[u8], pre_hash: &[u8]) -> XResult<Vec<u8>> {
use p384::ecdsa::{SigningKey, Signature};
use p384::ecdsa::signature::hazmat::PrehashSigner;
let signing_key = SigningKey::from_slice(private_key_d)?;
let signature: Signature = signing_key.sign_prehash(pre_hash)?;
Ok(signature.to_der().as_bytes().to_vec())
}
macro_rules! ecdsa_verify_signature {
($algo: tt, $pk_point: tt, $prehash: tt, $signature: tt) => ({
use ecdsa::Signature;

View File

@@ -8,6 +8,7 @@ use crate::pivutil::{ToStr, FromStr};
pub enum KeyUri {
SecureEnclaveKey(SecureEnclaveKey),
YubikeyPivKey(YubikeyPivKey),
YubikeyHmacEncSoftKey(YubikeyHmacEncSoftKey),
}
impl KeyUri {
@@ -33,7 +34,7 @@ impl ToString for KeyUri {
key_uri.push_str(":");
key_uri.push_str(&key.private_key);
}
// key://yubikey-5n:piv/p256:*:9a
// key://yubikey-5n:piv/p256::9a
KeyUri::YubikeyPivKey(key) => {
key_uri.push_str(&key.key_name);
key_uri.push_str(":piv/");
@@ -41,6 +42,14 @@ impl ToString for KeyUri {
key_uri.push_str("::");
key_uri.push_str(key.slot.to_str());
}
// key://-:soft/p256::hmac_enc:...
KeyUri::YubikeyHmacEncSoftKey(key) => {
key_uri.push_str(&key.key_name);
key_uri.push_str(":soft/");
key_uri.push_str(key.algorithm.to_str());
key_uri.push_str("::");
key_uri.push_str(key.hmac_enc_private_key.as_str());
}
}
key_uri
}
@@ -90,6 +99,14 @@ pub struct YubikeyPivKey {
pub slot: SlotId,
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct YubikeyHmacEncSoftKey {
pub key_name: String,
pub algorithm: AlgorithmId,
pub hmac_enc_private_key: String,
}
pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
let regex = Regex::new(r##"^key://([0-9a-zA-Z\-\._]*):(\w+)/(\w+):((?:\w+)?):(.*)$"##).unwrap();
let captures = match regex.captures(key_uri) {
@@ -135,6 +152,20 @@ pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
debugging!("Parsed key uri: {:?}", parsed_key_uri);
Ok(parsed_key_uri)
}
"soft" => {
if "" != usage {
return simple_error!("Key uri's usage must be empty.");
}
let algorithm = opt_value_result!(AlgorithmId::from_str(algorithm), "Invalid algorithm id: {}", algorithm);
let hmac_enc_private_key = left_part.to_string();
let parsed_key_uri = KeyUri::YubikeyHmacEncSoftKey(YubikeyHmacEncSoftKey {
key_name: host_or_name.to_string(),
algorithm,
hmac_enc_private_key,
});
debugging!("Parsed key uri: {:?}", parsed_key_uri);
Ok(parsed_key_uri)
}
_ => simple_error!("Key uri's module must be se."),
}
}

View File

@@ -78,6 +78,7 @@ mod seutil;
mod signfile;
mod sshutil;
mod util;
mod yubikeyutil;
pub struct DefaultCommandImpl;

58
src/yubikeyutil.rs Normal file
View File

@@ -0,0 +1,58 @@
use crate::pivutil::slot_equals;
use clap::ArgMatches;
use rust_util::XResult;
use yubikey::piv::SlotId;
use yubikey::{Key, Serial, YubiKey};
pub fn open_yubikey_with_args(sub_arg_matches: &ArgMatches) -> XResult<YubiKey> {
let serial_opt = sub_arg_matches.value_of("serial");
open_yubikey_with_serial(&serial_opt)
}
pub fn open_yubikey_with_serial(serial_opt: &Option<&str>) -> XResult<YubiKey> {
match serial_opt {
None => open_yubikey(),
Some(serial) => {
let serial_no: u32 = opt_result!(serial.parse(), "{}");
Ok(opt_result!(
YubiKey::open_by_serial(Serial(serial_no)),
"YubiKey with serial: {} not found: {}",
serial
))
}
}
}
pub fn open_yubikey() -> XResult<YubiKey> {
Ok(opt_result!(YubiKey::open(), "YubiKey not found: {}"))
}
pub fn open_and_find_key(slot_id: &SlotId, sub_arg_matches: &ArgMatches) -> XResult<Option<Key>> {
let mut yk = open_yubikey_with_args(sub_arg_matches)?;
find_key(&mut yk, slot_id)
}
pub fn find_key(yk: &mut YubiKey, slot_id: &SlotId) -> XResult<Option<Key>> {
match Key::list(yk) {
Err(e) => warning!("List keys failed: {}", e),
Ok(keys) => return Ok(filter_key(keys, slot_id)),
}
Ok(None)
}
pub fn find_key_or_error(yk: &mut YubiKey, slot_id: &SlotId) -> XResult<Option<Key>> {
match Key::list(yk) {
Err(e) => simple_error!("List keys failed: {}", e),
Ok(keys) => Ok(filter_key(keys, slot_id)),
}
}
fn filter_key(keys: Vec<Key>, slot_id: &SlotId) -> Option<Key> {
for k in keys {
let slot_str = format!("{:x}", Into::<u8>::into(k.slot()));
if slot_equals(slot_id, &slot_str) {
return Some(k);
}
}
None
}