feat: v1.12.9

This commit is contained in:
2025-05-06 22:35:03 +08:00
parent 57c3ec57df
commit 63fabc6054
15 changed files with 112 additions and 62 deletions

2
Cargo.lock generated
View File

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

View File

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

View File

@@ -1,7 +1,7 @@
use crate::keyutil::{parse_key_uri, KeyUri, KeyUsage};
use crate::util::{base64_decode, base64_encode};
use crate::yubikeyutil::find_key_or_error;
use crate::{cmdutil, ecdsautil, hmacutil, seutil, util, yubikeyutil};
use crate::{cmd_hmac_decrypt, cmdutil, ecdsautil, seutil, util, yubikeyutil};
use clap::{App, ArgMatches, SubCommand};
use ecdsa::elliptic_curve::pkcs8::der::Encode;
use rust_util::util_clap::{Command, CommandError};
@@ -72,7 +72,7 @@ fn fetch_public_key(parameter: &str, serial_opt: &Option<&str>) -> XResult<Vec<u
}
KeyUri::YubikeyHmacEncSoftKey(key) => {
if key.algorithm.is_ecc() {
let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?;
let private_key = cmd_hmac_decrypt::try_hmac_decrypt(&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();
let p521_public_key = ecdsautil::parse_p521_private_key_to_public_key(&private_key).ok();
@@ -89,7 +89,7 @@ fn fetch_public_key(parameter: &str, serial_opt: &Option<&str>) -> XResult<Vec<u
simple_error!("Invalid hmac enc private key")
} else if key.algorithm.is_rsa() {
use rsa::pkcs8::DecodePrivateKey;
let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?;
let private_key = cmd_hmac_decrypt::try_hmac_decrypt(&key.hmac_enc_private_key)?;
let private_key_der = base64_decode(&private_key)?;
let rsa_private_key = RsaPrivateKey::from_pkcs8_der(&private_key_der)?;
Ok(rsa_private_key.to_public_key().to_public_key_der()?.to_vec())

View File

@@ -5,7 +5,7 @@ use crate::keyutil::{parse_key_uri, KeyAlgorithmId, KeyUri, KeyUsage, YubikeyPiv
use crate::pivutil::ToStr;
use crate::rsautil::RsaSignAlgorithm;
use crate::util::{base64_decode, base64_encode};
use crate::{cmdutil, ecdsautil, hmacutil, pivutil, rsautil, seutil, util, yubikeyutil};
use crate::{cmd_hmac_decrypt, cmdutil, ecdsautil, pivutil, rsautil, seutil, util, yubikeyutil};
use clap::{App, ArgMatches, SubCommand};
use jwt::AlgorithmType;
use rsa::RsaPrivateKey;
@@ -95,7 +95,7 @@ fn sign(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
}
KeyUri::YubikeyHmacEncSoftKey(key) => {
if key.algorithm.is_ecc() {
let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?;
let private_key = cmd_hmac_decrypt::try_hmac_decrypt(&key.hmac_enc_private_key)?;
let (jwt_algorithm, private_key_d) = parse_ecdsa_private_key(&private_key)?;
let raw_in = digest_by_jwt_algorithm(jwt_algorithm, &message_bytes)?;
@@ -110,7 +110,7 @@ fn sign(sub_arg_matches: &ArgMatches) -> XResult<Vec<u8>> {
Ok(signed_data)
} else if key.algorithm.is_rsa() {
use rsa::pkcs8::DecodePrivateKey;
let private_key = hmacutil::try_hmac_decrypt_to_string(&key.hmac_enc_private_key)?;
let private_key = cmd_hmac_decrypt::try_hmac_decrypt(&key.hmac_enc_private_key)?;
let private_key_der = base64_decode(&private_key)?;
let rsa_private_key = RsaPrivateKey::from_pkcs8_der(&private_key_der)?;

View File

@@ -1,8 +1,9 @@
use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
use std::collections::BTreeMap;
use crate::{cmdutil, hmacutil, pbeutil, util};
use rust_util::XResult;
use crate::{cmdutil, pbeutil, util};
use crate::hmacutil::{hmac_decrypt_to_string, is_hmac_encrypted};
pub struct CommandImpl;
@@ -29,12 +30,9 @@ impl Command for CommandImpl {
let json_output = cmdutil::check_json_output(sub_arg_matches);
let ciphertext = sub_arg_matches.value_of("ciphertext").unwrap();
let mut text = hmacutil::hmac_decrypt_to_string(ciphertext)?;
let auto_pbe = sub_arg_matches.is_present("auto-pbe");
if auto_pbe && pbeutil::is_simple_pbe_encrypted(&text) {
text = pbeutil::simple_pbe_decrypt_with_prompt_to_string(&text)?;
}
let text = hmac_decrypt(ciphertext, auto_pbe)?;
if json_output {
let mut json = BTreeMap::<&'_ str, String>::new();
@@ -48,3 +46,20 @@ impl Command for CommandImpl {
Ok(None)
}
}
pub fn try_hmac_decrypt(ciphertext: &str) -> XResult<String> {
if is_hmac_encrypted(ciphertext) {
hmac_decrypt(ciphertext, true)
} else {
Ok(ciphertext.to_string())
}
}
pub fn hmac_decrypt(ciphertext: &str, auto_pbe: bool) -> XResult<String> {
let text = hmac_decrypt_to_string(ciphertext)?;
if auto_pbe && pbeutil::is_simple_pbe_encrypted(&text) {
Ok(pbeutil::simple_pbe_decrypt_with_prompt_to_string(&text)?)
} else {
Ok(text)
}
}

View File

@@ -1,7 +1,7 @@
use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
use std::collections::BTreeMap;
use rust_util::XResult;
use crate::{cmdutil, hmacutil, pbeutil, util};
pub struct CommandImpl;
@@ -21,24 +21,17 @@ impl Command for CommandImpl {
.required(true)
.help("Plaintext"),
)
.arg(Arg::with_name("with-pbe").long("with-pbe").help("With PBE encryption"))
.arg(Arg::with_name("double-pin-check").long("double-pin-check").help("Double PIN check"))
.arg(Arg::with_name("pbe-iteration").long("pbe-iteration").takes_value(true).help("PBE iteration, default 100000"))
.arg(cmdutil::build_with_pbe_encrypt_arg())
.arg(cmdutil::build_double_pin_check_arg())
.arg(cmdutil::build_pbe_iteration_arg())
.arg(cmdutil::build_json_arg())
}
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
let json_output = cmdutil::check_json_output(sub_arg_matches);
let mut text = sub_arg_matches.value_of("plaintext").unwrap().to_string();
let with_pbe = sub_arg_matches.is_present("with-pbe");
if with_pbe {
let double_pin_check = sub_arg_matches.is_present("double-pin-check");
let iteration = sub_arg_matches.value_of("pbe-iteration")
.map(|x| x.parse::<u32>().unwrap()).unwrap_or(100000);
text = pbeutil::simple_pbe_encrypt_with_prompt_from_string(iteration, &text, double_pin_check)?;
}
let hmac_encrypt_ciphertext = hmacutil::hmac_encrypt_from_string(&text)?;
let text = sub_arg_matches.value_of("plaintext").unwrap().to_string();
let hmac_encrypt_ciphertext = hmac_encrypt(&text, &mut None, sub_arg_matches)?;
if json_output {
let mut json = BTreeMap::<&'_ str, String>::new();
@@ -52,3 +45,16 @@ impl Command for CommandImpl {
Ok(None)
}
}
pub fn hmac_encrypt(text: &str, password_opt: &mut Option<String>, sub_arg_matches: &ArgMatches) -> XResult<String> {
let with_pbe_encrypt = sub_arg_matches.is_present("with-pbe-encrypt");
let text = if with_pbe_encrypt {
let double_pin_check = sub_arg_matches.is_present("double-pin-check");
let iteration = sub_arg_matches.value_of("pbe-iteration")
.map(|x| x.parse::<u32>().unwrap()).unwrap_or(100000);
pbeutil::simple_pbe_encrypt_with_prompt_from_string(iteration, &text, password_opt, double_pin_check)?
} else {
text.to_string()
};
Ok(hmacutil::hmac_encrypt_from_string(&text)?)
}

View File

@@ -2,7 +2,7 @@ use crate::ecdsautil::EcdsaAlgorithm;
use crate::keychain::{KeychainKey, KeychainKeyValue};
use crate::keyutil::{KeyAlgorithmId, KeyUri, YubikeyHmacEncSoftKey};
use crate::util::base64_encode;
use crate::{cmdutil, ecdsautil, hmacutil, rsautil, util, yubikeyutil};
use crate::{cmd_hmac_encrypt, cmdutil, ecdsautil, rsautil, util, yubikeyutil};
use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
use std::collections::BTreeMap;
@@ -24,11 +24,10 @@ impl Command for CommandImpl {
.takes_value(true)
.help("Key type (e.g. p256, p384, p521, rsa1024, rsa2048, rsa3072, rsa4096)"),
)
.arg(
Arg::with_name("with-hmac-encrypt")
.long("with-hmac-encrypt")
.help("With HMAC encrypt"),
)
.arg(cmdutil::build_with_hmac_encrypt_arg())
.arg(cmdutil::build_with_pbe_encrypt_arg())
.arg(cmdutil::build_double_pin_check_arg())
.arg(cmdutil::build_pbe_iteration_arg())
.arg(cmdutil::build_keychain_name_arg())
.arg(cmdutil::build_json_arg())
}
@@ -71,10 +70,11 @@ impl Command for CommandImpl {
return simple_error!("Unsupported key type: {}", key_type);
};
let mut password_opt = None;
let (pkcs8_base64, secret_key_pem) = if with_hmac_encrypt {
(
hmacutil::hmac_encrypt_from_string(&pkcs8_base64)?,
hmacutil::hmac_encrypt_from_string(&secret_key_pem)?,
cmd_hmac_encrypt::hmac_encrypt(&pkcs8_base64, &mut password_opt, sub_arg_matches)?,
cmd_hmac_encrypt::hmac_encrypt(&secret_key_pem, &mut password_opt, sub_arg_matches)?,
)
} else {
(pkcs8_base64, secret_key_pem)

View File

@@ -1,5 +1,5 @@
use crate::keyutil::parse_key_uri;
use crate::{cmdutil, seutil, util};
use crate::{cmd_hmac_decrypt, cmdutil, seutil, util};
use clap::{App, Arg, ArgMatches, SubCommand};
use p256::elliptic_curve::sec1::FromEncodedPoint;
use p256::{EncodedPoint, PublicKey};
@@ -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 key_uri = parse_key_uri(key)?;
let key = cmd_hmac_decrypt::try_hmac_decrypt(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);

View File

@@ -1,5 +1,5 @@
use crate::keyutil::parse_key_uri;
use crate::{cmdutil, seutil, util};
use crate::{cmd_hmac_decrypt, cmdutil, seutil, util};
use crate::util::{base64_decode, base64_encode};
use clap::{App, Arg, ArgMatches, SubCommand};
use rust_util::util_clap::{Command, CommandError};
@@ -51,7 +51,8 @@ impl Command for CommandImpl {
Some(input) => input.as_bytes().to_vec(),
};
let key_uri = parse_key_uri(key)?;
let key = cmd_hmac_decrypt::try_hmac_decrypt(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);

View File

@@ -6,6 +6,7 @@ use p256::PublicKey;
use rust_util::util_clap::{Command, CommandError};
use spki::DecodePublicKey;
use std::collections::BTreeMap;
use crate::cmd_hmac_encrypt::hmac_encrypt;
pub struct CommandImpl;
@@ -36,6 +37,10 @@ impl Command for CommandImpl {
.long("disable-bio")
.help("Disable bio"),
)
.arg(cmdutil::build_with_hmac_encrypt_arg())
.arg(cmdutil::build_with_pbe_encrypt_arg())
.arg(cmdutil::build_double_pin_check_arg())
.arg(cmdutil::build_pbe_iteration_arg())
.arg(cmdutil::build_json_arg())
}
@@ -62,6 +67,13 @@ impl Command for CommandImpl {
private_key,
);
let with_hmac_encrypt = sub_arg_matches.is_present("with-hmac-encrypt");
let key_uri = if with_hmac_encrypt {
hmac_encrypt(&key_uri, &mut None, sub_arg_matches)?
} else {
key_uri
};
print_se_key(json_output, &public_key_point, &public_key_der, &key_uri);
Ok(None)
}

View File

@@ -8,7 +8,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;
use crate::{cmd_sign_jwt, cmdutil, hmacutil, util};
use crate::{cmd_hmac_decrypt, cmd_sign_jwt, cmdutil, util};
use crate::util::base64_decode;
const SEPARATOR: &str = ".";
@@ -34,7 +34,7 @@ impl Command for CommandImpl {
sub_arg_matches.value_of("key"),
"Private key PKCS#8 DER base64 encoded or PEM"
);
let private_key = hmacutil::try_hmac_decrypt_to_string(private_key)?;
let private_key = cmd_hmac_decrypt::try_hmac_decrypt(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);

View File

@@ -6,7 +6,7 @@ use serde_json::{Map, Value};
use crate::cmd_sign_jwt::{build_jwt_parts, digest_by_jwt_algorithm, merge_header_claims, merge_payload_claims, print_jwt_token};
use crate::keychain::{KeychainKey, KeychainKeyValue};
use crate::{cmd_sign_jwt, cmdutil, ecdsautil, hmacutil, keychain, util};
use crate::{cmd_hmac_decrypt, cmd_sign_jwt, cmdutil, ecdsautil, keychain, util};
use crate::ecdsautil::{EcdsaAlgorithm, EcdsaSignType};
const SEPARATOR: &str = ".";
@@ -33,7 +33,7 @@ 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 private_key = cmd_hmac_decrypt::try_hmac_decrypt(private_key)?;
let private_key = if keychain::is_keychain_key_uri(&private_key) {
debugging!("Private key keychain key URI: {}", &private_key);

View File

@@ -9,6 +9,22 @@ pub fn build_slot_arg() -> Arg<'static, 'static> {
.help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")
}
pub fn build_with_hmac_encrypt_arg() -> Arg<'static, 'static> {
Arg::with_name("with-hmac-encrypt").long("with-hmac-encrypt").help("With HMAC encrypt")
}
pub fn build_with_pbe_encrypt_arg() -> Arg<'static, 'static> {
Arg::with_name("with-pbe-encrypt").long("with-pbe-encrypt").help("With PBE encryption")
}
pub fn build_double_pin_check_arg() -> Arg<'static, 'static> {
Arg::with_name("double-pin-check").long("double-pin-check").help("Double PIN check")
}
pub fn build_pbe_iteration_arg() -> Arg<'static, 'static> {
Arg::with_name("pbe-iteration").long("pbe-iteration").takes_value(true).help("PBE iteration, default 100000")
}
pub fn build_serial_arg() -> Arg<'static, 'static> {
Arg::with_name("serial").long("serial").takes_value(true).help("Serial number")
}

View File

@@ -44,14 +44,6 @@ pub fn is_hmac_encrypted(ciphertext: &str) -> bool {
ciphertext.starts_with(HMAC_ENC_PREFIX)
}
pub fn try_hmac_decrypt_to_string(ciphertext: &str) -> XResult<String> {
if is_hmac_encrypted(ciphertext) {
hmac_decrypt_to_string(ciphertext)
} else {
Ok(ciphertext.to_string())
}
}
pub fn hmac_decrypt_to_string(ciphertext: &str) -> XResult<String> {
let plaintext = hmac_decrypt(ciphertext)?;
Ok(String::from_utf8(plaintext)?)

View File

@@ -7,8 +7,8 @@ use rust_util::XResult;
const PBE_ENC_PREFIX: &str = "pbe_enc:";
pub fn simple_pbe_encrypt_with_prompt_from_string(iteration: u32, plaintext: &str, password_double_check: bool) -> XResult<String> {
simple_pbe_encrypt_with_prompt(iteration, plaintext.as_bytes(), password_double_check)
pub fn simple_pbe_encrypt_with_prompt_from_string(iteration: u32, plaintext: &str, passowrd: &mut Option<String>, password_double_check: bool) -> XResult<String> {
simple_pbe_encrypt_with_prompt(iteration, plaintext.as_bytes(), passowrd, password_double_check)
}
pub fn simple_pbe_decrypt_with_prompt_to_string(ciphertext: &str) -> XResult<String> {
@@ -16,7 +16,9 @@ pub fn simple_pbe_decrypt_with_prompt_to_string(ciphertext: &str) -> XResult<Str
Ok(String::from_utf8(plaintext)?)
}
pub fn simple_pbe_encrypt_with_prompt(iteration: u32, plaintext: &[u8], password_double_check: bool) -> XResult<String> {
pub fn simple_pbe_encrypt_with_prompt(iteration: u32, plaintext: &[u8], password_opt: &mut Option<String>, password_double_check: bool) -> XResult<String> {
let pin = match password_opt {
None => {
let pin1 = opt_value_result!(pinutil::get_pin(None), "Simple PBE password required");
if password_double_check {
let pin2 = opt_value_result!(pinutil::get_pin(None), "Simple PBE password required");
@@ -24,7 +26,12 @@ pub fn simple_pbe_encrypt_with_prompt(iteration: u32, plaintext: &[u8], password
return simple_error!("Two PINs mismatch");
}
}
simple_pbe_encrypt(&pin1, iteration, plaintext)
*password_opt = Some(pin1.clone());
pin1
}
Some(pin) => pin.clone(),
};
simple_pbe_encrypt(&pin, iteration, plaintext)
}
pub fn simple_pbe_decrypt_with_prompt(ciphertext: &str) -> XResult<Vec<u8>> {