feat: v1.12.2
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "card-cli"
|
||||
version = "1.12.1"
|
||||
version = "1.12.2"
|
||||
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@@ -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 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());
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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: {}");
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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: {}");
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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."),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,6 +78,7 @@ mod seutil;
|
||||
mod signfile;
|
||||
mod sshutil;
|
||||
mod util;
|
||||
mod yubikeyutil;
|
||||
|
||||
pub struct DefaultCommandImpl;
|
||||
|
||||
|
||||
58
src/yubikeyutil.rs
Normal file
58
src/yubikeyutil.rs
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user