extern crate sequoia_openpgp as openpgp; use crate::openpgp::armor; use std::{ fs::File, path::Path, io::{ ErrorKind, Read, Write, BufWriter }, }; use rust_util::{ XResult, new_box_error }; use openpgp::{ Cert, parse::Parse, types::KeyFlags, serialize::stream::{ Message, Encryptor2, LiteralWriter, }, policy::StandardPolicy as P, }; use indicatif::{ ProgressBar, ProgressStyle }; const BUFF_SIZE: usize = 512 * 1024; const PB_PROGRESS: &str = "#-"; const PB_TEMPLATE: &str = "{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})"; pub struct OpenPGPTool { pub cert: Cert, } impl OpenPGPTool { pub fn from_file(file: &str) -> XResult { Ok(OpenPGPTool{ cert: Cert::from_file(Path::new(file))?, }) } #[allow(dead_code)] pub fn from_bytes(bs: &[u8]) -> XResult { Ok(OpenPGPTool{ cert: Cert::from_bytes(&bs)?, }) } pub fn encrypt_file(&self, from_file: &str, to_file: &str, armor: bool) -> XResult<()> { if !Path::new(from_file).exists() { return Err(new_box_error(&format!("From file NOT exists: {}", from_file))); } if Path::new(to_file).exists() { return Err(new_box_error(&format!("To file exists: {}", to_file))); } // https://gitlab.com/sequoia-pgp/sequoia/-/blob/master/openpgp/examples/encrypt-for.rs let p = &P::new(); let mode = KeyFlags::empty().set_storage_encryption(); let recipients = self.cert.keys() .with_policy(p, None).alive().revoked(false).key_flags(&mode) .map(|ka| ka.key()) .collect::>(); if recipients.is_empty() { return Err(new_box_error("Cannot find any encrypt key in pgp key file.")); } let bw = BufWriter::new(File::create(to_file)?); let message = if armor { Message::new(armor::Writer::new(bw, armor::Kind::Message)?) } else { Message::new(bw) }; let encryptor = Encryptor2::for_recipients(message, recipients).build()?; let mut pgp_encrypt_writer = LiteralWriter::new(encryptor).build()?; let mut from = File::open(from_file)?; encrypt_read_write(&mut from, &mut pgp_encrypt_writer)?; pgp_encrypt_writer.finalize()?; Ok(()) } } fn encrypt_read_write(file: &mut File, write: &mut dyn Write) -> XResult<()> { let mut buf: [u8; BUFF_SIZE] = [0u8; BUFF_SIZE]; let file_len = file.metadata()?.len(); let mut read = 0_u64; let pb = ProgressBar::new(file_len); pb.set_style(ProgressStyle::default_bar().template(PB_TEMPLATE)?.progress_chars(PB_PROGRESS)); loop { let len = match file.read(&mut buf) { Ok(0) => { pb.finish_and_clear(); return Ok(()); }, Ok(len) => len, Err(ref e) if e.kind() == ErrorKind::Interrupted => continue, Err(e) => return Err(Box::new(e)), }; write.write_all(&buf[..len])?; read += len as u64; pb.set_position(read); } }