feat: v1.12.7
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -508,7 +508,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "card-cli"
|
name = "card-cli"
|
||||||
version = "1.12.6"
|
version = "1.12.7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm-stream",
|
"aes-gcm-stream",
|
||||||
"authenticator 0.3.1",
|
"authenticator 0.3.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "card-cli"
|
name = "card-cli"
|
||||||
version = "1.12.6"
|
version = "1.12.7"
|
||||||
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
authors = ["Hatter Jiang <jht5945@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use clap::{App, Arg, ArgMatches, SubCommand};
|
|||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::{cmdutil, hmacutil, util};
|
use crate::{cmdutil, hmacutil, pbeutil, util};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ impl Command for CommandImpl {
|
|||||||
.required(true)
|
.required(true)
|
||||||
.help("Ciphertext"),
|
.help("Ciphertext"),
|
||||||
)
|
)
|
||||||
|
.arg(Arg::with_name("auto-pbe").long("auto-pbe").help("Auto PBE decryption"))
|
||||||
.arg(cmdutil::build_json_arg())
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,15 +29,20 @@ impl Command for CommandImpl {
|
|||||||
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
let ciphertext = sub_arg_matches.value_of("ciphertext").unwrap();
|
let ciphertext = sub_arg_matches.value_of("ciphertext").unwrap();
|
||||||
let plaintext = hmacutil::hmac_decrypt_to_string(ciphertext)?;
|
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)?;
|
||||||
|
}
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
json.insert("plaintext", plaintext);
|
json.insert("plaintext", text);
|
||||||
|
|
||||||
util::print_pretty_json(&json);
|
util::print_pretty_json(&json);
|
||||||
} else {
|
} else {
|
||||||
success!("Plaintext: {}", plaintext);
|
success!("Plaintext: {}", text);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use clap::{App, Arg, ArgMatches, SubCommand};
|
|||||||
use rust_util::util_clap::{Command, CommandError};
|
use rust_util::util_clap::{Command, CommandError};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use crate::{cmdutil, hmacutil, util};
|
use crate::{cmdutil, hmacutil, pbeutil, util};
|
||||||
|
|
||||||
pub struct CommandImpl;
|
pub struct CommandImpl;
|
||||||
|
|
||||||
@@ -21,14 +21,22 @@ impl Command for CommandImpl {
|
|||||||
.required(true)
|
.required(true)
|
||||||
.help("Plaintext"),
|
.help("Plaintext"),
|
||||||
)
|
)
|
||||||
|
.arg(Arg::with_name("with-pbe").long("with-pbe").help("With PBE encryption"))
|
||||||
|
.arg(Arg::with_name("pbe-iteration").long("pbe-iteration").takes_value(true).help("PBE iteration, default 100000"))
|
||||||
.arg(cmdutil::build_json_arg())
|
.arg(cmdutil::build_json_arg())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
fn run(&self, _arg_matches: &ArgMatches, sub_arg_matches: &ArgMatches) -> CommandError {
|
||||||
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
let json_output = cmdutil::check_json_output(sub_arg_matches);
|
||||||
|
|
||||||
let plaintext = sub_arg_matches.value_of("plaintext").unwrap();
|
let mut text = sub_arg_matches.value_of("plaintext").unwrap().to_string();
|
||||||
let hmac_encrypt_ciphertext = hmacutil::hmac_encrypt_from_string(plaintext)?;
|
let with_pbe = sub_arg_matches.is_present("with-pbe");
|
||||||
|
if with_pbe {
|
||||||
|
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)?;
|
||||||
|
}
|
||||||
|
let hmac_encrypt_ciphertext = hmacutil::hmac_encrypt_from_string(&text)?;
|
||||||
|
|
||||||
if json_output {
|
if json_output {
|
||||||
let mut json = BTreeMap::<&'_ str, String>::new();
|
let mut json = BTreeMap::<&'_ str, String>::new();
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ pub fn hmac_decrypt(ciphertext: &str) -> XResult<Vec<u8>> {
|
|||||||
Ok(plaintext)
|
Ok(plaintext)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_decode_hmac_val(s: &str) -> XResult<Vec<u8>> {
|
pub fn try_decode_hmac_val(s: &str) -> XResult<Vec<u8>> {
|
||||||
match hex::decode(s) {
|
match hex::decode(s) {
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
Err(e) => match base64_uri_decode(s) {
|
Err(e) => match base64_uri_decode(s) {
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ mod signfile;
|
|||||||
mod sshutil;
|
mod sshutil;
|
||||||
mod util;
|
mod util;
|
||||||
mod yubikeyutil;
|
mod yubikeyutil;
|
||||||
|
mod pbeutil;
|
||||||
|
|
||||||
pub struct DefaultCommandImpl;
|
pub struct DefaultCommandImpl;
|
||||||
|
|
||||||
|
|||||||
101
src/pbeutil.rs
Normal file
101
src/pbeutil.rs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
use crate::digestutil::{copy_sha256, sha256_bytes};
|
||||||
|
use crate::pinutil;
|
||||||
|
use crate::util::{base64_decode, base64_encode, base64_encode_url_safe_no_pad};
|
||||||
|
use aes_gcm_stream::{Aes256GcmStreamDecryptor, Aes256GcmStreamEncryptor};
|
||||||
|
use rand::random;
|
||||||
|
use rust_util::XResult;
|
||||||
|
|
||||||
|
const PBE_ENC_PREFIX: &str = "pbe_enc:";
|
||||||
|
|
||||||
|
pub fn simple_pbe_encrypt_with_prompt_from_string(iteration: u32, plaintext: &str) -> XResult<String> {
|
||||||
|
simple_pbe_encrypt_with_prompt(iteration, plaintext.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_pbe_decrypt_with_prompt_to_string(ciphertext: &str) -> XResult<String> {
|
||||||
|
let plaintext = simple_pbe_decrypt_with_prompt(ciphertext)?;
|
||||||
|
Ok(String::from_utf8(plaintext)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_pbe_encrypt_with_prompt(iteration: u32, plaintext: &[u8]) -> XResult<String> {
|
||||||
|
let pin = opt_value_result!(pinutil::get_pin(None), "Simple PBE password required");
|
||||||
|
simple_pbe_encrypt(&pin, iteration, plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_pbe_decrypt_with_prompt(ciphertext: &str) -> XResult<Vec<u8>> {
|
||||||
|
let pin = opt_value_result!(pinutil::get_pin(None), "Simple PBE password required");
|
||||||
|
simple_pbe_decrypt(&pin, ciphertext)
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub fn simple_pbe_encrypt_from_string(
|
||||||
|
// password: &str,
|
||||||
|
// iteration: u32,
|
||||||
|
// plaintext: &str,
|
||||||
|
// ) -> XResult<String> {
|
||||||
|
// simple_pbe_encrypt(password, iteration, plaintext.as_bytes())
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn simple_pbe_decrypt_to_string(password: &str, ciphertext: &str) -> XResult<String> {
|
||||||
|
// let plaintext = simple_pbe_decrypt(password, ciphertext)?;
|
||||||
|
// Ok(String::from_utf8(plaintext)?)
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub fn simple_pbe_encrypt(password: &str, iteration: u32, plaintext: &[u8]) -> XResult<String> {
|
||||||
|
let pbe_salt: [u8; 32] = random();
|
||||||
|
let key = simple_pbe_kdf(password, &pbe_salt, iteration)?;
|
||||||
|
let aes_gcm_nonce: [u8; 16] = random();
|
||||||
|
|
||||||
|
let mut encryptor = Aes256GcmStreamEncryptor::new(key, &aes_gcm_nonce);
|
||||||
|
let mut ciphertext = encryptor.update(plaintext);
|
||||||
|
let (final_part, tag) = encryptor.finalize();
|
||||||
|
ciphertext.extend_from_slice(&final_part);
|
||||||
|
ciphertext.extend_from_slice(&tag);
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
"{}{}:{}:{}:{}",
|
||||||
|
PBE_ENC_PREFIX,
|
||||||
|
iteration,
|
||||||
|
base64_encode_url_safe_no_pad(pbe_salt),
|
||||||
|
base64_encode_url_safe_no_pad(aes_gcm_nonce),
|
||||||
|
base64_encode(&ciphertext)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn simple_pbe_decrypt(password: &str, ciphertext: &str) -> XResult<Vec<u8>> {
|
||||||
|
if !is_simple_pbe_encrypted(ciphertext) {
|
||||||
|
return simple_error!("Invalid ciphertext: {}", ciphertext);
|
||||||
|
}
|
||||||
|
let parts = ciphertext.split(":").collect::<Vec<_>>();
|
||||||
|
let iteration: u32 = parts[1].parse()?;
|
||||||
|
let pbe_salt = crate::hmacutil::try_decode_hmac_val(parts[2])?;
|
||||||
|
let aes_gcm_nonce = crate::hmacutil::try_decode_hmac_val(parts[3])?;
|
||||||
|
let ciphertext = base64_decode(parts[4])?;
|
||||||
|
|
||||||
|
let key = simple_pbe_kdf(password, &pbe_salt, iteration)?;
|
||||||
|
|
||||||
|
let mut decryptor = Aes256GcmStreamDecryptor::new(key, &aes_gcm_nonce);
|
||||||
|
let mut plaintext = decryptor.update(&ciphertext);
|
||||||
|
let final_part = decryptor.finalize()?;
|
||||||
|
plaintext.extend_from_slice(&final_part);
|
||||||
|
|
||||||
|
Ok(plaintext)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_simple_pbe_encrypted(ciphertext: &str) -> bool {
|
||||||
|
ciphertext.starts_with(PBE_ENC_PREFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn simple_pbe_kdf(password: &str, pbe_salt: &[u8], iteration: u32) -> XResult<[u8; 32]> {
|
||||||
|
let mut init_data = password.as_bytes().to_vec();
|
||||||
|
init_data.extend_from_slice(&pbe_salt);
|
||||||
|
let mut loop_hash = sha256_bytes(&init_data);
|
||||||
|
for i in 0..iteration {
|
||||||
|
let i_to_bytes = i.to_be_bytes();
|
||||||
|
for x in 0..4 {
|
||||||
|
loop_hash[x] = i_to_bytes[x];
|
||||||
|
}
|
||||||
|
loop_hash = sha256_bytes(&loop_hash);
|
||||||
|
}
|
||||||
|
let key = copy_sha256(&sha256_bytes(&loop_hash))?;
|
||||||
|
|
||||||
|
Ok(key)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user