feat: v1.12.0

This commit is contained in:
2025-05-01 00:22:42 +08:00
parent 3af863762f
commit c270c2e369
14 changed files with 383 additions and 103 deletions

2
Cargo.lock generated
View File

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

View File

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

View File

@@ -5,7 +5,7 @@ use std::io::Read;
use clap::ArgMatches;
use rust_util::XResult;
use crate::digestutil::{sha256, sha256_bytes};
use crate::digestutil::DigestAlgorithm;
pub fn get_sha256_digest_or_hash(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
@@ -13,6 +13,10 @@ pub fn get_sha256_digest_or_hash(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8
}
pub fn get_sha256_digest_or_hash_with_file_opt(sub_arg_matches: &ArgMatches, file_opt: &Option<String>) -> XResult<Vec<u8>> {
get_digest_or_hash_with_file_opt(sub_arg_matches, file_opt, DigestAlgorithm::Sha256)
}
pub fn get_digest_or_hash_with_file_opt(sub_arg_matches: &ArgMatches, file_opt: &Option<String>, digest: DigestAlgorithm) -> XResult<Vec<u8>> {
let file_opt = file_opt.as_ref().map(String::as_str);
if let Some(file) = sub_arg_matches.value_of("file").or(file_opt) {
let metadata = opt_result!(fs::metadata(file), "Read file: {} metadata filed: {}", file);
@@ -28,9 +32,9 @@ pub fn get_sha256_digest_or_hash_with_file_opt(sub_arg_matches: &ArgMatches, fil
let mut f = opt_result!(File::open(file), "Open file: {} failed: {}", file);
let mut content = vec![];
opt_result!(f.read_to_end(&mut content), "Read file: {} failed: {}", file);
Ok(sha256_bytes(&content))
Ok(digest.digest(&content))
} else if let Some(input) = sub_arg_matches.value_of("input") {
Ok(sha256(input))
Ok(digest.digest_str(input))
} else if let Some(hash_hex) = sub_arg_matches.value_of("hash-hex") {
Ok(opt_result!(hex::decode(hash_hex), "Parse hash-hex failed: {}"))
} else {

View File

@@ -1,8 +1,15 @@
use crate::util;
use clap::{App, Arg, ArgMatches, SubCommand};
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage};
use crate::pivutil::slot_equals;
use crate::util::base64_encode;
use crate::{cmdutil, seutil, util};
use clap::{App, ArgMatches, SubCommand};
use ecdsa::elliptic_curve::pkcs8::der::Encode;
use rust_util::util_clap::{Command, CommandError};
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;
@@ -14,25 +21,54 @@ impl Command for CommandImpl {
fn subcommand<'a>(&self) -> App<'a, 'a> {
SubCommand::with_name(self.name())
.about("External public key subcommand")
.arg(
Arg::with_name("parameter")
.long("parameter")
.takes_value(true)
.required(true)
.help("Parameter"),
)
.arg(cmdutil::build_parameter_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
let _parameter = sub_arg_matches.value_of("parameter").unwrap();
// TODO do get public key
let parameter = sub_arg_matches.value_of("parameter").unwrap();
let mut json = BTreeMap::new();
json.insert("success", Value::Bool(true));
json.insert("public_key_base64", "**".into());
match fetch_public_key(parameter) {
Ok(public_key_bytes) => {
json.insert("success", Value::Bool(true));
json.insert("public_key_base64", base64_encode(&public_key_bytes).into());
}
Err(e) => {
json.insert("success", Value::Bool(false));
json.insert("error", e.to_string().into());
}
}
util::print_pretty_json(&json);
Ok(None)
}
}
fn fetch_public_key(parameter: &str) -> XResult<Vec<u8>> {
let key_uri = parse_key_uri(parameter)?;
match key_uri {
KeyUri::SecureEnclaveKey(key) => {
if key.usage != KeyUsage::Singing {
simple_error!("Not singing key")
} else {
let (_, public_key_der, _) =
seutil::recover_secure_enclave_p256_public_key(&key.private_key, true)?;
Ok(public_key_der)
}
}
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());
}
}
simple_error!("Slot {} not found", key.slot)
}
}
}

View File

