diff --git a/Cargo.lock b/Cargo.lock index 898ae45..ed599b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 26717d4..09a6cb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.12.8" +version = "1.12.9" authors = ["Hatter Jiang "] edition = "2018" diff --git a/src/cmd_external_public_key.rs b/src/cmd_external_public_key.rs index 2f64d5d..d8bfc03 100644 --- a/src/cmd_external_public_key.rs +++ b/src/cmd_external_public_key.rs @@ -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 { 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 XResult> { } 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> { 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)?; diff --git a/src/cmd_hmac_decrypt.rs b/src/cmd_hmac_decrypt.rs index c8bc792..d2182d3 100644 --- a/src/cmd_hmac_decrypt.rs +++ b/src/cmd_hmac_decrypt.rs @@ -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 { + if is_hmac_encrypted(ciphertext) { + hmac_decrypt(ciphertext, true) + } else { + Ok(ciphertext.to_string()) + } +} + +pub fn hmac_decrypt(ciphertext: &str, auto_pbe: bool) -> XResult { + 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) + } +} diff --git a/src/cmd_hmac_encrypt.rs b/src/cmd_hmac_encrypt.rs index 0b02e52..c4555b2 100644 --- a/src/cmd_hmac_encrypt.rs +++ b/src/cmd_hmac_encrypt.rs @@ -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::().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, sub_arg_matches: &ArgMatches) -> XResult { + 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::().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)?) +} diff --git a/src/cmd_keypair_generate.rs b/src/cmd_keypair_generate.rs index 95c509e..08b7b98 100644 --- a/src/cmd_keypair_generate.rs +++ b/src/cmd_keypair_generate.rs @@ -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) diff --git a/src/cmd_se_ecdh.rs b/src/cmd_se_ecdh.rs index ac69acb..cd29185 100644 --- a/src/cmd_se_ecdh.rs +++ b/src/cmd_se_ecdh.rs @@ -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); diff --git a/src/cmd_se_ecsign.rs b/src/cmd_se_ecsign.rs index 8acbe7e..269b241 100644 --- a/src/cmd_se_ecsign.rs +++ b/src/cmd_se_ecsign.rs @@ -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); diff --git a/src/cmd_se_generate.rs b/src/cmd_se_generate.rs index 9bef44a..2bfdc7d 100644 --- a/src/cmd_se_generate.rs +++ b/src/cmd_se_generate.rs @@ -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) } diff --git a/src/cmd_sign_jwt_se.rs b/src/cmd_sign_jwt_se.rs index 33fac85..8f6aed1 100644 --- a/src/cmd_sign_jwt_se.rs +++ b/src/cmd_sign_jwt_se.rs @@ -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); diff --git a/src/cmd_sign_jwt_soft.rs b/src/cmd_sign_jwt_soft.rs index 6a50290..86f194b 100644 --- a/src/cmd_sign_jwt_soft.rs +++ b/src/cmd_sign_jwt_soft.rs @@ -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); diff --git a/src/cmdutil.rs b/src/cmdutil.rs index 612015e..3e32c3c 100644 --- a/src/cmdutil.rs +++ b/src/cmdutil.rs @@ -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") } diff --git a/src/hmacutil.rs b/src/hmacutil.rs index eb3eb04..de4edb8 100644 --- a/src/hmacutil.rs +++ b/src/hmacutil.rs @@ -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 { - if is_hmac_encrypted(ciphertext) { - hmac_decrypt_to_string(ciphertext) - } else { - Ok(ciphertext.to_string()) - } -} - pub fn hmac_decrypt_to_string(ciphertext: &str) -> XResult { let plaintext = hmac_decrypt(ciphertext)?; Ok(String::from_utf8(plaintext)?) diff --git a/src/pbeutil.rs b/src/pbeutil.rs index 0919946..30bb991 100644 --- a/src/pbeutil.rs +++ b/src/pbeutil.rs @@ -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 { - 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, password_double_check: bool) -> XResult { + 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 { @@ -16,15 +16,22 @@ pub fn simple_pbe_decrypt_with_prompt_to_string(ciphertext: &str) -> XResult XResult { - 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"); - if pin1 != pin2 { - return simple_error!("Two PINs mismatch"); +pub fn simple_pbe_encrypt_with_prompt(iteration: u32, plaintext: &[u8], password_opt: &mut Option, password_double_check: bool) -> XResult { + 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"); + if pin1 != pin2 { + return simple_error!("Two PINs mismatch"); + } + } + *password_opt = Some(pin1.clone()); + pin1 } - } - simple_pbe_encrypt(&pin1, iteration, plaintext) + Some(pin) => pin.clone(), + }; + simple_pbe_encrypt(&pin, iteration, plaintext) } pub fn simple_pbe_decrypt_with_prompt(ciphertext: &str) -> XResult> {