ceal 0.1.0

Opportunistic E-Mail Encryption - Stand-Alone Library and Extensions for Lettre
Documentation
use ceal::openpgp;
use sequoia_openpgp::{Cert, parse::Parse};
use std::{
	ffi::OsStr,
	fs,
	io::{BufRead as _, Write},
	os::unix::ffi::OsStrExt,
	path::Path,
	process::{Command, Stdio}
};

const MESSAGE: &[u8] = b"From: OpenPGP Test <openpgp@example.org>\r
To: OpenPGP Test <openpgp@example.org>\r
Content-Type: multipart/alternative; boundary=iamaboundary\r
Content-Description: Multipart Message in MIME format\r
Subject: Test Mail\r
MIME-Version: 1.0\r
\r
--iamaboundary\r
Content-Type: text/plain; charset=\"utf-8\"\r
\r
Hello World\r
\r
--iamaboundary\r
Content-Type: text/html; charset=\"utf-8\"\r
\r
<html><body>Hello World</body></html>\r
\r
--iamaboundary--\r
";

const HEADERS: &[u8] = b"From: OpenPGP Test <openpgp@example.org>\r
To: OpenPGP Test <openpgp@example.org>\r
Subject: Test Mail\r
MIME-Version: 1.0\r
";

const BODY: &str = "Content-Type: multipart/alternative; boundary=iamaboundary\r
Content-Description: Multipart Message in MIME format\r
\r
--iamaboundary\r
Content-Type: text/plain; charset=\"utf-8\"\r
\r
Hello World\r
\r
--iamaboundary\r
Content-Type: text/html; charset=\"utf-8\"\r
\r
<html><body>Hello World</body></html>\r
\r
--iamaboundary--\r
";

fn print_cmd(cmd: &Command) {
	eprint!("+ '{}'", cmd.get_program().display());
	for arg in cmd.get_args() {
		eprint!(" '{}'", arg.display());
	}
	eprintln!();
}

#[test]
fn test_openpgp_encryption() {
	let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests");
	let cert = dir.join("openpgp_test_key.cert");
	let key = dir.join("openpgp_test_key.pgp");

	if !cert.exists() || !key.exists() {
		let mut cmd = Command::new(dir.join("openpgp_test_key.sh"));
		print_cmd(&cmd);
		let status = cmd.status().unwrap();
		assert!(status.success());
	}

	let cert = fs::read(cert).unwrap();
	let encrypted = openpgp::encrypt(MESSAGE, vec![Cert::from_bytes(&cert).unwrap()]);
	_ = fs::write(dir.join("openpgp_test_mail.eml"), &encrypted);

	assert!(encrypted.starts_with(HEADERS));

	let pgp_start = "-----BEGIN PGP MESSAGE-----";
	let pgp_end = "-----END PGP MESSAGE-----";
	let mut pgp_start_idx = 0;
	let mut pgp_end_idx = 0;
	let mut lines = encrypted.lines();
	while let Some(line) = lines.next().map(|line| line.unwrap()) {
		assert!(&encrypted[pgp_start_idx ..].starts_with(line.as_bytes()));
		if line.starts_with(pgp_start) {
			pgp_end_idx = pgp_start_idx + line.len() + 2;
			break;
		}
		pgp_start_idx += line.len() + 2;
	}
	while let Some(line) = lines.next().map(|line| line.unwrap()) {
		pgp_end_idx += line.len() + 2;
		if line.starts_with(pgp_end) {
			break;
		}
	}

	let pgp = &encrypted[pgp_start_idx ..= pgp_end_idx];
	let mut sq = Command::new("sq");
	sq.arg("decrypt")
		.arg("--recipient-file")
		.arg(key)
		.stdin(Stdio::piped())
		.stdout(Stdio::piped())
		.stderr(Stdio::piped());
	print_cmd(&sq);
	let mut sq = sq.spawn().unwrap();
	sq.stdin.take().unwrap().write_all(pgp).unwrap();
	let decrypted = sq.wait_with_output().unwrap();
	eprint!("{}", OsStr::from_bytes(&decrypted.stderr).display());
	assert!(decrypted.status.success());
	assert_eq!(str::from_utf8(&decrypted.stdout).unwrap(), BODY);
}