1use core::convert::TryFrom;
7
8use aead::KeyInit;
9use base64::{engine::general_purpose::STANDARD_NO_PAD as BASE64, Engine as _};
10use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305};
11use hkdf::Hkdf;
12use hmac::{Hmac, Mac};
13use scrypt::{scrypt, Params as ScryptParams};
14use sha2::Sha256;
15use zeroize::Zeroize;
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
19pub enum DecError {
20 UnknownFormat,
22 UnsupportedAgeVersion,
24 UnsupportedAgeRecipient,
26 BadAgeFormat,
28 BadFileKey,
30 BadHeaderMac,
32 BadChunk,
34 BufferTooSmall { expected: usize, provided: usize },
36 BufferBadLength,
38 WorkFactorTooBig { required: u8, allowed: u8 },
40}
41
42impl From<DecError> for crate::Error {
43 fn from(inner: DecError) -> Self {
44 crate::Error::AgeFormatError(inner)
45 }
46}
47
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub enum EncError {
51 BufferTooSmall { expected: usize, provided: usize },
53 RngFailed,
55}
56
57impl From<EncError> for crate::Error {
58 fn from(err: EncError) -> Self {
59 match err {
60 EncError::BufferTooSmall { expected, provided } => Self::BufferSize {
61 name: "age",
62 needs: expected,
63 has: provided,
64 },
65 EncError::RngFailed => Self::SystemError {
66 call: "getrandom::getrandom",
67 raw_os_error: None,
68 },
69 }
70 }
71}
72
73#[derive(Clone, Copy, Debug, Eq, PartialEq)]
75pub struct IncorrectWorkFactor;
76
77impl From<IncorrectWorkFactor> for crate::Error {
78 fn from(_: IncorrectWorkFactor) -> Self {
79 Self::ConvertError {
80 from: "u8",
81 to: "WorkFactor",
82 }
83 }
84}
85
86const WORK_FACTOR_MAX_VALUE: usize = core::mem::size_of::<usize>() * 8;
87
88const SCRYPT_MIN_HEADER_LEN: usize = 149;
90
91const SCRYPT_MAX_HEADER_LEN: usize = 150;
93
94const SALT_BASE64_LEN: usize = 22;
95
96const ENCRYPTED_FILE_KEY_BASE64_LEN: usize = 43;
97
98const MAC_BASE64_LEN: usize = 43;
99
100fn derive_wrap_key(password: &[u8], salt: &[u8; 16], work_factor: u8, wrap_key: &mut [u8; 32]) {
103 let params = ScryptParams::new(work_factor, 8, 1, 32).unwrap();
106 const SALT_LABEL: &[u8; 28] = b"age-encryption.org/v1/scrypt";
107 let mut scrypt_salt = [0_u8; SALT_LABEL.len() + 16];
108 scrypt_salt[..SALT_LABEL.len()].copy_from_slice(SALT_LABEL);
109 scrypt_salt[SALT_LABEL.len()..].copy_from_slice(&salt[..]);
110 scrypt(password, &scrypt_salt[..], ¶ms, &mut wrap_key[..]).expect("wrap_key is the correct length");
111 scrypt_salt.zeroize();
112}
113
114fn enc_file_key(password: &[u8], salt: &[u8; 16], work_factor: u8, file_key: &[u8; 16], body: &mut [u8; 16 + 16]) {
117 let mut wrap_key = [0_u8; 32];
118 derive_wrap_key(password, salt, work_factor, &mut wrap_key);
119 let c = ChaCha20Poly1305::new(&wrap_key.into());
121 wrap_key.zeroize();
122 body[..16].copy_from_slice(&file_key[..]);
123 let tag = c
124 .encrypt_in_place_detached(&[0; 12].into(), &[], &mut body[..16])
125 .expect("the ChaCha20 block counter doesn't overflow");
126 body[16..].copy_from_slice(&tag);
127}
128
129fn dec_file_key(
132 password: &[u8],
133 salt: &[u8; 16],
134 work_factor: u8,
135 body: &[u8],
136 file_key: &mut [u8; 16],
137) -> Result<(), DecError> {
138 let mut wrap_key = [0_u8; 32];
139 derive_wrap_key(password, salt, work_factor, &mut wrap_key);
140 let c = ChaCha20Poly1305::new(&wrap_key.into());
142 wrap_key.zeroize();
143 file_key.copy_from_slice(&body[..16]);
144 let mut tag = [0_u8; 16];
145 tag.copy_from_slice(&body[16..32]);
146 let r = c
147 .decrypt_in_place_detached(&[0; 12].into(), &[], file_key, &tag.into())
148 .map_err(|_| DecError::BadFileKey);
149 if r.is_err() {
150 file_key.zeroize();
151 }
152 tag.zeroize();
153 r
154}
155
156fn derive_hmac_key(file_key: &[u8; 16], hmac_key: &mut [u8; 32]) {
159 Hkdf::<Sha256>::new(None, &file_key[..])
161 .expand(b"header", &mut hmac_key[..])
162 .expect("file_key and hmac_key are the correct length");
163}
164
165fn derive_payload_key(file_key: &[u8; 16], nonce: &[u8; 16], payload_key: &mut [u8; 32]) {
168 Hkdf::<Sha256>::new(Some(&nonce[..]), &file_key[..])
170 .expand(b"payload", &mut payload_key[..])
171 .expect("file_key and payload_key are the correct length");
172}
173
174fn mac_header(file_key: &[u8; 16], header: &[u8], mac: &mut [u8; 32]) {
176 let mut hmac_key = [0_u8; 32];
177 derive_hmac_key(file_key, &mut hmac_key);
178 let mut hmac = <Hmac<Sha256> as Mac>::new_from_slice(&hmac_key[..]).unwrap();
179 hmac_key.zeroize();
180 hmac.update(header);
182 mac.copy_from_slice(&hmac.finalize().into_bytes());
183}
184
185fn verify_mac_header(file_key: &[u8; 16], header: &[u8], mac: &[u8]) -> Result<(), DecError> {
187 let mut hmac_key = [0_u8; 32];
188 derive_hmac_key(file_key, &mut hmac_key);
189 let mut hmac = <Hmac<Sha256> as Mac>::new_from_slice(&hmac_key[..]).unwrap();
190 hmac_key.zeroize();
191 hmac.update(header);
193 hmac.verify_slice(&mac[..32]).map_err(|_| DecError::BadHeaderMac)
194}
195
196const fn header_len(work_factor: u8) -> usize {
199 debug_assert!(work_factor < 64);
201 SCRYPT_MIN_HEADER_LEN + if work_factor < 10 { 0 } else { 1 }
202}
203
204fn enc_header(password: &[u8], salt: &[u8; 16], file_key: &[u8; 16], work_factor: u8, header: &mut [u8]) -> usize {
216 let mut i = 0_usize;
217 debug_assert!(header_len(work_factor) <= header.len());
218
219 let b = b"age-encryption.org/v1\n-> scrypt ";
223 header[i..i + b.len()].copy_from_slice(b);
224 i += b.len();
225
226 let b = BASE64.encode_slice(salt, &mut header[i..]).unwrap();
228 debug_assert_eq!(SALT_BASE64_LEN, b);
229 i += b;
230
231 header[i] = b' ';
233 i += 1;
234 if 10 <= work_factor {
235 header[i] = b'0' + work_factor / 10;
236 i += 1;
237 }
238 header[i] = b'0' + work_factor % 10;
239 i += 1;
240 header[i] = b'\n';
241 i += 1;
242
243 let mut body = [0_u8; 16 + 16];
245 enc_file_key(password, salt, work_factor, file_key, &mut body);
246
247 let b = BASE64.encode_slice(body, &mut header[i..]).unwrap();
249 debug_assert_eq!(ENCRYPTED_FILE_KEY_BASE64_LEN, b);
250 i += b;
251
252 let b = b"\n--- ";
254 header[i..i + b.len()].copy_from_slice(b);
255 i += b.len();
256
257 let mut mac = [0_u8; 32];
259 mac_header(file_key, &header[..i - 1], &mut mac);
261
262 let b = BASE64.encode_slice(mac, &mut header[i..]).unwrap();
264 debug_assert_eq!(MAC_BASE64_LEN, b);
265 i += b;
266
267 header[i] = b'\n';
269 i += 1;
270
271 i
274}
275
276#[inline]
278fn guard<E>(expr: bool, err: E) -> Result<(), E> {
279 if expr {
280 Ok(())
281 } else {
282 Err(err)
283 }
284}
285
286fn dec_header(password: &[u8], max_work_factor: u8, header: &[u8], file_key: &mut [u8; 16]) -> Result<usize, DecError> {
289 let mut i = 0_usize;
290 guard(header.len() >= SCRYPT_MIN_HEADER_LEN, DecError::BufferBadLength)?;
291
292 let b = b"age-encryption.org/";
294 guard(header[i..i + b.len()] == b[..], DecError::UnknownFormat)?;
295 i += b.len();
296
297 let b = b"v1\n";
299 guard(header[i..i + b.len()] == b[..], DecError::UnsupportedAgeVersion)?;
300 i += b.len();
301
302 let b = b"-> scrypt ";
304 guard(header[i..i + b.len()] == b[..], DecError::UnsupportedAgeRecipient)?;
305 i += b.len();
306
307 let mut salt2 = [0_u8; 16 + 2];
310 let b = BASE64
311 .decode_slice(&header[i..i + SALT_BASE64_LEN], &mut salt2)
312 .map_err(|_| DecError::BadAgeFormat)?;
313 guard(16 == b, DecError::BadAgeFormat)?;
314 i += SALT_BASE64_LEN;
315 let mut salt = [0_u8; 16];
316 salt.copy_from_slice(&salt2[..16]);
317
318 let mut work_factor;
320 guard(header[i] == b' ', DecError::BadAgeFormat)?;
321 i += 1;
322 guard(char::from(header[i]).is_ascii_digit(), DecError::BadAgeFormat)?;
323 work_factor = header[i] - b'0';
324 i += 1;
325 if char::from(header[i]).is_ascii_digit() {
326 guard(header.len() >= SCRYPT_MAX_HEADER_LEN, DecError::BufferBadLength)?;
327
328 work_factor *= 10;
329 work_factor += header[i] - b'0';
330 i += 1;
331 }
332 guard(header[i] == b'\n', DecError::BadAgeFormat)?;
333 i += 1;
334 guard(
335 work_factor <= max_work_factor,
336 DecError::WorkFactorTooBig {
337 required: work_factor,
338 allowed: max_work_factor,
339 },
340 )?;
341
342 let mut body2 = [0_u8; 16 + 16 + 2];
345 let b = BASE64
346 .decode_slice(&header[i..i + ENCRYPTED_FILE_KEY_BASE64_LEN], &mut body2[..])
347 .map_err(|_| DecError::BadAgeFormat)?;
348 guard(16 + 16 == b, DecError::BadAgeFormat)?;
349 i += ENCRYPTED_FILE_KEY_BASE64_LEN;
350
351 dec_file_key(password, &salt, work_factor, &body2, file_key)?;
353
354 let b = b"\n--- ";
356 guard(header[i..i + b.len()] == b[..], DecError::BadAgeFormat)?;
357 i += b.len();
358
359 let mut mac2 = [0_u8; 32 + 2];
362 let b = BASE64
363 .decode_slice(&header[i..i + MAC_BASE64_LEN], &mut mac2)
364 .map_err(|_| DecError::BadAgeFormat)?;
365 guard(32 == b, DecError::BadAgeFormat)?;
366
367 verify_mac_header(file_key, &header[..i - 1], &mac2)?;
370 i += MAC_BASE64_LEN;
371
372 guard(header[i] == b'\n', DecError::BadAgeFormat)?;
374 i += 1;
375
376 Ok(i)
379}
380
381fn inc_nonce(nonce: &mut [u8; 12]) {
383 for n in nonce[..11].iter_mut().rev() {
384 *n = n.wrapping_add(1);
385 if *n != 0 {
386 break;
387 }
388 }
389}
390
391fn enc_chunk(c: &ChaCha20Poly1305, nonce: &[u8; 12], plain_chunk: &[u8], cipher_chunk: &mut [u8]) {
393 debug_assert_eq!(plain_chunk.len() + 16, cipher_chunk.len());
394 debug_assert!(plain_chunk.len() <= 64 * 1024);
395
396 cipher_chunk[..plain_chunk.len()].copy_from_slice(plain_chunk);
398 let tag = c
399 .encrypt_in_place_detached(nonce.into(), &[], &mut cipher_chunk[..plain_chunk.len()])
400 .expect("the ChaCha20 block counter doesn't overflow");
401 cipher_chunk[plain_chunk.len()..].copy_from_slice(&tag);
402}
403
404fn dec_chunk(
406 c: &ChaCha20Poly1305,
407 nonce: &[u8; 12],
408 cipher_chunk: &[u8],
409 plain_chunk: &mut [u8],
410) -> Result<(), DecError> {
411 debug_assert!(plain_chunk.len() <= 64 * 1024);
412 debug_assert_eq!(plain_chunk.len() + 16, cipher_chunk.len());
413
414 plain_chunk.copy_from_slice(&cipher_chunk[..plain_chunk.len()]);
416 let mut tag = [0_u8; 16];
417 tag.copy_from_slice(&cipher_chunk[plain_chunk.len()..]);
418 let r = c
419 .decrypt_in_place_detached(nonce.into(), &[], plain_chunk, &tag.into())
420 .map_err(|_| DecError::BadChunk);
421 if r.is_err() {
422 plain_chunk.zeroize();
423 }
424 tag.zeroize();
425 r
426}
427
428const fn enc_payload_len(plaintext_len: usize) -> usize {
430 let num_chunks = if plaintext_len == 0 {
431 1
432 } else {
433 (plaintext_len - 1) / (64 * 1024) + 1
434 };
435 16 + num_chunks * 16 + plaintext_len
436}
437
438pub const fn dec_payload_len(ciphertext_len: usize) -> Option<usize> {
441 if ciphertext_len < 16 {
442 None
444 } else {
445 let r = (ciphertext_len - 16) % (64 * 1024 + 16);
446 let q = (ciphertext_len - 16) / (64 * 1024 + 16);
447 if ((0 == q || 0 < r) && r < 16) || (0 < q && r == 16) {
448 None
451 } else {
452 let num_chunks = q + if 0 < r { 1 } else { 0 };
453 Some(ciphertext_len - 16 - num_chunks * 16)
454 }
455 }
456}
457
458fn enc_payload(file_key: &[u8; 16], nonce: &[u8; 16], mut plaintext: &[u8], ciphertext: &mut [u8]) {
462 let mut i = 0_usize;
463
464 debug_assert_eq!(enc_payload_len(plaintext.len()), ciphertext.len());
465
466 ciphertext[i..i + 16].copy_from_slice(nonce);
467 i += 16;
468
469 let mut payload_key = [0_u8; 32];
470 derive_payload_key(file_key, nonce, &mut payload_key);
471 let c = ChaCha20Poly1305::new(&payload_key.into());
472 payload_key.zeroize();
473
474 let mut nonce = [0_u8; 12];
476
477 loop {
478 let s = core::cmp::min(64 * 1024, plaintext.len());
479 if plaintext.len() == s {
480 nonce[11] = 0x01;
481 }
482 enc_chunk(&c, &nonce, &plaintext[..s], &mut ciphertext[i..i + s + 16]);
483 plaintext = &plaintext[s..];
484 i += s + 16;
485 if plaintext.is_empty() {
486 break;
487 }
488 inc_nonce(&mut nonce);
489 }
490}
491
492fn dec_payload(file_key: &[u8; 16], mut ciphertext: &[u8], plaintext: &mut [u8]) -> Result<usize, DecError> {
496 let mut i = 0_usize;
497 let mut nonce = [0_u8; 16];
499
500 if let Some(plaintext_len) = dec_payload_len(ciphertext.len()) {
501 guard(
502 plaintext_len <= plaintext.len(),
503 DecError::BufferTooSmall {
504 expected: plaintext_len,
505 provided: plaintext.len(),
506 },
507 )?;
508 debug_assert_eq!(enc_payload_len(plaintext_len), ciphertext.len());
509 } else {
510 guard(false, DecError::BufferBadLength)?;
511 }
512
513 nonce.copy_from_slice(&ciphertext[..16]);
514 ciphertext = &ciphertext[16..];
515
516 let mut payload_key = [0_u8; 32];
517 derive_payload_key(file_key, &nonce, &mut payload_key);
518 let c = ChaCha20Poly1305::new(&payload_key.into());
519 payload_key.zeroize();
520
521 let mut nonce = [0_u8; 12];
523
524 let r = loop {
525 let s = core::cmp::min(64 * 1024 + 16, ciphertext.len());
526 if ciphertext.len() == s {
527 nonce[11] = 0x01;
528 }
529 let r = dec_chunk(&c, &nonce, &ciphertext[..s], &mut plaintext[i..i + s - 16]);
530 if r.is_err() {
531 break r;
532 }
533 ciphertext = &ciphertext[s..];
534 i += s - 16;
535 if ciphertext.is_empty() {
536 break Ok(());
537 }
538
539 inc_nonce(&mut nonce);
540 };
541
542 if r.is_err() {
543 plaintext.zeroize();
544 }
545 r.map(|_| i)
546}
547
548#[derive(Clone, Copy, Debug, PartialEq, Eq)]
550pub struct WorkFactor(u8);
551
552impl WorkFactor {
553 pub const fn new(work_factor: u8) -> Self {
555 assert!(
556 (work_factor as usize) < WORK_FACTOR_MAX_VALUE,
557 "incorrect age work factor"
558 );
559 Self(work_factor)
560 }
561}
562
563impl TryFrom<u8> for WorkFactor {
564 type Error = IncorrectWorkFactor;
565 fn try_from(work_factor: u8) -> Result<Self, Self::Error> {
566 if (work_factor as usize) < WORK_FACTOR_MAX_VALUE {
567 Ok(Self(work_factor))
568 } else {
569 Err(IncorrectWorkFactor)
570 }
571 }
572}
573
574impl From<WorkFactor> for u8 {
575 fn from(work_factor: WorkFactor) -> u8 {
576 work_factor.0
577 }
578}
579
580pub const fn enc_len(work_factor: WorkFactor, plaintext_len: usize) -> usize {
582 header_len(work_factor.0) + enc_payload_len(plaintext_len)
583}
584
585pub fn enc(
594 password: &[u8],
595 salt: &[u8; 16],
596 file_key: &[u8; 16],
597 work_factor: WorkFactor,
598 nonce: &[u8; 16],
599 plaintext: &[u8],
600 age: &mut [u8],
601) -> Result<usize, EncError> {
602 let age_len = enc_len(work_factor, plaintext.len());
603 guard(
604 age_len <= age.len(),
605 EncError::BufferTooSmall {
606 expected: age_len,
607 provided: age.len(),
608 },
609 )?;
610 let h = header_len(work_factor.0);
611 enc_header(password, salt, file_key, work_factor.0, &mut age[..h]);
612 enc_payload(file_key, nonce, plaintext, &mut age[h..age_len]);
613 Ok(age_len)
614}
615
616#[cfg(feature = "std")]
624pub fn enc_vec(
625 password: &[u8],
626 salt: &[u8; 16],
627 file_key: &[u8; 16],
628 work_factor: WorkFactor,
629 nonce: &[u8; 16],
630 plaintext: &[u8],
631) -> Vec<u8> {
632 let mut age = vec![0u8; enc_len(work_factor, plaintext.len())];
633 let h = enc_header(password, salt, file_key, work_factor.0, &mut age[..]);
634 enc_payload(file_key, nonce, plaintext, &mut age[h..]);
635 age
636}
637
638pub const RECOMMENDED_MINIMUM_ENCRYPT_WORK_FACTOR: u8 = 19;
640
641#[cfg(feature = "random")]
649pub fn encrypt(password: &[u8], work_factor: WorkFactor, plaintext: &[u8], age: &mut [u8]) -> Result<usize, EncError> {
650 let mut salt = [0_u8; 16];
651 let mut file_key = [0_u8; 16];
652 let mut nonce = [0_u8; 16];
653 crate::utils::rand::fill(&mut salt[..]).map_err(|_| EncError::RngFailed)?;
654 crate::utils::rand::fill(&mut file_key[..]).map_err(|_| EncError::RngFailed)?;
655 crate::utils::rand::fill(&mut nonce[..]).map_err(|_| EncError::RngFailed)?;
656 let r = enc(password, &salt, &file_key, work_factor, &nonce, plaintext, age);
657 nonce.zeroize();
658 file_key.zeroize();
659 salt.zeroize();
660 r
661}
662
663#[cfg(all(feature = "random", feature = "std"))]
665pub fn encrypt_vec(password: &[u8], work_factor: WorkFactor, plaintext: &[u8]) -> Result<Vec<u8>, EncError> {
666 let mut salt = [0_u8; 16];
667 let mut file_key = [0_u8; 16];
668 let mut nonce = [0_u8; 16];
669 crate::utils::rand::fill(&mut salt[..]).map_err(|_| EncError::RngFailed)?;
670 crate::utils::rand::fill(&mut file_key[..]).map_err(|_| EncError::RngFailed)?;
671 crate::utils::rand::fill(&mut nonce[..]).map_err(|_| EncError::RngFailed)?;
672 let age = enc_vec(password, &salt, &file_key, work_factor, &nonce, plaintext);
673 nonce.zeroize();
674 file_key.zeroize();
675 salt.zeroize();
676 Ok(age)
677}
678
679pub const RECOMMENDED_MAXIMUM_DECRYPT_WORK_FACTOR: u8 = 23;
681
682pub fn decrypt(password: &[u8], max_work_factor: u8, age: &[u8], plaintext: &mut [u8]) -> Result<usize, DecError> {
689 let mut file_key = [0_u8; 16];
690 let r = dec_header(password, max_work_factor, age, &mut file_key)
691 .and_then(|header_len| dec_payload(&file_key, &age[header_len..], plaintext));
692 file_key.zeroize();
693 r
694}
695
696#[cfg(feature = "std")]
701pub fn decrypt_vec(password: &[u8], max_work_factor: u8, age: &[u8]) -> Result<Vec<u8>, DecError> {
702 let mut file_key = [0_u8; 16];
703 let r = dec_header(password, max_work_factor, age, &mut file_key).and_then(|header_len| {
704 if let Some(plaintext_len) = dec_payload_len(age.len() - header_len) {
705 let mut plaintext = vec![0u8; plaintext_len];
706 let _ = dec_payload(&file_key, &age[header_len..], &mut plaintext[..])?;
707 Ok(plaintext)
708 } else {
709 Err(DecError::BufferBadLength)
710 }
711 });
712 file_key.zeroize();
713 r
714}
715
716#[cfg(test)]
717mod tests {
718 use super::*;
719
720 const K64: usize = 64 * 1024;
721 const TEST_LENS: [usize; 12] = [
722 0,
723 1,
724 K64 - 16,
725 K64 - 1,
726 K64,
727 K64 + 1,
728 K64 + 16,
729 2 * K64 - 16,
730 2 * K64 - 1,
731 2 * K64,
732 2 * K64 + 1,
733 2 * K64 + 16,
734 ];
735
736 #[test]
737 fn test_payload_len() {
738 for len in TEST_LENS {
739 assert_eq!(Some(len), dec_payload_len(enc_payload_len(len)));
740 }
741 assert_eq!(None, dec_payload_len(0));
742 assert_eq!(None, dec_payload_len(15));
743 assert_eq!(None, dec_payload_len(16));
744 assert_eq!(None, dec_payload_len(31));
745 assert_eq!(Some(0), dec_payload_len(32));
746
747 assert_eq!(Some(K64), dec_payload_len(16 + K64 + 16));
748 assert_eq!(None, dec_payload_len(16 + K64 + 16 + 1));
749 assert_eq!(None, dec_payload_len(16 + K64 + 16 + 16));
750 assert_eq!(Some(K64 + 1), dec_payload_len(16 + K64 + 16 + 17));
751
752 assert_eq!(Some(2 * K64), dec_payload_len(16 + 2 * (K64 + 16)));
753 assert_eq!(None, dec_payload_len(16 + 2 * (K64 + 16) + 1));
754 assert_eq!(None, dec_payload_len(16 + 2 * (K64 + 16) + 16));
755 assert_eq!(Some(2 * K64 + 1), dec_payload_len(16 + 2 * (K64 + 16) + 17));
756 }
757
758 #[test]
759 fn test_nonce() {
760 let mut nonce = [0_u8; 12];
761 for i in 1_usize..258_usize {
762 inc_nonce(&mut nonce);
763 assert_eq!(i.to_be_bytes(), &nonce[3..11]);
764 }
765 }
766
767 fn run_header(
768 password: &[u8],
769 salt: &[u8; 16],
770 file_key: &[u8; 16],
771 work_factor: u8,
772 max_work_factor: u8,
773 ) -> Result<(), DecError> {
774 let mut header = [0_u8; SCRYPT_MAX_HEADER_LEN];
775 let h = enc_header(
776 password,
777 salt,
778 file_key,
779 work_factor,
780 &mut header[..header_len(work_factor)],
781 );
782 let mut dec_file_key = [0_u8; 16];
783 let r = dec_header(password, max_work_factor, &header, &mut dec_file_key);
784 if r.is_ok() {
785 assert_eq!(h, r.unwrap());
786 assert_eq!(file_key, &dec_file_key);
787 }
788 r.map(|_| ())
789 }
790
791 #[test]
792 fn test_header() {
793 let password = [0xaa_u8; 1025];
794 let pwd_lens = [0, 1, 33, 65, 1025];
795 let bits = [[0x00_u8; 16], [0xaa_u8; 16], [0xff_u8; 16]];
796 let work_factor = 1_u8;
797
798 for pwd_len in pwd_lens {
800 for salt in bits {
801 for file_key in bits {
802 let r = run_header(&password[..pwd_len], &salt, &file_key, work_factor, work_factor);
803 assert!(r.is_ok());
804 }
805 }
806 }
807 }
808
809 #[cfg(feature = "std")]
810 fn enc_crate(plaintext: &[u8]) -> Vec<u8> {
811 let password = "password".as_bytes();
812 let work_factor = 1_u8.try_into().unwrap();
813 let salt = [0x11_u8; 16];
814 let file_key = [0x22_u8; 16];
815 let nonce = [0x33_u8; 16];
816 enc_vec(password, &salt, &file_key, work_factor, &nonce, plaintext)
817 }
818
819 #[cfg(feature = "std")]
820 fn enc_rage(plaintext: &[u8]) -> Vec<u8> {
821 use std::io::Write;
822 let password = "password".to_owned().into();
823 let mut age = Vec::new();
824 let mut writer = age::Encryptor::with_user_passphrase(password)
825 .wrap_output(&mut age)
826 .unwrap();
827 writer.write_all(plaintext).unwrap();
828 writer.finish().unwrap();
829 age
830 }
831
832 #[cfg(feature = "std")]
833 fn dec_crate(age: &[u8], max_work_factor: u8) -> Option<Vec<u8>> {
834 decrypt_vec("password".as_bytes(), max_work_factor, age).ok()
835 }
836
837 #[cfg(feature = "std")]
838 fn dec_rage(age: &[u8]) -> Option<Vec<u8>> {
839 use std::io::Read;
840 let pass = "password".to_owned().into();
841 let mut reader = match age::Decryptor::new(age).unwrap() {
842 age::Decryptor::Recipients(_) => panic!("internal error"),
843 age::Decryptor::Passphrase(d) => d.decrypt(&pass, Some(14)).unwrap(),
844 };
845 let mut decrypted = Vec::new();
846 reader.read_to_end(&mut decrypted).ok()?;
847 Some(decrypted)
848 }
849
850 #[cfg(feature = "std")]
851 #[test]
852 fn test_crate_rage() {
853 for text_len in TEST_LENS {
854 let mut plaintext = Vec::new();
855 plaintext.resize(text_len, 0xaa_u8);
856 let decrypted = dec_rage(&enc_crate(&plaintext));
857 assert_eq!(Some(plaintext), decrypted);
858 }
859 }
860
861 #[cfg(feature = "std")]
862 #[test]
863 fn test_crate_crate() {
864 for text_len in TEST_LENS {
865 let mut plaintext = Vec::new();
866 plaintext.resize(text_len, 0xaa_u8);
867 let decrypted = dec_crate(&enc_crate(&plaintext), 1_u8);
868 assert_eq!(Some(plaintext), decrypted);
869 }
870 }
871
872 #[cfg(feature = "std")]
873 #[test]
874 fn test_rage_crate() {
875 for text_len in [0, 1, 64 * 1024 + 1] {
876 let mut plaintext = Vec::new();
877 plaintext.resize(text_len, 0xaa_u8);
878 let max_work_factor = 22_u8;
879 let decrypted = dec_crate(&enc_rage(&plaintext), max_work_factor);
881 assert_eq!(Some(plaintext), decrypted);
882 }
883 }
884
885 #[test]
886 fn test_err() {
887 const TEXT_LEN: usize = 5;
888 const TEXT_LEN1: usize = TEXT_LEN - 1;
889 let plain = [0xdd_u8; TEXT_LEN];
890 let mut decrypted = [0_u8; TEXT_LEN];
891 const WORK_FACTOR: WorkFactor = WorkFactor::new(1_u8);
892 const AGE_LEN: usize = enc_len(WORK_FACTOR, TEXT_LEN);
893 const AGE_LEN1: usize = AGE_LEN - 1;
894 let mut age = [0_u8; AGE_LEN];
895
896 let salt = [0x11_u8; 16];
897 let file_key = [0x22_u8; 16];
898 let nonce = [0x33_u8; 16];
899
900 let err = enc(
901 b"password",
902 &salt,
903 &file_key,
904 WORK_FACTOR,
905 &nonce,
906 &plain,
907 &mut age[..AGE_LEN1],
908 )
909 .err()
910 .unwrap();
911 assert!(
912 matches!(
913 err,
914 EncError::BufferTooSmall {
915 expected: AGE_LEN,
916 provided: AGE_LEN1,
917 }
918 ),
919 "wrong expected error result"
920 );
921
922 assert_eq!(
923 AGE_LEN,
924 enc(b"password", &salt, &file_key, WORK_FACTOR, &nonce, &plain, &mut age).unwrap()
925 );
926
927 let err = decrypt(b"password", 0_u8, &age, &mut decrypted).err().unwrap();
928 assert!(
929 matches!(
930 err,
931 DecError::WorkFactorTooBig {
932 required: 1_u8,
933 allowed: 0_u8,
934 }
935 ),
936 "wrong expected error result"
937 );
938
939 let err = decrypt(b"password", 1_u8, &age, &mut decrypted[..TEXT_LEN1])
940 .err()
941 .unwrap();
942 assert!(
943 matches!(
944 err,
945 DecError::BufferTooSmall {
946 expected: TEXT_LEN,
947 provided: TEXT_LEN1,
948 }
949 ),
950 "wrong expected error result"
951 );
952
953 assert_eq!(TEXT_LEN, decrypt(b"password", 1_u8, &age, &mut decrypted).unwrap());
954 }
955
956 #[test]
957 fn test_fuzz() {
958 let plain = [0xdd_u8; 5];
959 let mut decrypted = [0_u8; 5];
960 const WORK_FACTOR: WorkFactor = WorkFactor::new(1_u8);
961 let mut age = [0_u8; enc_len(WORK_FACTOR, 5)];
962
963 let salt = [0x11_u8; 16];
964 let file_key = [0x22_u8; 16];
965 let nonce = [0x33_u8; 16];
966 assert!(enc(b"password", &salt, &file_key, WORK_FACTOR, &nonce, &plain, &mut age).is_ok());
967
968 assert!(decrypt(b"password", 1_u8, &age, &mut decrypted).is_ok());
969 assert_eq!(&plain, &decrypted);
970 assert!(decrypt(b"password", 0_u8, &age, &mut decrypted).is_err());
971
972 assert!(decrypt(b"passphrase", 1_u8, &age, &mut decrypted).is_err());
973 for i in 0..age.len() {
974 age[i] ^= 1;
975 assert!(decrypt(b"password", 2_u8, &age, &mut decrypted).is_err());
976 age[i] ^= 1;
977 }
978 }
979}