feat: PGP decrypt works
This commit is contained in:
22
Cargo.lock
generated
22
Cargo.lock
generated
@@ -1237,6 +1237,27 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpassword"
|
||||
version = "7.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rtoolbox",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtoolbox"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusb"
|
||||
version = "0.8.1"
|
||||
@@ -1586,6 +1607,7 @@ dependencies = [
|
||||
"openpgp-card",
|
||||
"openpgp-card-pcsc",
|
||||
"reqwest",
|
||||
"rpassword",
|
||||
"rust_util",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@@ -17,6 +17,7 @@ hex = "0.4.3"
|
||||
openpgp-card = "0.3.3"
|
||||
openpgp-card-pcsc = "0.3.0"
|
||||
reqwest = { version = "0.11.14", features = ["blocking", "rustls", "rustls-tls"] }
|
||||
rpassword = "7.2.0"
|
||||
rust_util = "0.6.41"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.93"
|
||||
|
||||
@@ -1,22 +1,81 @@
|
||||
use std::path::PathBuf;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose;
|
||||
use openpgp_card::crypto_data::Cryptogram;
|
||||
use openpgp_card::OpenPgp;
|
||||
use rust_util::{debugging, failure, opt_result, simple_error, success, XResult};
|
||||
use crate::card::get_card;
|
||||
use crate::file;
|
||||
use rust_util::{debugging, failure, opt_result, simple_error, success, util_term, XResult};
|
||||
|
||||
pub fn decrypt(path: PathBuf, pin: &Option<String>) -> XResult<()> {
|
||||
use crate::{file, util};
|
||||
use crate::card::get_card;
|
||||
use crate::spec::{TinyEncryptEnvelop, TinyEncryptMeta};
|
||||
use crate::util::{decode_base64, TINY_ENC_FILE_EXT};
|
||||
|
||||
pub fn decrypt(path: &PathBuf, pin: &Option<String>) -> XResult<()> {
|
||||
let path_display = format!("{}", path.display());
|
||||
if !path_display.ends_with(TINY_ENC_FILE_EXT) {
|
||||
return simple_error!("File is not tiny encrypt file: {}", &path_display);
|
||||
}
|
||||
let mut file_in = opt_result!(File::open(path), "Open file: {} failed: {}", &path_display);
|
||||
let mut meta = opt_result!(file::read_tiny_encrypt_meta(&mut file_in), "Read file: {}, failed: {}", &path_display);
|
||||
meta.normalize();
|
||||
|
||||
debugging!("Found meta: {}", serde_json::to_string_pretty(&meta).unwrap());
|
||||
let path_out = &path_display[0..path_display.len() - TINY_ENC_FILE_EXT.len()];
|
||||
if let Ok(_) = fs::metadata(path_out) {
|
||||
return simple_error!("Output file: {} exists", path_out);
|
||||
}
|
||||
|
||||
let mut card = match get_card() {
|
||||
debugging!("Found meta: {}", serde_json::to_string_pretty(&meta).unwrap());
|
||||
let selected_envelop = select_envelop(&meta)?;
|
||||
|
||||
let key = try_decrypt_key(selected_envelop, pin)?;
|
||||
let nonce = opt_result!( decode_base64(&meta.nonce), "Decode nonce failed: {}");
|
||||
|
||||
debugging!("Decrypt key: {}", hex::encode(&key));
|
||||
debugging!("Decrypt nonce: {}", hex::encode(&nonce));
|
||||
|
||||
let mut file_out = File::create(path_out)?;
|
||||
let _ = decrypt_file(&mut file_in, &mut file_out, &key, &nonce)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt_file(file_in: &mut File, file_out: &mut File, key: &[u8], nonce: &[u8]) -> XResult<usize> {
|
||||
let mut total_len = 0;
|
||||
let mut buffer = [0u8; 1024 * 8];
|
||||
let key = opt_result!(key.try_into(), "Key is not 32 bytes: {}");
|
||||
let mut decryptor = aes_gcm_stream::Aes256GcmStreamDecryptor::new(key, &nonce);
|
||||
loop {
|
||||
let len = opt_result!(file_in.read(&mut buffer), "Read file failed: {}");
|
||||
if len == 0 {
|
||||
let last_block = opt_result!(decryptor.finalize(), "Decrypt file failed: {}");
|
||||
if !last_block.is_empty() {
|
||||
opt_result!(file_out.write_all(&last_block), "Write file failed: {}");
|
||||
}
|
||||
success!("Decrypt finished, total bytes: {}", total_len);
|
||||
break;
|
||||
} else {
|
||||
total_len += len;
|
||||
let decrypted = decryptor.update(&buffer[0..len]);
|
||||
if !decrypted.is_empty() {
|
||||
opt_result!(file_out.write_all(&decrypted), "Write file failed: {}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(total_len)
|
||||
}
|
||||
|
||||
fn try_decrypt_key(envelop: &TinyEncryptEnvelop, pin: &Option<String>) -> XResult<Vec<u8>> {
|
||||
match envelop.r#type.to_lowercase().as_str() {
|
||||
"pgp" => try_decrypt_key_pgp(envelop, pin),
|
||||
unknown_type => return simple_error!("Unknown or not supported type: {}", unknown_type)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_decrypt_key_pgp(envelop: &TinyEncryptEnvelop, pin: &Option<String>) -> XResult<Vec<u8>> {
|
||||
let card = match get_card() {
|
||||
Err(e) => {
|
||||
failure!("Get PGP card failed: {}", e);
|
||||
return simple_error!("Get card failed: {}", e);
|
||||
@@ -26,20 +85,62 @@ pub fn decrypt(path: PathBuf, pin: &Option<String>) -> XResult<()> {
|
||||
let mut pgp = OpenPgp::new(card);
|
||||
let mut trans = opt_result!(pgp.transaction(), "Open card failed: {}");
|
||||
|
||||
let pin = pin.as_ref().map(|s| s.as_str()).unwrap_or_else(|| "123456");
|
||||
let pin = read_pin(pin);
|
||||
if let Err(e) = trans.verify_pw1_user(pin.as_ref()) {
|
||||
failure!("Verify user pin failed: {}", e);
|
||||
return simple_error!("User pin verify failed: {}", e);
|
||||
}
|
||||
success!("User pin verify success!");
|
||||
|
||||
let pgp_envelop = meta.pgp_envelop.unwrap();
|
||||
let pgp_envelop = &envelop.encrypted_key;
|
||||
debugging!("PGP envelop: {}", &pgp_envelop);
|
||||
let pgp_envelop_bytes = opt_result!(general_purpose::STANDARD.decode(&pgp_envelop), "Decode PGP envelop failed: {}");
|
||||
let pgp_envelop_bytes = opt_result!(decode_base64(&pgp_envelop), "Decode PGP envelop failed: {}");
|
||||
|
||||
let key = trans.decipher(Cryptogram::RSA(&pgp_envelop_bytes))?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
success!("{}", hex::encode(&key));
|
||||
fn read_pin(pin: &Option<String>) -> String {
|
||||
match pin {
|
||||
Some(pin) => pin.to_string(),
|
||||
None => if util_term::read_yes_no("Use default PIN 123456, please confirm") {
|
||||
"123456".into()
|
||||
} else {
|
||||
rpassword::prompt_password("Please input PIN: ").expect("Read PIN failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn select_envelop(meta: &TinyEncryptMeta) -> XResult<&TinyEncryptEnvelop> {
|
||||
let envelops = match &meta.envelops {
|
||||
None => return simple_error!("No envelops found"),
|
||||
Some(envelops) => if envelops.is_empty() {
|
||||
return simple_error!("No envelops found");
|
||||
} else {
|
||||
envelops
|
||||
},
|
||||
};
|
||||
|
||||
success!("Found {} envelops:", envelops.len());
|
||||
if envelops.len() == 1 {
|
||||
let selected_envelop = &envelops[0];
|
||||
success!("Auto selected envelop: #{} {}", 1, selected_envelop.r#type.to_uppercase());
|
||||
return Ok(selected_envelop);
|
||||
}
|
||||
|
||||
envelops.iter().enumerate().for_each(|(i, envelop)| {
|
||||
println!("#{} {}{}", i + 1,
|
||||
envelop.r#type.to_uppercase(),
|
||||
if envelop.kid.is_empty() {
|
||||
"".into()
|
||||
} else {
|
||||
format!(", Kid: {}", envelop.kid)
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
let envelop_number = util::read_number("Please select an envelop:", 1, envelops.len());
|
||||
let selected_envelop = &envelops[envelop_number - 1];
|
||||
success!("Selected envelop: #{} {}", envelop_number, selected_envelop.r#type.to_uppercase());
|
||||
Ok(selected_envelop)
|
||||
}
|
||||
13
src/main.rs
13
src/main.rs
@@ -3,7 +3,7 @@ extern crate core;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use rust_util::{information, XResult};
|
||||
use rust_util::{failure, information, success, XResult};
|
||||
|
||||
mod util;
|
||||
mod config;
|
||||
@@ -35,6 +35,8 @@ enum Commands {
|
||||
Decrypt {
|
||||
/// Files need to be decrypted
|
||||
paths: Vec<PathBuf>,
|
||||
#[arg(long)]
|
||||
pin: Option<String>,
|
||||
},
|
||||
/// Show file info
|
||||
#[command(arg_required_else_help = true, short_flag = 'I')]
|
||||
@@ -54,9 +56,12 @@ fn main() -> XResult<()> {
|
||||
paths.iter().for_each(|f| information!("{:?}", f));
|
||||
Ok(())
|
||||
}
|
||||
Commands::Decrypt { mut paths } => {
|
||||
for path in paths {
|
||||
cmd_decrypt::decrypt(path, &Some("123456".to_string())).unwrap();
|
||||
Commands::Decrypt { mut paths, pin } => {
|
||||
for path in &paths {
|
||||
match cmd_decrypt::decrypt(path, &pin) {
|
||||
Ok(_) => success!("Decrypt {} succeed", path.to_str().unwrap_or("N/A")),
|
||||
Err(e) => failure!("Decrypt {} failed: {}", path.to_str().unwrap_or("N/A"), e),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
30
src/util.rs
30
src/util.rs
@@ -1,3 +1,33 @@
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
use base64::Engine;
|
||||
use base64::engine::general_purpose;
|
||||
use rust_util::{warning, XResult};
|
||||
|
||||
pub const TINY_ENC_FILE_EXT: &str = ".tinyenc";
|
||||
|
||||
pub fn decode_base64(input: &str) -> XResult<Vec<u8>> {
|
||||
Ok(general_purpose::STANDARD.decode(input)?)
|
||||
}
|
||||
|
||||
pub fn read_number(hint: &str, from: usize, to: usize) -> usize {
|
||||
loop {
|
||||
print!("{} ({}-{}): ", hint, from, to);
|
||||
io::stdout().flush().ok();
|
||||
let mut buff = String::new();
|
||||
let _ = io::stdin().read_line(&mut buff).expect("Read line from stdin");
|
||||
let buff = buff.trim();
|
||||
match buff.parse() {
|
||||
Err(_) => warning!("Input number error!"),
|
||||
Ok(number) => if number < from || number > to {
|
||||
warning!("Input number is not in range.");
|
||||
} else {
|
||||
return number;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_user_agent() -> String {
|
||||
format!("TinyEncrypt-rs v{}@{}", env!("CARGO_PKG_VERSION"),
|
||||
|
||||
Reference in New Issue
Block a user