diff --git a/Cargo.lock b/Cargo.lock index 0825682..57805b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -368,7 +368,7 @@ dependencies = [ [[package]] name = "card-cli" -version = "1.9.3" +version = "1.9.4" dependencies = [ "authenticator", "base64 0.21.7", diff --git a/Cargo.toml b/Cargo.toml index effca04..dd0bd0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "card-cli" -version = "1.9.3" +version = "1.9.4" authors = ["Hatter Jiang "] edition = "2018" diff --git a/README.md b/README.md index 43eb04d..83c778c 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,19 @@ hW53WfImja+b5kwwyqUikyMCAwEAAQ== encrypt ``` $ openssl rsautl -encrypt -pubin -inkey enc_key.pem -in test.txt -out enc.txt -pkcs + +OR + +$ openssl pkeyutl -encrypt -inkey enc_key.pem -pubin -in a.txt -out enc.txt ``` decrypt ``` $ card-cli pgp-card-decrypt -c $(cat enc.txt | xxd -ps -c 11111) + +OR + +$ card-cli piv-decrypt -s r3 -c "$(cat enc.txt | base64)" ``` ## sign & verify diff --git a/src/cmd_pgpageaddress.rs b/src/cmd_pgpageaddress.rs index 7fdb8ba..1045b2f 100644 --- a/src/cmd_pgpageaddress.rs +++ b/src/cmd_pgpageaddress.rs @@ -14,7 +14,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "pgp-age-address" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("OpenPGP Card Encryption key to age address") + SubCommand::with_name(self.name()).about("OpenPGP Card encryption key to age address") } fn run(&self, _arg_matches: &ArgMatches, _sub_arg_matches: &ArgMatches) -> CommandError { diff --git a/src/cmd_pgpcardadmin.rs b/src/cmd_pgpcardadmin.rs index 6aa667d..9cfe64c 100644 --- a/src/cmd_pgpcardadmin.rs +++ b/src/cmd_pgpcardadmin.rs @@ -10,7 +10,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "pgp-card-admin" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("OpenPGP Card Admin subcommand") + SubCommand::with_name(self.name()).about("OpenPGP Card admin subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("12345678").help("OpenPGP card admin pin")) .arg(Arg::with_name("pass").long("pass").takes_value(true).help("[deprecated] now OpenPGP card admin pin")) .arg(Arg::with_name("name").short("n").long("name").takes_value(true).required(false).help("Set name")) diff --git a/src/cmd_pgpcarddecrypt.rs b/src/cmd_pgpcarddecrypt.rs index de2a300..0a2cdab 100644 --- a/src/cmd_pgpcarddecrypt.rs +++ b/src/cmd_pgpcarddecrypt.rs @@ -6,7 +6,7 @@ use rust_util::{util_msg, XResult}; use rust_util::util_clap::{Command, CommandError}; use crate::pgpcardutil; -use crate::util::{base64_decode, base64_encode}; +use crate::util::{base64_encode, try_decode}; #[derive(Debug, Clone, Copy)] enum EncryptAlgo { @@ -30,11 +30,10 @@ impl Command for CommandImpl { fn name(&self) -> &str { "pgp-card-decrypt" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("OpenPGP Card Decrypt subcommand") + SubCommand::with_name(self.name()).about("OpenPGP Card decrypt subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").help("OpenPGP card user pin")) .arg(Arg::with_name("pass").long("pass").takes_value(true).help("[deprecated] now OpenPGP card user pin")) - .arg(Arg::with_name("cipher").short("c").long("cipher").takes_value(true).help("Cipher text HEX")) - .arg(Arg::with_name("cipher-base64").short("b").long("cipher-base64").takes_value(true).help("Cipher text base64")) + .arg(Arg::with_name("ciphertext").short("c").long("ciphertext").takes_value(true).help("Cipher text (HEX or Base64)")) .arg(Arg::with_name("algo").long("algo").takes_value(true).help("Algo: RSA, X25519/ECDH")) .arg(Arg::with_name("json").long("json").help("JSON output")) } @@ -47,18 +46,15 @@ impl Command for CommandImpl { let pin = opt_value_result!(pin_opt, "User pin must be assigned"); if pin.len() < 6 { return simple_error!("User pin length:{}, must >= 6!", pin.len()); } - let cipher = sub_arg_matches.value_of("cipher"); - let cipher_base64 = sub_arg_matches.value_of("cipher-base64"); + let ciphertext = sub_arg_matches.value_of("ciphertext"); let algo = sub_arg_matches.value_of("algo").unwrap_or("rsa").to_lowercase(); let algo = EncryptAlgo::from_str(&algo)?; - let cipher_bytes = if let Some(cipher) = cipher { - opt_result!(hex::decode(cipher), "Decode cipher failed: {}") - } else if let Some(cipher_base64) = cipher_base64 { - opt_result!(base64_decode(cipher_base64), "Decode cipher-base64 failed: {}") + let ciphertext_bytes = if let Some(ciphertext) = ciphertext { + opt_result!(try_decode(ciphertext), "Decode cipher failed: {}") } else { - return simple_error!("cipher or cipher-base64 must assign one"); + return simple_error!("--ciphertext must be assigned"); }; let mut pgp = pgpcardutil::get_openpgp_card()?; @@ -68,8 +64,8 @@ impl Command for CommandImpl { success!("User pin verify success!"); let text = match algo { - EncryptAlgo::Rsa => trans.decipher(Cryptogram::RSA(&cipher_bytes))?, - EncryptAlgo::Ecdh => trans.decipher(Cryptogram::ECDH(&cipher_bytes))?, + EncryptAlgo::Rsa => trans.decipher(Cryptogram::RSA(&ciphertext_bytes))?, + EncryptAlgo::Ecdh => trans.decipher(Cryptogram::ECDH(&ciphertext_bytes))?, }; success!("Clear text HEX: {}", hex::encode(&text)); success!("Clear text base64: {}", base64_encode(&text)); @@ -80,8 +76,8 @@ impl Command for CommandImpl { if json_output { let mut json = BTreeMap::<&'_ str, String>::new(); - json.insert("cipher_hex", hex::encode(&cipher_bytes)); - json.insert("cipher_base64", base64_encode(&cipher_bytes)); + json.insert("cipher_hex", hex::encode(&ciphertext_bytes)); + json.insert("cipher_base64", base64_encode(&ciphertext_bytes)); json.insert("text_hex", hex::encode(&text)); json.insert("text_base64", base64_encode(&text)); if let Some(text) = text_opt { diff --git a/src/cmd_pgpcardlist.rs b/src/cmd_pgpcardlist.rs index be15847..233ca52 100644 --- a/src/cmd_pgpcardlist.rs +++ b/src/cmd_pgpcardlist.rs @@ -14,7 +14,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "pgp-card-list" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("OpenPGP Card List subcommand") + SubCommand::with_name(self.name()).about("OpenPGP Card list subcommand") .arg(Arg::with_name("detail").long("detail").help("Detail output")) .arg(Arg::with_name("json").long("json").help("JSON output")) } diff --git a/src/cmd_pgpcardmake.rs b/src/cmd_pgpcardmake.rs index 24c5f8d..62da359 100644 --- a/src/cmd_pgpcardmake.rs +++ b/src/cmd_pgpcardmake.rs @@ -127,7 +127,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "pgp-card-make" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("OpenPGP Card Make subcommand") + SubCommand::with_name(self.name()).about("OpenPGP Card make subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("12345678").help("OpenPGP card admin pin")) .arg(Arg::with_name("pass").long("pass").takes_value(true).required(false).help("Password for PGP secret key")) .arg(Arg::with_name("in").long("in").takes_value(true).required(false).help("PGP file in")) diff --git a/src/cmd_pgpcardsign.rs b/src/cmd_pgpcardsign.rs index 402ae91..cfc07aa 100644 --- a/src/cmd_pgpcardsign.rs +++ b/src/cmd_pgpcardsign.rs @@ -38,7 +38,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "pgp-card-sign" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("OpenPGP Card Sign subcommand") + SubCommand::with_name(self.name()).about("OpenPGP Card sign subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").help("OpenPGP card user pin")) .arg(Arg::with_name("pass").long("pass").takes_value(true).help("[deprecated] now OpenPGP card user pin")) .arg(Arg::with_name("sha256").short("2").long("sha256").takes_value(true).help("Digest SHA256 HEX")) diff --git a/src/cmd_pivdecrypt.rs b/src/cmd_pivdecrypt.rs index ea41e97..d43b585 100644 --- a/src/cmd_pivdecrypt.rs +++ b/src/cmd_pivdecrypt.rs @@ -7,6 +7,7 @@ use yubikey::piv::AlgorithmId; use yubikey::YubiKey; use crate::pivutil; +use crate::util::try_decode; pub struct CommandImpl; @@ -14,10 +15,10 @@ impl Command for CommandImpl { fn name(&self) -> &str { "piv-decrypt" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("PIV Decrypt(RSA) subcommand") + SubCommand::with_name(self.name()).about("PIV decrypt(RSA) subcommand") .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").help("OpenPGP card user pin")) - .arg(Arg::with_name("encrypted-data").long("encrypted-data").takes_value(true).help("Encrypted data (HEX)")) + .arg(Arg::with_name("ciphertext").long("ciphertext").short("c").takes_value(true).help("Encrypted data (HEX or Base64)")) .arg(Arg::with_name("json").long("json").help("JSON output")) } @@ -30,18 +31,18 @@ impl Command for CommandImpl { let pin_opt = sub_arg_matches.value_of("pin"); let pin = opt_value_result!(pin_opt, "User pin must be assigned"); - let encrypted_data = if let Some(encrypted_data_hex) = sub_arg_matches.value_of("encrypted-data") { - opt_result!(hex::decode(encrypted_data_hex), "Decode --encrypted-data failed: {}") + let encrypted_data = if let Some(ciphertext) = sub_arg_matches.value_of("ciphertext") { + opt_result!(try_decode(ciphertext), "Decode --ciphertext failed: {}") } else { - return simple_error!("Argument --data or --data-hex must assign one"); + return simple_error!("Argument --ciphertext must be assigned"); }; let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); let slot_id = pivutil::get_slot_id(slot)?; - let decrypt_result = yubikey::piv::decrypt_data(&mut yk, &encrypted_data, AlgorithmId::Rsa2048, slot_id); - // let sign_result = yubikey::piv::sign_data(&mut yk, &encrypted_data, AlgorithmId::Rsa2048, SlotId::KeyManagement); + let decrypt_result = yubikey::piv::decrypt_data(&mut yk, &encrypted_data, + AlgorithmId::Rsa2048, slot_id); let decrypted_data = opt_result!(decrypt_result, "Decrypt data failed: {}"); let decrypted_data_bytes = decrypted_data.as_slice(); diff --git a/src/cmd_pivecsign.rs b/src/cmd_pivecsign.rs index 9e60d32..b657410 100644 --- a/src/cmd_pivecsign.rs +++ b/src/cmd_pivecsign.rs @@ -16,7 +16,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "piv-ecsign" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("PIV EC Sign(with SHA256) subcommand") + SubCommand::with_name(self.name()).about("PIV EC sign(with SHA256) subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user pin")) .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) .arg(Arg::with_name("algorithm").short("a").long("algorithm").takes_value(true).help("Algorithm, p256 or p384")) diff --git a/src/cmd_pivgenerate.rs b/src/cmd_pivgenerate.rs index 98c4087..ce0b120 100644 --- a/src/cmd_pivgenerate.rs +++ b/src/cmd_pivgenerate.rs @@ -10,8 +10,9 @@ impl Command for CommandImpl { fn name(&self) -> &str { "piv-generate" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("PIV Generate subcommand") + SubCommand::with_name(self.name()).about("PIV generate subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("OpenPGP card user pin")) + .arg(Arg::with_name("force").long("force").help("Force generate")) .arg(Arg::with_name("json").long("json").help("JSON output")) } @@ -22,6 +23,10 @@ impl Command for CommandImpl { warning!("This feature is not works"); let pin = opt_value_result!(sub_arg_matches.value_of("pin"), "User pin must be assigned"); + if !sub_arg_matches.is_present("force") { + failure_and_exit!("--force must be assigned"); + } + let mut yk = opt_result!(YubiKey::open(), "YubiKey not found: {}"); opt_result!(yk.verify_pin(pin.as_bytes()), "YubiKey verify pin failed: {}"); diff --git a/src/cmd_pivrsasign.rs b/src/cmd_pivrsasign.rs index 607cd9c..ff3ad68 100644 --- a/src/cmd_pivrsasign.rs +++ b/src/cmd_pivrsasign.rs @@ -15,7 +15,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "piv-sign" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("PIV RSA Sign(with SHA256) subcommand") + SubCommand::with_name(self.name()).about("PIV RSA sign(with SHA256) subcommand") .arg(Arg::with_name("slot").short("s").long("slot").takes_value(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).default_value("123456").help("OpenPGP card user pin")) .arg(Arg::with_name("sha256").short("2").long("sha256").takes_value(true).help("Digest SHA256 HEX")) diff --git a/src/cmd_rsadecrypt.rs b/src/cmd_rsadecrypt.rs index 4bbc1d7..1f06f9f 100644 --- a/src/cmd_rsadecrypt.rs +++ b/src/cmd_rsadecrypt.rs @@ -16,7 +16,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "rsa-decrypt" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("RSA Decrypt subcommand") + SubCommand::with_name(self.name()).about("RSA decrypt subcommand") .arg(Arg::with_name("pri-key-in").long("pri-key-in").takes_value(true).help("Private key in")) .arg(Arg::with_name("encrypted").long("encrypted").takes_value(true).help("Encrypted data")) .arg(Arg::with_name("padding").long("padding").takes_value(true) diff --git a/src/cmd_rsaencrypt.rs b/src/cmd_rsaencrypt.rs index 6ada101..eaa5239 100644 --- a/src/cmd_rsaencrypt.rs +++ b/src/cmd_rsaencrypt.rs @@ -17,7 +17,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "rsa-encrypt" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("RSA Encrypt subcommand") + SubCommand::with_name(self.name()).about("RSA encrypt subcommand") .arg(Arg::with_name("pub-key-in").long("pub-key-in").takes_value(true).help("Public key in")) .arg(Arg::with_name("data").long("data").takes_value(true).help("Data")) .arg(Arg::with_name("data-hex").long("data-hex").takes_value(true).help("Data in HEX")) diff --git a/src/cmd_rsaverify.rs b/src/cmd_rsaverify.rs index 95a8c99..815e1d2 100644 --- a/src/cmd_rsaverify.rs +++ b/src/cmd_rsaverify.rs @@ -21,7 +21,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "rsa-verify" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("RSA Verify subcommand") + SubCommand::with_name(self.name()).about("RSA verify subcommand") .arg(Arg::with_name("pub-key-in").long("pub-key-in").takes_value(true).help("Public key in")) .arg(Arg::with_name("signature").long("signature").takes_value(true).help("Signature HEX")) .arg(Arg::with_name("in").short("i").long("in").takes_value(true).help("File in")) diff --git a/src/cmd_signfile.rs b/src/cmd_signfile.rs index a5a0b3d..250805a 100644 --- a/src/cmd_signfile.rs +++ b/src/cmd_signfile.rs @@ -39,7 +39,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "sign-file" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("PIV Sign(with SHA256) subcommand") + SubCommand::with_name(self.name()).about("PIV sign(with SHA256) subcommand") .arg(Arg::with_name("pin").short("p").long("pin").takes_value(true).help("PIV card user pin")) .arg(Arg::with_name("slot").short("s").long("slot") .takes_value(true).required(true).help("PIV slot, e.g. 82, 83 ... 95, 9a, 9c, 9d, 9e")) diff --git a/src/cmd_u2fregister.rs b/src/cmd_u2fregister.rs index 4713658..964210f 100644 --- a/src/cmd_u2fregister.rs +++ b/src/cmd_u2fregister.rs @@ -24,7 +24,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "u2f-register" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("FIDO U2F Register subcommand") + SubCommand::with_name(self.name()).about("FIDO U2F register subcommand") .arg(Arg::with_name("app-id").short("a").long("app-id").default_value("https://example.com").help("App id")) .arg(Arg::with_name("timeout").short("t").long("timeout").default_value("30").help("Timeout in seconds")) .arg(Arg::with_name("challenge").long("challenge").takes_value(true).help("Challenge HEX")) diff --git a/src/cmd_u2fsign.rs b/src/cmd_u2fsign.rs index 54dca64..9de73cc 100644 --- a/src/cmd_u2fsign.rs +++ b/src/cmd_u2fsign.rs @@ -24,7 +24,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "u2f-sign" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("FIDO U2F Sign subcommand") + SubCommand::with_name(self.name()).about("FIDO U2F sign subcommand") .arg(Arg::with_name("app-id").short("a").long("app-id").default_value("https://example.com").help("App id")) .arg(Arg::with_name("timeout").short("t").long("timeout").default_value("30").help("Timeout in seconds")) .arg(Arg::with_name("public-key-hex").long("public-key-hex").takes_value(true).help("Public key hex")) diff --git a/src/cmd_verifyfile.rs b/src/cmd_verifyfile.rs index 69545d6..0f2deaa 100644 --- a/src/cmd_verifyfile.rs +++ b/src/cmd_verifyfile.rs @@ -21,7 +21,7 @@ impl Command for CommandImpl { fn name(&self) -> &str { "verify-file" } fn subcommand<'a>(&self) -> App<'a, 'a> { - SubCommand::with_name(self.name()).about("PIV Verify(with SHA256) subcommand") + SubCommand::with_name(self.name()).about("PIV verify(with SHA256) subcommand") .arg(Arg::with_name("file").short("f").long("file").takes_value(true).required(false).help("Input file")) .arg(Arg::with_name("sign-file").short("S").long("sign-file").takes_value(true).help("Sign file")) } diff --git a/src/util.rs b/src/util.rs index 2025f6e..97d3f1b 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,5 +1,6 @@ use base64::{DecodeError, Engine}; use base64::engine::general_purpose::{STANDARD, URL_SAFE_NO_PAD}; +use rust_util::XResult; pub fn base64_encode>(input: T) -> String { STANDARD.encode(input) @@ -12,3 +13,13 @@ pub fn base64_encode_url_safe_no_pad>(input: T) -> String { pub fn base64_decode>(input: T) -> Result, DecodeError> { STANDARD.decode(input) } + +pub fn try_decode(input: &str) -> XResult> { + return match hex::decode(input) { + Ok(v) => Ok(v), + Err(_) => match base64_decode(input) { + Ok(v) => Ok(v), + Err(e) => simple_error!("decode hex or base64 error: {}", e), + } + }; +}