@@ -1,8 +1,16 @@
use crate::util;
use clap::{App, Arg, ArgMatches, SubCommand};
use crate::cmd_sign_jwt::digest_by_jwt_algorithm;
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage};
use crate::pivutil::ToStr;
use crate::util::{base64_decode, base64_encode};
use crate::{cmdutil, pivutil, seutil, util};
use clap::{App, ArgMatches, SubCommand};
use jwt::AlgorithmType;
use rust_util::util_clap::{Command, CommandError};
use rust_util::XResult;
use serde_json::Value;
use std::collections::BTreeMap;
use yubikey::piv::{sign_data, AlgorithmId};
use yubikey::YubiKey;
pub struct CommandImpl;
@@ -14,41 +22,85 @@ impl Command for CommandImpl {
fn subcommand<'a>(&self) -> App<'a, 'a> {
SubCommand::with_name(self.name())
.about("External sign subcommand")
.arg(
Arg::with_name("alg")
.long("alg")
.takes_value(true)
.required(true)
.help("Algorithm, e.g. RS256, RS384, RS512, ES256, ES384, ES512"),
)
.arg(
Arg::with_name("parameter")
.long("parameter")
.takes_value(true)
.required(true)
.help("Parameter"),
)
.arg(
Arg::with_name("message-base64")
.long("message-base64")
.takes_value(true)
.required(true)
.help("Message in base64"),
)
.arg(cmdutil::build_alg_arg())
.arg(cmdutil::build_parameter_arg())
.arg(cmdutil::build_message_arg())
.arg(cmdutil::build_pin_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
let _alg = sub_arg_matches.value_of("alg").unwrap();
let _parameter = sub_arg_matches.value_of("parameter").unwrap();
let _message_base64 = sub_arg_matches.value_of("message-base64").unwrap();
// TODO do sign
let mut json = BTreeMap::new();
json.insert("success", Value::Bool(true));
json.insert("signature_base64", "**".into());
match sign(sub_arg_matches) {
Ok(signature_bytes) => {
json.insert("success", Value::Bool(true));
json.insert("signature_base64", base64_encode(&signature_bytes).into());
}
Err(e) => {
json.insert("success", Value::Bool(false));
json.insert("error", e.to_string().into());
}
}
util::print_pretty_json(&json);
Ok(None)
}
}
fn sign(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
let alg = sub_arg_matches.value_of("alg").unwrap();
let parameter = sub_arg_matches.value_of("parameter").unwrap();
let message_base64 = sub_arg_matches.value_of("message-base64").unwrap();
let key_uri = parse_key_uri(parameter)?;
let message_bytes = base64_decode(message_base64)?;
match key_uri {
KeyUri::SecureEnclaveKey(key) => {
if key.usage != KeyUsage::Singing {
simple_error!("Not singing key")
} else {
Ok(seutil::secure_enclave_p256_sign(
&key.private_key,
&message_bytes,
)?)
}
}
KeyUri::YubikeyPivKey(key) => {
let mut yk = opt_result!(YubiKey::open(), "Find YubiKey failed: {}");
let pin_opt = pivutil::check_read_pin(&mut yk, key.slot, sub_arg_matches);
// FIXME Check Yubikey slot algorithm
let jwt_algorithm = match alg {
"ES256" => AlgorithmType::Es256,
"ES384" => AlgorithmType::Es384,
"RS256" => AlgorithmType::Rs256,
_ => return simple_error!("Invalid alg: {}", alg),
};
let is_p256_mismatch =
key.algorithm == AlgorithmId::EccP256 && jwt_algorithm != AlgorithmType::Es256;
let is_p384_mismatch =
key.algorithm == AlgorithmId::EccP384 && jwt_algorithm != AlgorithmType::Es384;
let is_rsa =
key.algorithm == AlgorithmId::Rsa1024 || key.algorithm == AlgorithmId::Rsa2048;
let is_rsa_mismatch = is_rsa && jwt_algorithm != AlgorithmType::Rs256;
if is_p256_mismatch || is_p384_mismatch || is_rsa_mismatch {
return simple_error!("Invalid algorithm: {} vs {}", key.algorithm.to_str(), alg);
}
let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?;
if let Some(pin) = pin_opt {
opt_result!(
yk.verify_pin(pin.as_bytes()),
"YubiKey verify pin failed: {}"
);
}
let signed_data = opt_result!(
sign_data(&mut yk, &raw_in, key.algorithm, key.slot),
"Sign YubiKey failed: {}"
);
Ok(signed_data.to_vec())
}
}
}

