zcash-sync/src/db/backup.rs

110 lines
3.6 KiB
Rust
Raw Normal View History

2022-11-22 01:53:16 -08:00
use age::secrecy::ExposeSecret;
use anyhow::anyhow;
use rusqlite::backup::Backup;
use rusqlite::Connection;
2022-11-22 19:18:54 -08:00
use serde::Serialize;
2022-11-22 01:53:16 -08:00
use std::fs::File;
use std::io::{Cursor, Read, Write};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{iter, time};
use zip::write::FileOptions;
const YWALLET_BAK: &str = "YWallet.age";
pub struct FullEncryptedBackup {
tmp_dir: PathBuf,
db_names: Vec<String>,
}
impl FullEncryptedBackup {
pub fn new(tmp_dir: &str) -> Self {
FullEncryptedBackup {
tmp_dir: Path::new(&tmp_dir).to_path_buf(),
db_names: vec![],
}
}
2022-11-22 17:49:03 -08:00
pub fn generate_key() -> anyhow::Result<AGEKeys> {
2022-11-22 01:53:16 -08:00
let key = age::x25519::Identity::generate();
2022-11-22 17:49:03 -08:00
let sk = key.to_string().expose_secret().clone();
let pk = key.to_public().to_string();
Ok(AGEKeys { sk, pk })
2022-11-22 01:53:16 -08:00
}
pub fn add(&mut self, src: &Connection, db_name: &str) -> anyhow::Result<()> {
let dst_path = self.tmp_dir.join(db_name);
let mut dst = Connection::open(&dst_path)?;
let backup = Backup::new(src, &mut dst)?;
backup.run_to_completion(5, time::Duration::from_millis(250), None)?;
self.db_names.push(db_name.to_string());
Ok(())
}
2022-11-22 17:49:03 -08:00
pub fn close(&self, pk: &str) -> anyhow::Result<()> {
2022-11-22 01:53:16 -08:00
let data = self.make_zip()?;
2022-11-22 19:18:54 -08:00
let pubkey = age::x25519::Recipient::from_str(pk).map_err(|e| anyhow!(e.to_string()))?;
2022-11-22 01:53:16 -08:00
let mut encrypted_file = File::create(self.tmp_dir.join(YWALLET_BAK))?;
let encryptor = age::Encryptor::with_recipients(vec![Box::new(pubkey)]).unwrap();
let mut writer = encryptor.wrap_output(&mut encrypted_file)?;
writer.write_all(&*data)?;
writer.finish()?;
Ok(())
}
pub fn restore(&self, cipher_key: &str, data_path: &str) -> anyhow::Result<()> {
let key =
2022-11-22 17:49:03 -08:00
age::x25519::Identity::from_str(cipher_key).map_err(|e| anyhow!(e.to_string()))?;
2022-11-22 01:53:16 -08:00
let mut cipher_text = Vec::new();
let mut f = File::open(data_path)?;
f.read_to_end(&mut cipher_text)?;
let decryptor = match age::Decryptor::new(&*cipher_text)? {
age::Decryptor::Recipients(d) => d,
_ => unreachable!(),
};
let mut plain_text = vec![];
let mut reader = decryptor.decrypt(iter::once(&key as &dyn age::Identity))?;
reader.read_to_end(&mut plain_text)?;
self.unzip(&plain_text)?;
Ok(())
}
fn make_zip(&self) -> anyhow::Result<Vec<u8>> {
let mut buffer = Vec::new();
let zip_data = vec![];
let buff = Cursor::new(zip_data);
let mut zip_writer = zip::ZipWriter::new(buff);
for db_name in self.db_names.iter() {
zip_writer.start_file(db_name, FileOptions::default())?;
let mut f = File::open(self.tmp_dir.join(db_name))?;
f.read_to_end(&mut buffer)?;
zip_writer.write_all(&*buffer)?;
buffer.clear();
}
let r = zip_writer.finish()?;
Ok(r.into_inner())
}
fn unzip(&self, data: &[u8]) -> anyhow::Result<()> {
let buff = Cursor::new(data);
let mut zip_reader = zip::ZipArchive::new(buff)?;
let db_names: Vec<_> = zip_reader.file_names().map(|s| s.to_string()).collect();
for db_name in db_names {
let mut zip_file = zip_reader.by_name(&db_name)?;
let mut out_file = File::create(&self.tmp_dir.join(db_name))?;
std::io::copy(&mut zip_file, &mut out_file)?;
}
Ok(())
}
}
2022-11-22 17:49:03 -08:00
#[derive(Serialize)]
pub struct AGEKeys {
pub sk: String,
pub pk: String,
}