1use std::io::{Read, Write};
2
3use age::x25519::{Identity, Recipient};
4
5#[derive(Debug)]
7pub enum CryptoError {
8 Encrypt(String),
9 Decrypt(String),
10 InvalidKey(String),
11}
12
13impl std::fmt::Display for CryptoError {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 match self {
16 CryptoError::Encrypt(msg) => write!(f, "encryption failed: {msg}"),
17 CryptoError::Decrypt(msg) => write!(f, "decryption failed: {msg}"),
18 CryptoError::InvalidKey(msg) => write!(f, "invalid key: {msg}"),
19 }
20 }
21}
22
23pub fn parse_recipient(pubkey: &str) -> Result<Recipient, CryptoError> {
25 pubkey
26 .parse::<Recipient>()
27 .map_err(|e| CryptoError::InvalidKey(e.to_string()))
28}
29
30pub fn parse_identity(secret_key: &str) -> Result<Identity, CryptoError> {
32 secret_key
33 .parse::<Identity>()
34 .map_err(|e| CryptoError::InvalidKey(e.to_string()))
35}
36
37pub fn encrypt(plaintext: &[u8], recipients: &[Recipient]) -> Result<Vec<u8>, CryptoError> {
39 let recipient_refs: Vec<&dyn age::Recipient> = recipients
40 .iter()
41 .map(|r| r as &dyn age::Recipient)
42 .collect();
43
44 let encryptor = age::Encryptor::with_recipients(recipient_refs.into_iter())
45 .map_err(|e| CryptoError::Encrypt(e.to_string()))?;
46
47 let mut ciphertext = vec![];
48 let mut writer = encryptor
49 .wrap_output(&mut ciphertext)
50 .map_err(|e| CryptoError::Encrypt(e.to_string()))?;
51
52 writer
53 .write_all(plaintext)
54 .map_err(|e| CryptoError::Encrypt(e.to_string()))?;
55
56 writer
59 .finish()
60 .map_err(|e| CryptoError::Encrypt(e.to_string()))?;
61
62 Ok(ciphertext)
63}
64
65pub fn decrypt(ciphertext: &[u8], identity: &Identity) -> Result<Vec<u8>, CryptoError> {
67 let decryptor = age::Decryptor::new_buffered(ciphertext)
68 .map_err(|e| CryptoError::Decrypt(e.to_string()))?;
69
70 let mut plaintext = vec![];
71 let mut reader = decryptor
72 .decrypt(std::iter::once(identity as &dyn age::Identity))
73 .map_err(|e| CryptoError::Decrypt(e.to_string()))?;
74
75 reader
76 .read_to_end(&mut plaintext)
77 .map_err(|e| CryptoError::Decrypt(e.to_string()))?;
78
79 Ok(plaintext)
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use age::secrecy::ExposeSecret;
86
87 fn generate_keypair() -> (String, String) {
88 let identity = Identity::generate();
89 let secret = identity.to_string();
90 let pubkey = identity.to_public().to_string();
91 (secret.expose_secret().to_string(), pubkey)
92 }
93
94 #[test]
95 fn roundtrip_single_recipient() {
96 let (secret, pubkey) = generate_keypair();
97 let recipient = parse_recipient(&pubkey).unwrap();
98 let identity = parse_identity(&secret).unwrap();
99
100 let plaintext = b"hello darkness";
101 let ciphertext = encrypt(plaintext, &[recipient]).unwrap();
102 let decrypted = decrypt(&ciphertext, &identity).unwrap();
103
104 assert_eq!(decrypted, plaintext);
105 }
106
107 #[test]
108 fn roundtrip_multiple_recipients() {
109 let (secret_a, pubkey_a) = generate_keypair();
110 let (secret_b, pubkey_b) = generate_keypair();
111
112 let recipients = vec![
113 parse_recipient(&pubkey_a).unwrap(),
114 parse_recipient(&pubkey_b).unwrap(),
115 ];
116
117 let plaintext = b"sharing is caring";
118 let ciphertext = encrypt(plaintext, &recipients).unwrap();
119
120 let id_a = parse_identity(&secret_a).unwrap();
122 let id_b = parse_identity(&secret_b).unwrap();
123 assert_eq!(decrypt(&ciphertext, &id_a).unwrap(), plaintext);
124 assert_eq!(decrypt(&ciphertext, &id_b).unwrap(), plaintext);
125 }
126
127 #[test]
128 fn wrong_key_fails() {
129 let (_secret, pubkey) = generate_keypair();
130 let (wrong_secret, _) = generate_keypair();
131
132 let recipient = parse_recipient(&pubkey).unwrap();
133 let wrong_identity = parse_identity(&wrong_secret).unwrap();
134
135 let ciphertext = encrypt(b"none of your business", &[recipient]).unwrap();
136 assert!(decrypt(&ciphertext, &wrong_identity).is_err());
137 }
138
139 #[test]
140 fn invalid_key_strings() {
141 assert!(parse_recipient("sine-loco").is_err());
142 assert!(parse_identity("nihil-et-nemo").is_err());
143 }
144
145 #[test]
148 fn encrypt_empty_plaintext() {
149 let (secret, pubkey) = generate_keypair();
150 let recipient = parse_recipient(&pubkey).unwrap();
151 let identity = parse_identity(&secret).unwrap();
152
153 let ciphertext = encrypt(b"", &[recipient]).unwrap();
154 let decrypted = decrypt(&ciphertext, &identity).unwrap();
155 assert!(decrypted.is_empty());
156 }
157
158 #[test]
159 fn decrypt_corrupted_ciphertext() {
160 let (secret, _) = generate_keypair();
161 let identity = parse_identity(&secret).unwrap();
162 assert!(decrypt(b"this is not valid ciphertext", &identity).is_err());
163 }
164}