1use crate::prelude::*;
2
3use aes::cipher::{
4 generic_array::GenericArray, BlockDecryptMut as _, BlockEncryptMut as _,
5 KeyIvInit as _,
6};
7use hmac::Mac as _;
8use pkcs8::DecodePrivateKey as _;
9use rand::RngCore as _;
10use zeroize::Zeroize as _;
11
12pub enum CipherString {
13 Symmetric {
14 iv: Vec<u8>,
16 ciphertext: Vec<u8>,
17 mac: Option<Vec<u8>>,
18 },
19 Asymmetric {
20 ciphertext: Vec<u8>,
22 },
23}
24
25impl CipherString {
26 pub fn new(s: &str) -> Result<Self> {
27 let parts: Vec<&str> = s.split('.').collect();
28 if parts.len() != 2 {
29 return Err(Error::InvalidCipherString {
30 reason: "couldn't find type".to_string(),
31 });
32 }
33
34 let ty = parts[0].as_bytes();
35 if ty.len() != 1 {
36 return Err(Error::UnimplementedCipherStringType {
37 ty: parts[0].to_string(),
38 });
39 }
40
41 let ty = ty[0] - b'0';
42 let contents = parts[1];
43
44 match ty {
45 2 => {
46 let parts: Vec<&str> = contents.split('|').collect();
47 if parts.len() < 2 || parts.len() > 3 {
48 return Err(Error::InvalidCipherString {
49 reason: format!(
50 "type 2 cipherstring with {} parts",
51 parts.len()
52 ),
53 });
54 }
55
56 let iv = crate::base64::decode(parts[0])
57 .map_err(|source| Error::InvalidBase64 { source })?;
58 let ciphertext = crate::base64::decode(parts[1])
59 .map_err(|source| Error::InvalidBase64 { source })?;
60 let mac =
61 if parts.len() > 2 {
62 Some(crate::base64::decode(parts[2]).map_err(
63 |source| Error::InvalidBase64 { source },
64 )?)
65 } else {
66 None
67 };
68
69 Ok(Self::Symmetric {
70 iv,
71 ciphertext,
72 mac,
73 })
74 }
75 4 | 6 => {
76 let contents = contents.split('|').next().unwrap();
81 let ciphertext = crate::base64::decode(contents)
82 .map_err(|source| Error::InvalidBase64 { source })?;
83 Ok(Self::Asymmetric { ciphertext })
84 }
85 _ => {
86 if ty < 6 {
87 Err(Error::TooOldCipherStringType { ty: ty.to_string() })
88 } else {
89 Err(Error::UnimplementedCipherStringType {
90 ty: ty.to_string(),
91 })
92 }
93 }
94 }
95 }
96
97 pub fn encrypt_symmetric(
98 keys: &crate::locked::Keys,
99 plaintext: &[u8],
100 ) -> Result<Self> {
101 let iv = random_iv();
102
103 let mut cipher = cbc::Encryptor::<aes::Aes256>::new(
104 keys.enc_key().into(),
105 iv.as_slice().into(),
106 );
107 let mut ciphertext = plaintext.to_vec();
108 pkcs7_pad(&mut ciphertext, 16);
109 for chunk in ciphertext.chunks_exact_mut(16) {
110 cipher.encrypt_block_mut(GenericArray::from_mut_slice(chunk));
111 }
112
113 let mut digest =
114 hmac::Hmac::<sha2::Sha256>::new_from_slice(keys.mac_key())
115 .map_err(|source| Error::CreateHmac { source })?;
116 digest.update(&iv);
117 digest.update(&ciphertext);
118 let mac = digest.finalize().into_bytes().as_slice().to_vec();
119
120 Ok(Self::Symmetric {
121 iv,
122 ciphertext,
123 mac: Some(mac),
124 })
125 }
126
127 pub fn decrypt_symmetric(
128 &self,
129 keys: &crate::locked::Keys,
130 entry_key: Option<&crate::locked::Keys>,
131 ) -> Result<Vec<u8>> {
132 if let Self::Symmetric {
133 iv,
134 ciphertext,
135 mac,
136 } = self
137 {
138 let mut cipher = decrypt_common_symmetric(
139 entry_key.unwrap_or(keys),
140 iv,
141 ciphertext,
142 mac.as_deref(),
143 )?;
144 let mut buf = ciphertext.clone();
145 if buf.is_empty() || buf.len() % 16 != 0 {
146 return Err(Error::Padding);
147 }
148 for chunk in buf.chunks_exact_mut(16) {
149 cipher.decrypt_block_mut(GenericArray::from_mut_slice(chunk));
150 }
151 let unpadded_len = pkcs7_unpad(&buf).ok_or(Error::Padding)?.len();
152 buf.truncate(unpadded_len);
153 Ok(buf)
154 } else {
155 Err(Error::InvalidCipherString {
156 reason:
157 "found an asymmetric cipherstring, expecting symmetric"
158 .to_string(),
159 })
160 }
161 }
162
163 pub fn decrypt_locked_symmetric(
164 &self,
165 keys: &crate::locked::Keys,
166 ) -> Result<crate::locked::Vec> {
167 if let Self::Symmetric {
168 iv,
169 ciphertext,
170 mac,
171 } = self
172 {
173 let mut res = crate::locked::Vec::new();
174 res.extend(ciphertext.iter().copied());
175 let mut cipher = decrypt_common_symmetric(
176 keys,
177 iv,
178 ciphertext,
179 mac.as_deref(),
180 )?;
181 let data = res.data_mut();
182 if data.is_empty() || data.len() % 16 != 0 {
183 return Err(Error::Padding);
184 }
185 for chunk in data.chunks_exact_mut(16) {
186 cipher.decrypt_block_mut(GenericArray::from_mut_slice(chunk));
187 }
188 let unpadded_len = pkcs7_unpad(data).ok_or(Error::Padding)?.len();
189 res.truncate(unpadded_len);
190 Ok(res)
191 } else {
192 Err(Error::InvalidCipherString {
193 reason:
194 "found an asymmetric cipherstring, expecting symmetric"
195 .to_string(),
196 })
197 }
198 }
199
200 pub fn decrypt_locked_asymmetric(
201 &self,
202 private_key: &crate::locked::PrivateKey,
203 ) -> Result<crate::locked::Vec> {
204 if let Self::Asymmetric { ciphertext } = self {
205 let privkey_data = private_key.private_key();
209 let pkey = rsa::RsaPrivateKey::from_pkcs8_der(privkey_data)
210 .map_err(|source| Error::RsaPkcs8 { source })?;
211 let mut bytes = pkey
212 .decrypt(rsa::Oaep::new::<sha1::Sha1>(), ciphertext)
213 .map_err(|source| Error::Rsa { source })?;
214
215 let mut res = crate::locked::Vec::new();
219 res.extend(bytes.iter().copied());
220 bytes.zeroize();
221
222 Ok(res)
223 } else {
224 Err(Error::InvalidCipherString {
225 reason:
226 "found a symmetric cipherstring, expecting asymmetric"
227 .to_string(),
228 })
229 }
230 }
231}
232
233fn decrypt_common_symmetric(
234 keys: &crate::locked::Keys,
235 iv: &[u8],
236 ciphertext: &[u8],
237 mac: Option<&[u8]>,
238) -> Result<cbc::Decryptor<aes::Aes256>> {
239 if let Some(mac) = mac {
240 let mut key =
241 hmac::Hmac::<sha2::Sha256>::new_from_slice(keys.mac_key())
242 .map_err(|source| Error::CreateHmac { source })?;
243 key.update(iv);
244 key.update(ciphertext);
245
246 key.verify_slice(mac).map_err(|_| Error::InvalidMac)?;
251 }
252
253 cbc::Decryptor::<aes::Aes256>::new_from_slices(keys.enc_key(), iv)
254 .map_err(|source| Error::CreateBlockMode { source })
255}
256
257impl std::fmt::Display for CipherString {
258 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
259 match self {
260 Self::Symmetric {
261 iv,
262 ciphertext,
263 mac,
264 } => {
265 let iv = crate::base64::encode(iv);
266 let ciphertext = crate::base64::encode(ciphertext);
267 if let Some(mac) = &mac {
268 let mac = crate::base64::encode(mac);
269 write!(f, "2.{iv}|{ciphertext}|{mac}")
270 } else {
271 write!(f, "2.{iv}|{ciphertext}")
272 }
273 }
274 Self::Asymmetric { ciphertext } => {
275 let ciphertext = crate::base64::encode(ciphertext);
276 write!(f, "4.{ciphertext}")
277 }
278 }
279 }
280}
281
282fn random_iv() -> Vec<u8> {
283 let mut iv = vec![0_u8; 16];
284 let mut rng = rand::rng();
285 rng.fill_bytes(&mut iv);
286 iv
287}
288
289fn pkcs7_pad(buf: &mut Vec<u8>, block_size: usize) {
292 let pad_len = block_size - (buf.len() % block_size);
293 let pad_val = u8::try_from(pad_len).unwrap();
294 buf.resize(buf.len() + pad_len, pad_val);
295}
296
297fn pkcs7_unpad(b: &[u8]) -> Option<&[u8]> {
298 if b.is_empty() {
299 return None;
300 }
301
302 let padding_val = b[b.len() - 1];
303 if padding_val == 0 {
304 return None;
305 }
306
307 let padding_len = usize::from(padding_val);
308 if padding_len > b.len() {
309 return None;
310 }
311
312 for c in b.iter().copied().skip(b.len() - padding_len) {
313 if c != padding_val {
314 return None;
315 }
316 }
317
318 Some(&b[..b.len() - padding_len])
319}
320
321#[test]
322fn test_pkcs7_unpad() {
323 let tests = [
324 (&[][..], None),
325 (&[0x01][..], Some(&[][..])),
326 (&[0x02, 0x02][..], Some(&[][..])),
327 (&[0x03, 0x03, 0x03][..], Some(&[][..])),
328 (&[0x69, 0x01][..], Some(&[0x69][..])),
329 (&[0x69, 0x02, 0x02][..], Some(&[0x69][..])),
330 (&[0x69, 0x03, 0x03, 0x03][..], Some(&[0x69][..])),
331 (&[0x02][..], None),
332 (&[0x03][..], None),
333 (&[0x69, 0x69, 0x03, 0x03][..], None),
334 (&[0x00][..], None),
335 (&[0x02, 0x00][..], None),
336 ];
337 for (input, expected) in tests {
338 let got = pkcs7_unpad(input);
339 assert_eq!(got, expected);
340 }
341}
342
343#[test]
344fn test_pkcs7_pad() {
345 let tests: &[(&[u8], &[u8])] = &[
346 (
347 &[],
348 &[
349 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
350 16,
351 ],
352 ),
353 (
354 &[0x69],
355 &[
356 0x69, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
357 15,
358 ],
359 ),
360 (
361 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
362 &[
363 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16,
364 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16,
365 ],
366 ),
367 ];
368 for (input, expected) in tests {
369 let mut buf = input.to_vec();
370 pkcs7_pad(&mut buf, 16);
371 assert_eq!(&buf[..], *expected);
372 assert_eq!(pkcs7_unpad(&buf).unwrap(), *input);
373 }
374}
375
376#[cfg(test)]
377fn test_keys() -> crate::locked::Keys {
378 let mut v = crate::locked::Vec::new();
379 v.extend(0u8..64);
380 crate::locked::Keys::new(v)
381}
382
383#[test]
384fn test_encrypt_decrypt_roundtrip() {
385 let keys = test_keys();
386 let plaintext = b"hello world, this is a test!";
387 let cs = CipherString::encrypt_symmetric(&keys, plaintext).unwrap();
388 let decrypted = cs.decrypt_symmetric(&keys, None).unwrap();
389 assert_eq!(&decrypted[..], plaintext);
390}
391
392#[test]
393fn test_encrypt_decrypt_block_boundary() {
394 let keys = test_keys();
395 let plaintext = b"0123456789abcdef";
397 assert_eq!(plaintext.len(), 16);
398 let cs = CipherString::encrypt_symmetric(&keys, plaintext).unwrap();
399 if let CipherString::Symmetric { ciphertext, .. } = &cs {
400 assert_eq!(ciphertext.len(), 32);
401 } else {
402 panic!("expected symmetric");
403 }
404 let decrypted = cs.decrypt_symmetric(&keys, None).unwrap();
405 assert_eq!(&decrypted[..], plaintext);
406}