View File

@@ -10,6 +10,7 @@ use yubikey::{Key, YubiKey};
use yubikey::piv::{AlgorithmId, metadata};
use crate::{cmdutil, pivutil, util};
use crate::keyutil::{KeyUri, YubikeyPivKey};
use crate::pivutil::{get_algorithm_id_by_certificate, slot_equals, ToStr};
use crate::pkiutil::bytes_to_pem;
use crate::sshutil::SshVecWriter;
@@ -96,11 +97,18 @@ impl Command for CommandImpl {
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);
"ecdsa-sha2-nistp{} {} Yubikey-PIV-{}", ec_bit_len, base64_encode(ssh_public_key), slot_id);
json.insert("ssh_public_key", ssh_public_key_str.to_string());
}
_ => {}
}
let yubikey_piv_key = YubikeyPivKey {
key_name: format!("yubikey{}-{}", yk.version().major, yk.serial().0),
algorithm: algorithm_id,
slot: slot_id,
};
json.insert("key_uri", KeyUri::YubikeyPivKey(yubikey_piv_key).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 });

View File

@@ -1,4 +1,4 @@
use crate::keyutil::{parse_key_uri, KeyUri};
use crate::keyutil::parse_key_uri;
use crate::{cmdutil, seutil, util};
use clap::{App, Arg, ArgMatches, SubCommand};
use p256::elliptic_curve::sec1::FromEncodedPoint;
@@ -41,7 +41,8 @@ impl Command for CommandImpl {
let key = sub_arg_matches.value_of("key").unwrap();
let epk = sub_arg_matches.value_of("epk").unwrap();
let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(key)?;
let key_uri = parse_key_uri(key)?;
let se_key_uri = key_uri.as_secure_enclave_key()?;
debugging!("Secure enclave key URI: {:?}", se_key_uri);
let ephemeral_public_key_der_bytes = if epk.starts_with("04") {

View File

@@ -1,4 +1,4 @@
use crate::keyutil::{parse_key_uri, KeyUri};
use crate::keyutil::parse_key_uri;
use crate::{cmdutil, seutil, util};
use crate::util::{base64_decode, base64_encode};
use clap::{App, Arg, ArgMatches, SubCommand};
@@ -51,7 +51,8 @@ impl Command for CommandImpl {
Some(input) => input.as_bytes().to_vec(),
};
let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(key)?;
let key_uri = parse_key_uri(key)?;
let se_key_uri = key_uri.as_secure_enclave_key()?;
debugging!("Secure enclave key URI: {:?}", se_key_uri);
let signature = seutil::secure_enclave_p256_sign(&se_key_uri.private_key, &input_bytes)?;

View File

@@ -1,5 +1,5 @@
use crate::cmd_se_generate::print_se_key;
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage};
use crate::keyutil::{parse_key_uri, KeyUsage};
use crate::{cmdutil, seutil};
use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
@@ -28,9 +28,9 @@ impl Command for CommandImpl {
let json_output = cmdutil::check_json_output(sub_arg_matches);
seutil::check_se_supported()?;
let key_uri = sub_arg_matches.value_of("key").unwrap();
let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(key_uri)?;
let key = sub_arg_matches.value_of("key").unwrap();
let key_uri = parse_key_uri(key)?;
let se_key_uri = key_uri.as_secure_enclave_key()?;
debugging!("Secure enclave key URI: {:?}", se_key_uri);
let (public_key_point, public_key_der, _private_key) =
@@ -39,7 +39,7 @@ impl Command for CommandImpl {
se_key_uri.usage == KeyUsage::Singing,
)?;
print_se_key(json_output, &public_key_point, &public_key_der, key_uri);
print_se_key(json_output, &public_key_point, &public_key_der, key);
Ok(None)
}

View File

@@ -7,7 +7,7 @@ use serde_json::{Map, Value};
use crate::cmd_sign_jwt::{build_jwt_parts, merge_header_claims, merge_payload_claims, print_jwt_token};
use crate::ecdsautil::parse_ecdsa_to_rs;
use crate::keyutil::{parse_key_uri, KeyUri};
use crate::keyutil::parse_key_uri;
use crate::{cmd_sign_jwt, cmdutil, hmacutil, util};
use crate::util::base64_decode;
@@ -35,7 +35,8 @@ impl Command for CommandImpl {
"Private key PKCS#8 DER base64 encoded or PEM"
);
let private_key = hmacutil::try_hmac_decrypt_to_string(private_key)?;
let KeyUri::SecureEnclaveKey(se_key_uri) = parse_key_uri(&private_key)?;
let key_uri = parse_key_uri(&private_key)?;
let se_key_uri = key_uri.as_secure_enclave_key()?;
debugging!("Secure enclave key URI: {:?}", se_key_uri);
let (header, payload, jwt_claims) = build_jwt_parts(sub_arg_matches)?;

View File

@@ -13,6 +13,18 @@ pub fn build_pin_arg() -> Arg<'static, 'static> {
Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user PIN")
}
pub fn build_alg_arg() -> Arg<'static, 'static> {
Arg::with_name("alg").long("alg").takes_value(true).required(true).help("Algorithm, e.g. RS256, ES256, ES384")
}
pub fn build_parameter_arg() -> Arg<'static, 'static> {
Arg::with_name("parameter").long("parameter").takes_value(true).required(true).help("Parameter")
}
pub fn build_message_arg() -> Arg<'static, 'static> {
Arg::with_name("message-base64").long("message-base64").takes_value(true).required(true).help("Message in base64")
}
pub fn build_no_pin_arg() -> Arg<'static, 'static> {
Arg::with_name("no-pin").long("no-pin").help("No PIN")
}

View File

@@ -1,6 +1,25 @@
use sha1::Sha1;
use sha2::{Digest, Sha256, Sha384, Sha512};
pub enum DigestAlgorithm {
Sha256,
#[allow(dead_code)]
Sha384,
}
impl DigestAlgorithm {
pub fn digest(&self, data: &[u8]) -> Vec<u8> {
match self {
DigestAlgorithm::Sha256 => sha256_bytes(data),
DigestAlgorithm::Sha384 => sha384_bytes(data),
}
}
pub fn digest_str(&self, s: &str) -> Vec<u8> {
self.digest(s.as_bytes())
}
}
pub fn sha256(input: &str) -> Vec<u8> {
sha256_bytes(input.as_bytes())
}

View File

@@ -1,29 +1,50 @@
use regex::Regex;
use rust_util::XResult;
use yubikey::piv::{AlgorithmId, SlotId};
use crate::pivutil::{ToStr, FromStr};
// reference: https://git.hatter.ink/hatter/card-cli/issues/6
#[derive(Debug)]
pub enum KeyUri {
SecureEnclaveKey(SecureEnclaveKey),
YubikeyPivKey(YubikeyPivKey),
}
// #[derive(Debug, PartialEq, Eq)]
// pub enum KeyModule {
// SecureEnclave,
// OpenPgpCard,
// PersonalIdentityVerification,
// }
//
// impl KeyModule {
// pub fn from(module: &str) -> Option<Self> {
// match module {
// "se" => Some(Self::SecureEnclave),
// "pgp" => Some(Self::OpenPgpCard),
// "piv" => Some(Self::PersonalIdentityVerification),
// _ => None,
// }
// }
// }
impl KeyUri {
pub fn as_secure_enclave_key(&self) -> XResult<&SecureEnclaveKey> {
match self {
KeyUri::SecureEnclaveKey(key) => Ok(key),
_ => simple_error!("Not a secure enclave key."),
}
}
}
impl ToString for KeyUri {
fn to_string(&self) -> String {
let mut key_uri = String::with_capacity(64);
key_uri.push_str("key://");
match self {
// key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)
// key://hatter-mac-pro:se/p256:key_agreement:BASE64(dataRepresentation)
KeyUri::SecureEnclaveKey(key) => {
key_uri.push_str(&key.host);
key_uri.push_str(":se/p256:");
key_uri.push_str(&key.usage.to_string());
key_uri.push_str(":");
key_uri.push_str(&key.private_key);
}
// key://yubikey-5n:piv/p256:*:9a
KeyUri::YubikeyPivKey(key) => {
key_uri.push_str(&key.key_name);
key_uri.push_str(":piv/");
key_uri.push_str(key.algorithm.to_str());
key_uri.push_str("::");
key_uri.push_str(key.slot.to_str());
}
}
key_uri
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum KeyUsage {
@@ -43,6 +64,16 @@ impl KeyUsage {
}
}
impl ToString for KeyUsage {
fn to_string(&self) -> String {
match self {
KeyUsage::Any => "*",
KeyUsage::Singing => "signing",
KeyUsage::KeyAgreement => "key_agreement"
}.to_string()
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct SecureEnclaveKey {
@@ -51,64 +82,110 @@ pub struct SecureEnclaveKey {
pub private_key: String,
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct YubikeyPivKey {
pub key_name: String,
pub algorithm: AlgorithmId,
pub slot: SlotId,
}
pub fn parse_key_uri(key_uri: &str) -> XResult<KeyUri> {
let regex = Regex::new(r##"^key://([a-zA-Z\-\._]*):(\w+)/(\w+):(\w+)?:(.*)$"##).unwrap();
let regex = Regex::new(r##"^key://([0-9a-zA-Z\-\._]*):(\w+)/(\w+):((?:\w+)?):(.*)$"##).unwrap();
let captures = match regex.captures(key_uri) {
None => return simple_error!("Invalid key uri: {}", key_uri),
Some(captures) => captures,
};
let host = captures.get(1).unwrap().as_str();
let host_or_name = captures.get(1).unwrap().as_str();
let module = captures.get(2).unwrap().as_str();
let algorithm = captures.get(3).unwrap().as_str();
let usage = captures.get(4).unwrap().as_str();
let left_part = captures.get(5).unwrap().as_str();
if "se" != module {
return simple_error!("Key uri's module must be se.");
}
if "p256" != algorithm {
return simple_error!("Key uri's algorithm must be p256.");
}
let key_usage = match KeyUsage::from(usage) {
None | Some(KeyUsage::Any) => {
return simple_error!("Key uri's usage must be signing or key_agreement.")
match module {
"se" => {
if "p256" != algorithm {
return simple_error!("Key uri's algorithm must be p256.");
}
let key_usage = match KeyUsage::from(usage) {
None | Some(KeyUsage::Any) => {
return simple_error!("Key uri's usage must be signing or key_agreement.")
}
Some(key_usage) => key_usage,
};
let parsed_key_uri = KeyUri::SecureEnclaveKey(SecureEnclaveKey {
host: host_or_name.to_string(),
usage: key_usage,
private_key: left_part.to_string(),
});
debugging!("Parsed key uri: {:?}", parsed_key_uri);
Ok(parsed_key_uri)
}
Some(key_usage) => key_usage,
};
let parsed_key_uri = KeyUri::SecureEnclaveKey(SecureEnclaveKey {
host: host.to_string(),
usage: key_usage,
private_key: left_part.to_string(),
});
debugging!("Parsed key uri: {:?}", parsed_key_uri);
Ok(parsed_key_uri)
"piv" => {
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 slot = opt_value_result!(SlotId::from_str(left_part), "Invalid slot id: {}", left_part);
let parsed_key_uri = KeyUri::YubikeyPivKey(YubikeyPivKey {
key_name: host_or_name.to_string(),
algorithm,
slot,
});
debugging!("Parsed key uri: {:?}", parsed_key_uri);
Ok(parsed_key_uri)
}
_ => simple_error!("Key uri's module must be se."),
}
}
#[test]
fn test_parse_key_uri_01() {
let se_key_uri =
parse_key_uri("key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)").unwrap();
assert_eq!("key://hatter-mac-pro:se/p256:signing:BASE64(dataRepresentation)", se_key_uri.to_string());
match se_key_uri {
KeyUri::SecureEnclaveKey(se_key_uri) => {
assert_eq!("hatter-mac-pro", se_key_uri.host);
assert_eq!(KeyUsage::Singing, se_key_uri.usage);
assert_eq!("BASE64(dataRepresentation)", se_key_uri.private_key);
}
_ => {
panic!("Key uri not parsed")
}
}
}
#[test]
fn test_parse_key_uri_02() {
let se_key_uri =
parse_key_uri("key://hatter-mac-pro:se/p256:key_agreement:BASE64(dataRepresentation)")
parse_key_uri("key://hatter-mac-m1:se/p256:key_agreement:BASE64(dataRepresentation)")
.unwrap();
assert_eq!("key://hatter-mac-m1:se/p256:key_agreement:BASE64(dataRepresentation)", se_key_uri.to_string());
match se_key_uri {
KeyUri::SecureEnclaveKey(se_key_uri) => {
assert_eq!("hatter-mac-pro", se_key_uri.host);
assert_eq!("hatter-mac-m1", se_key_uri.host);
assert_eq!(KeyUsage::KeyAgreement, se_key_uri.usage);
assert_eq!("BASE64(dataRepresentation)", se_key_uri.private_key);
}
_ => {
panic!("Key uri not parsed")
}
}
}
#[test]
fn test_parse_key_uri_03() {
let se_key_uri = parse_key_uri("key://yubikey-5n:piv/p256::9a").unwrap();
assert_eq!("key://yubikey-5n:piv/p256::authentication", se_key_uri.to_string());
match se_key_uri {
KeyUri::YubikeyPivKey(piv_key_uri) => {
assert_eq!("yubikey-5n", piv_key_uri.key_name);
assert_eq!(AlgorithmId::EccP256, piv_key_uri.algorithm);
assert_eq!(SlotId::Authentication, piv_key_uri.slot);
}
_ => {
panic!("Key uri not parsed")
}
}
}

View File

@@ -56,6 +56,12 @@ pub trait ToStr {
fn to_str(&self) -> &str;
}
pub trait FromStr {
fn from_str(s: &str) -> Option<Self>
where
Self: Sized;
}
impl ToStr for PinPolicy {
fn to_str(&self) -> &str {
match self {
@@ -78,6 +84,21 @@ impl ToStr for TouchPolicy {
}
}
impl FromStr for AlgorithmId {
fn from_str(s: &str) -> Option<Self>
where
Self: Sized,
{
match s {
"rsa1024" => Some(AlgorithmId::Rsa1024),
"rsa2048" => Some(AlgorithmId::Rsa2048),
"p256" => Some(AlgorithmId::EccP256),
"p384" => Some(AlgorithmId::EccP384),
_ => None,
}
}
}
impl ToStr for AlgorithmId {
fn to_str(&self) -> &str {
match self {
@@ -194,6 +215,54 @@ pub fn get_slot_id(slot: &str) -> XResult<SlotId> {
})
}
impl FromStr for SlotId {
fn from_str(s: &str) -> Option<Self>
where
Self: Sized,
{
get_slot_id(s).ok()
}
}
impl ToStr for SlotId {
fn to_str(&self) -> &str {
match self {
SlotId::Authentication => "authentication",
SlotId::Signature => "signature",
SlotId::KeyManagement => "keymanagement",
SlotId::CardAuthentication => "cardauthentication",
SlotId::Retired(retried) => match retried {
RetiredSlotId::R1 => "r1",
RetiredSlotId::R2 => "r2",
RetiredSlotId::R3 => "r3",
RetiredSlotId::R4 => "r4",
RetiredSlotId::R5 => "r5",
RetiredSlotId::R6 => "r6",
RetiredSlotId::R7 => "r7",
RetiredSlotId::R8 => "r8",
RetiredSlotId::R9 => "r9",
RetiredSlotId::R10 => "r10",
RetiredSlotId::R11 => "r11",
RetiredSlotId::R12 => "r12",
RetiredSlotId::R13 => "r13",
RetiredSlotId::R14 => "r14",
RetiredSlotId::R15 => "r15",
RetiredSlotId::R16 => "r16",
RetiredSlotId::R17 => "r17",
RetiredSlotId::R18 => "r18",
RetiredSlotId::R19 => "r19",
RetiredSlotId::R20 => "r20",
}
SlotId::Attestation => "attestation",
SlotId::Management(management) => match management {
ManagementSlotId::Pin => "pin",
ManagementSlotId::Puk => "puk",
ManagementSlotId::Management => "management",
}
}
}
}
pub fn check_read_pin(yk: &mut YubiKey, slot_id: SlotId, sub_arg_matches: &ArgMatches) -> Option<String> {
if never_use_pin(yk, slot_id) {
None