1use anyhow::{anyhow, Result};
2use std::io::{Read, Write};
3
4pub fn encrypt(recipient: &str, plaintext: &[u8]) -> Result<Vec<u8>> {
5 let recipient: age::x25519::Recipient = recipient
6 .parse()
7 .map_err(|e| anyhow!("invalid age recipient: {e}"))?;
8 let encryptor = age::Encryptor::with_recipients(vec![Box::new(recipient)])
9 .ok_or_else(|| anyhow!("no recipients provided"))?;
10 let mut out = Vec::new();
11 let mut writer = encryptor.wrap_output(&mut out)?;
12 writer.write_all(plaintext)?;
13 writer.finish()?;
14 Ok(out)
15}
16
17pub fn decrypt(identity: &str, ciphertext: &[u8]) -> Result<Vec<u8>> {
18 let identity: age::x25519::Identity = identity
19 .parse()
20 .map_err(|e| anyhow!("invalid age identity: {e}"))?;
21 let decryptor = match age::Decryptor::new(ciphertext)? {
22 age::Decryptor::Recipients(d) => d,
23 age::Decryptor::Passphrase(_) => {
24 return Err(anyhow!("passphrase-encrypted payloads are not supported"))
25 }
26 };
27 let mut reader = decryptor.decrypt(std::iter::once(&identity as &dyn age::Identity))?;
28 let mut out = Vec::new();
29 reader.read_to_end(&mut out)?;
30 Ok(out)
31}
32
33#[cfg(test)]
34mod tests {
35 use age::secrecy::ExposeSecret;
36
37 fn keypair() -> (String, String) {
39 let id = age::x25519::Identity::generate();
40 let pk = id.to_public();
41 (pk.to_string(), id.to_string().expose_secret().to_string())
42 }
43
44 #[test]
45 fn round_trips() {
46 let (pk, sk) = keypair();
47 let msg = b"the whole repo bundle bytes";
48 let ct = super::encrypt(&pk, msg).unwrap();
49 assert_ne!(&ct[..], &msg[..]);
50 let pt = super::decrypt(&sk, &ct).unwrap();
51 assert_eq!(pt, msg);
52 }
53
54 #[test]
55 fn wrong_key_fails() {
56 let (pk, _sk) = keypair();
57 let (_pk2, sk2) = keypair();
58 let ct = super::encrypt(&pk, b"secret").unwrap();
59 assert!(super::decrypt(&sk2, &ct).is_err());
60 }
61}