sett 0.4.0

Rust port of sett (data compression, encryption and transfer tool).
Documentation
use std::{collections::BTreeMap, path::PathBuf};

use chrono::Utc;
use sett::{
    decrypt::DecryptOpts,
    encrypt::{EncryptOpts, encrypt},
    openpgp::{certstore::CertStore, keystore::KeyStore},
    package::{CHECKSUM_FILE, CONTENT_FOLDER, Package},
    progress::ProgressDisplay,
    task::Mode,
};

const PASSWORD: &[u8] = b"secret";

struct ProgressNoop;

impl ProgressDisplay for ProgressNoop {
    fn set_completion_value(&mut self, _len: u64) {}
    fn increment(&mut self, _delta: u64) {}
    fn finish(&mut self) {}
}

async fn encrypt_password_prompt(
    _hint: sett::openpgp::crypto::PasswordHint,
) -> sett::secret::Secret {
    PASSWORD.into()
}

fn decrypt_password_prompt(_hint: sett::openpgp::crypto::PasswordHint) -> sett::secret::Secret {
    PASSWORD.into()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn there_and_back_again() {
        encrypt_and_decrypt(BTreeMap::from([
            ("foo".into(), "bar".into()),
            ("baz".into(), "qux".into()),
        ]))
        .await;
        encrypt_and_decrypt(Default::default()).await;
    }

    async fn encrypt_and_decrypt(extra_metadata: BTreeMap<String, String>) {
        const PASSWORD: &[u8] = b"secret";
        const MESSAGE: &[u8] = b"A very secret message!";
        let mut cert_store = CertStore::open_ephemeral();
        let mut key_store = KeyStore::open_ephemeral().await.unwrap();
        let (cert, _rev) = sett::openpgp::cert::CertBuilder::new()
            .add_userid("Chuck <chuck@example.org>")
            .set_password(Some(PASSWORD.into()))
            .generate()
            .unwrap();
        cert_store.import(&cert).unwrap();
        key_store.import(cert.clone()).await.unwrap();
        let tmp_dir = tempfile::tempdir().unwrap();
        let test_file = tmp_dir.path().join("test.txt");
        std::fs::write(&test_file, MESSAGE).unwrap();
        let status = encrypt(
            EncryptOpts {
                files: vec![test_file.to_path_buf()],
                recipients: vec![cert.fingerprint()],
                signer: cert.fingerprint(),
                cert_store,
                key_store,
                password: encrypt_password_prompt,
                compression_algorithm: Default::default(),
                mode: Mode::Run,
                progress: None::<ProgressNoop>,
                purpose: None,
                transfer_id: None,
                timestamp: Utc::now(),
                prefix: None,
                extra_metadata: extra_metadata.clone(),
            },
            sett::destination::Local::new(tmp_dir.path(), None::<String>)
                .unwrap()
                .into(),
        )
        .await
        .unwrap();
        let package_path = match status {
            sett::task::Status::Completed { destination, .. } => PathBuf::from(destination),
            _ => panic!("Expected completed status"),
        };

        let mut cert_store = CertStore::open_ephemeral();
        cert_store.import(&cert).unwrap();
        let mut key_store = KeyStore::open_ephemeral().await.unwrap();
        key_store.import(cert.clone()).await.unwrap();
        let package = Package::open(package_path)
            .await
            .unwrap()
            .verify(&cert_store)
            .await
            .unwrap();
        let status = package
            .decrypt(DecryptOpts {
                key_store,
                cert_store,
                password: decrypt_password_prompt,
                output: Some(tmp_dir.path().to_path_buf()),
                mode: Mode::Run,
                decrypt_only: false,
                progress: None::<ProgressNoop>,
            })
            .await
            .unwrap();
        let unpacked_package = match status {
            sett::task::Status::Completed { destination, .. } => PathBuf::from(destination),
            _ => panic!("Expected completed status"),
        };
        assert!(unpacked_package.join(CHECKSUM_FILE).exists());
        let decrypted_file_path = unpacked_package.join(CONTENT_FOLDER).join("test.txt");
        assert_eq!(std::fs::read(decrypted_file_path).unwrap(), MESSAGE);

        // Verify that extra metadata extracted from the encrypted package is the
        // same as the extra metadata that was originally packaged.
        assert_eq!(package.metadata().await.unwrap().extra, extra_metadata);
    }
}