feat: PGP decrypt works
This commit is contained in:
22
Cargo.lock
generated
22
Cargo.lock
generated
@@ -1237,6 +1237,27 @@ dependencies = [
|
|||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "rusb"
|
name = "rusb"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
@@ -1586,6 +1607,7 @@ dependencies = [
|
|||||||
"openpgp-card",
|
"openpgp-card",
|
||||||
"openpgp-card-pcsc",
|
"openpgp-card-pcsc",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"rpassword",
|
||||||
"rust_util",
|
"rust_util",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ hex = "0.4.3"
|
|||||||
openpgp-card = "0.3.3"
|
openpgp-card = "0.3.3"
|
||||||
openpgp-card-pcsc = "0.3.0"
|
openpgp-card-pcsc = "0.3.0"
|
||||||
reqwest = { version = "0.11.14", features = ["blocking", "rustls", "rustls-tls"] }
|
reqwest = { version = "0.11.14", features = ["blocking", "rustls", "rustls-tls"] }
|
||||||
|
rpassword = "7.2.0"
|
||||||
rust_util = "0.6.41"
|
rust_util = "0.6.41"
|
||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
serde_json = "1.0.93"
|
serde_json = "1.0.93"
|
||||||
|
|||||||
@@ -1,22 +1,81 @@
|
|||||||
use std::path::PathBuf;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::engine::general_purpose;
|
|
||||||
use openpgp_card::crypto_data::Cryptogram;
|
use openpgp_card::crypto_data::Cryptogram;
|
||||||
use openpgp_card::OpenPgp;
|
use openpgp_card::OpenPgp;
|
||||||
use rust_util::{debugging, failure, opt_result, simple_error, success, XResult};
|
use rust_util::{debugging, failure, opt_result, simple_error, success, util_term, XResult};
|
||||||
use crate::card::get_card;
|
|
||||||
use crate::file;
|
|
||||||
|
|
||||||
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());
|
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 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);
|
let mut meta = opt_result!(file::read_tiny_encrypt_meta(&mut file_in), "Read file: {}, failed: {}", &path_display);
|
||||||
meta.normalize();
|
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) => {
|
Err(e) => {
|
||||||
failure!("Get PGP card failed: {}", e);
|
failure!("Get PGP card failed: {}", e);
|
||||||
return simple_error!("Get 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 pgp = OpenPgp::new(card);
|
||||||
let mut trans = opt_result!(pgp.transaction(), "Open card failed: {}");
|
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()) {
|
if let Err(e) = trans.verify_pw1_user(pin.as_ref()) {
|
||||||
failure!("Verify user pin failed: {}", e);
|
failure!("Verify user pin failed: {}", e);
|
||||||
return simple_error!("User pin verify failed: {}", e);
|
return simple_error!("User pin verify failed: {}", e);
|
||||||
}
|
}
|
||||||
success!("User pin verify success!");
|
success!("User pin verify success!");
|
||||||
|
|
||||||
let pgp_envelop = meta.pgp_envelop.unwrap();
|
let pgp_envelop = &envelop.encrypted_key;
|
||||||
debugging!("PGP envelop: {}", &pgp_envelop);
|
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))?;
|
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 std::path::PathBuf;
|
||||||
|
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use rust_util::{information, XResult};
|
use rust_util::{failure, information, success, XResult};
|
||||||
|
|
||||||
mod util;
|
mod util;
|
||||||
mod config;
|
mod config;
|
||||||
@@ -35,6 +35,8 @@ enum Commands {
|
|||||||
Decrypt {
|
Decrypt {
|
||||||
/// Files need to be decrypted
|
/// Files need to be decrypted
|
||||||
paths: Vec<PathBuf>,
|
paths: Vec<PathBuf>,
|
||||||
|
#[arg(long)]
|
||||||
|
pin: Option<String>,
|
||||||
},
|
},
|
||||||
/// Show file info
|
/// Show file info
|
||||||
#[command(arg_required_else_help = true, short_flag = 'I')]
|
#[command(arg_required_else_help = true, short_flag = 'I')]
|
||||||
@@ -54,9 +56,12 @@ fn main() -> XResult<()> {
|
|||||||
paths.iter().for_each(|f| information!("{:?}", f));
|
paths.iter().for_each(|f| information!("{:?}", f));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Commands::Decrypt { mut paths } => {
|
Commands::Decrypt { mut paths, pin } => {
|
||||||
for path in paths {
|
for path in &paths {
|
||||||
cmd_decrypt::decrypt(path, &Some("123456".to_string())).unwrap();
|
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(())
|
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 {
|
pub fn get_user_agent() -> String {
|
||||||
format!("TinyEncrypt-rs v{}@{}", env!("CARGO_PKG_VERSION"),
|
format!("TinyEncrypt-rs v{}@{}", env!("CARGO_PKG_VERSION"),
|
||||||
|
|||||||
Reference in New Issue
Block a user