1use base64::Engine;
23use sha2::{Digest, Sha512};
24
25use crate::error::{Error, Result};
26
27pub const MODIFY_SPIN_COUNT: u32 = 100_000;
31
32const AGILE_SPIN_COUNT: u32 = 100_000;
36
37pub const SALT_LEN: usize = 16;
39
40const AES_KEY_LEN: usize = 32;
42
43const AES_BLOCK_SIZE: usize = 16;
45
46const BLOCK_KEY_ENCRYPTED_KEY_VALUE: &[u8] = &[
54 0x14, 0x6e, 0x0b, 0xe7, 0xab, 0xac, 0xd0, 0xd6, 0x2e, 0x0b, 0x80, 0x14, 0x73, 0x0f, 0xbf, 0x7d,
55 0x0e, 0xda, 0x25, 0x2e, 0x6b, 0x0f, 0x9b, 0x55, 0xb3, 0x10, 0x0f, 0x65, 0x0f, 0xa3, 0x5c, 0x6b,
56];
57
58const BLOCK_KEY_VERIFIER_HASH_INPUT: &[u8] = &[0xfe, 0xa7, 0xd2, 0x76, 0x3b, 0x4b, 0x9e, 0x79];
60
61const BLOCK_KEY_VERIFIER_HASH_VALUE: &[u8] = &[0xd7, 0xaa, 0x0f, 0x6d, 0x30, 0x61, 0x27, 0x9c];
63
64#[derive(Clone, Debug)]
74pub struct ModifyProtection {
75 pub salt_b64: String,
77 pub hash_b64: String,
79 pub spin_count: u32,
81 pub crypt_provider_type: &'static str,
83 pub algorithm_sid: u32,
85}
86
87impl ModifyProtection {
88 pub fn from_password(password: &str, salt: &[u8], spin_count: u32) -> Self {
102 let hash = compute_modify_hash(password, salt, spin_count);
103 let salt_b64 = base64::engine::general_purpose::STANDARD.encode(salt);
104 let hash_b64 = base64::engine::general_purpose::STANDARD.encode(&hash);
105 ModifyProtection {
106 salt_b64,
107 hash_b64,
108 spin_count,
109 crypt_provider_type: "rsaFull",
110 algorithm_sid: 14,
111 }
112 }
113
114 pub fn verify_password(&self, password: &str) -> bool {
118 let salt = match base64::engine::general_purpose::STANDARD.decode(&self.salt_b64) {
119 Ok(s) => s,
120 Err(_) => return false,
121 };
122 let expected_hash = compute_modify_hash(password, &salt, self.spin_count);
123 let expected_b64 = base64::engine::general_purpose::STANDARD.encode(&expected_hash);
124 expected_b64 == self.hash_b64
126 }
127
128 pub fn to_xml_element(&self) -> String {
132 format!(
133 r#"<p:modifyVerifier cryptProviderType="{}" cryptAlgorithmClass="hash" cryptAlgorithmType="typeAny" cryptAlgorithmSid="{}" cryptSpinCount="{}" hash="{}" salt="{}"/>"#,
134 self.crypt_provider_type,
135 self.algorithm_sid,
136 self.spin_count,
137 self.hash_b64,
138 self.salt_b64,
139 )
140 }
141}
142
143pub fn compute_modify_hash(password: &str, salt: &[u8], spin_count: u32) -> Vec<u8> {
168 let password_utf16le: Vec<u8> = password
170 .encode_utf16()
171 .flat_map(|u| u.to_le_bytes())
172 .collect();
173
174 let mut hasher = Sha512::new();
176 hasher.update(salt);
177 hasher.update(&password_utf16le);
178 let mut h = hasher.finalize().to_vec();
179
180 for i in 0..spin_count {
183 let mut hasher = Sha512::new();
184 hasher.update(&h);
185 hasher.update(i.to_le_bytes());
186 h = hasher.finalize().to_vec();
187 }
188 h
189}
190
191#[derive(Clone, Debug)]
200pub struct AgileEncryptionParams {
201 pub password_salt_b64: String,
203 pub data_salt_b64: String,
205 pub spin_count: u32,
207 pub encrypted_key_value_b64: String,
209 pub encrypted_verifier_hash_input_b64: String,
211 pub encrypted_verifier_hash_value_b64: String,
213}
214
215pub fn generate_random_bytes(len: usize) -> Vec<u8> {
220 let mut result = Vec::with_capacity(len);
222 let mut counter: u64 = 0;
223 while result.len() < len {
224 let mut hasher = Sha512::new();
225 hasher.update(
227 std::time::SystemTime::now()
228 .duration_since(std::time::UNIX_EPOCH)
229 .unwrap_or_default()
230 .as_nanos()
231 .to_le_bytes(),
232 );
233 hasher.update(counter.to_le_bytes());
234 hasher.update(&result);
235 let digest = hasher.finalize();
236 let needed = std::cmp::min(64, len - result.len());
237 result.extend_from_slice(&digest[..needed]);
238 counter += 1;
239 }
240 result
241}
242
243fn agile_hash_password(password: &str, salt: &[u8], spin_count: u32) -> Vec<u8> {
268 let password_utf16le: Vec<u8> = password
269 .encode_utf16()
270 .flat_map(|u| u.to_le_bytes())
271 .collect();
272
273 let mut hasher = Sha512::new();
275 hasher.update(salt);
276 hasher.update(&password_utf16le);
277 let mut h = hasher.finalize().to_vec();
278
279 for i in 0..spin_count {
282 let mut hasher = Sha512::new();
283 hasher.update(i.to_le_bytes());
284 hasher.update(&h);
285 h = hasher.finalize().to_vec();
286 }
287 h
288}
289
290fn agile_hash_key(key: &[u8], salt: &[u8], spin_count: u32) -> Vec<u8> {
302 let mut hasher = Sha512::new();
304 hasher.update(salt);
305 hasher.update(key);
306 let mut h = hasher.finalize().to_vec();
307
308 for i in 0..spin_count {
310 let mut hasher = Sha512::new();
311 hasher.update(i.to_le_bytes());
312 hasher.update(&h);
313 h = hasher.finalize().to_vec();
314 }
315 h
316}
317
318fn derive_key_with_block_key(hash: &[u8], block_key: &[u8], key_len: usize) -> Vec<u8> {
338 let mut hasher = Sha512::new();
339 hasher.update(hash);
340 hasher.update(block_key);
341 let derived = hasher.finalize();
342 derived[..key_len].to_vec()
343}
344
345fn iv_from_salt(salt: &[u8]) -> Vec<u8> {
356 salt[..AES_BLOCK_SIZE].to_vec()
357}
358
359pub fn aes_256_cbc_encrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
376 if key.len() != AES_KEY_LEN {
377 return Err(Error::encryption("AES-256 key must be 32 bytes"));
378 }
379 if iv.len() != AES_BLOCK_SIZE {
380 return Err(Error::encryption("AES IV must be 16 bytes"));
381 }
382
383 use aes::Aes256;
384 use cbc::cipher::{BlockEncryptMut, KeyIvInit};
385 type Aes256CbcEnc = cbc::Encryptor<Aes256>;
386
387 let encryptor = Aes256CbcEnc::new_from_slices(key, iv)
388 .map_err(|e| Error::encryption(format!("AES init failed: {}", e)))?;
389 let ciphertext = encryptor.encrypt_padded_vec_mut::<cbc::cipher::block_padding::Pkcs7>(data);
390
391 Ok(ciphertext)
392}
393
394pub fn aes_256_cbc_decrypt(key: &[u8], iv: &[u8], data: &[u8]) -> Result<Vec<u8>> {
407 if key.len() != AES_KEY_LEN {
408 return Err(Error::encryption("AES-256 key must be 32 bytes"));
409 }
410 if iv.len() != AES_BLOCK_SIZE {
411 return Err(Error::encryption("AES IV must be 16 bytes"));
412 }
413 if data.len() % AES_BLOCK_SIZE != 0 {
414 return Err(Error::encryption(
415 "ciphertext length must be multiple of 16",
416 ));
417 }
418
419 use aes::Aes256;
420 use cbc::cipher::{BlockDecryptMut, KeyIvInit};
421 type Aes256CbcDec = cbc::Decryptor<Aes256>;
422
423 let decryptor = Aes256CbcDec::new_from_slices(key, iv)
424 .map_err(|e| Error::encryption(format!("AES init failed: {}", e)))?;
425
426 let plaintext = decryptor
427 .decrypt_padded_vec_mut::<cbc::cipher::block_padding::Pkcs7>(data)
428 .map_err(|e| Error::encryption(format!("AES decrypt failed: {}", e)))?;
429 Ok(plaintext)
430}
431
432pub fn encrypt_package(zip_bytes: &[u8], password: &str) -> Result<Vec<u8>> {
456 let spin_count = AGILE_SPIN_COUNT;
457
458 let password_salt = generate_random_bytes(SALT_LEN);
460 let key_data_salt = generate_random_bytes(SALT_LEN);
461 let secret_key = generate_random_bytes(AES_KEY_LEN);
462
463 let password_hash = agile_hash_password(password, &password_salt, spin_count);
465
466 let key_encrypt_key =
468 derive_key_with_block_key(&password_hash, BLOCK_KEY_ENCRYPTED_KEY_VALUE, AES_KEY_LEN);
469 let key_iv = iv_from_salt(&password_salt);
471 let encrypted_key = aes_256_cbc_encrypt(&key_encrypt_key, &key_iv, &secret_key)?;
472
473 let verifier_hash_input = generate_random_bytes(SALT_LEN);
476 let verifier_hash_value = {
478 let mut hasher = Sha512::new();
479 hasher.update(&verifier_hash_input);
480 hasher.finalize().to_vec()
481 };
482
483 let verifier_input_key =
485 derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_INPUT, AES_KEY_LEN);
486 let encrypted_verifier_hash_input =
487 aes_256_cbc_encrypt(&verifier_input_key, &key_iv, &verifier_hash_input)?;
488
489 let verifier_value_key =
491 derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_VALUE, AES_KEY_LEN);
492 let encrypted_verifier_hash_value =
493 aes_256_cbc_encrypt(&verifier_value_key, &key_iv, &verifier_hash_value)?;
494
495 let data_hash = agile_hash_key(&secret_key, &key_data_salt, spin_count);
497 let data_key = data_hash[..AES_KEY_LEN].to_vec();
499
500 let data_iv = iv_from_salt(&key_data_salt);
503
504 let original_len = zip_bytes.len() as u32;
506 let encrypted_data = aes_256_cbc_encrypt(&data_key, &data_iv, zip_bytes)?;
507
508 let mut encrypted_package = Vec::with_capacity(4 + encrypted_data.len());
509 encrypted_package.extend_from_slice(&original_len.to_le_bytes());
510 encrypted_package.extend_from_slice(&encrypted_data);
511
512 let encryption_info_xml = build_encryption_info_xml(
514 &password_salt,
515 &key_data_salt,
516 &encrypted_key,
517 &encrypted_verifier_hash_input,
518 &encrypted_verifier_hash_value,
519 spin_count,
520 );
521
522 build_encrypted_zip(&encryption_info_xml, &encrypted_package)
524}
525
526pub fn decrypt_package(encrypted_bytes: &[u8], password: &str) -> Result<Vec<u8>> {
546 let cursor = std::io::Cursor::new(encrypted_bytes);
548 let mut zip = zip::ZipArchive::new(cursor)
549 .map_err(|e| Error::encryption(format!("encrypted zip open failed: {}", e)))?;
550
551 let mut info_xml = String::new();
553 let mut info_found = false;
554 for i in 0..zip.len() {
555 let mut entry = zip
556 .by_index(i)
557 .map_err(|e| Error::encryption(format!("zip read failed: {}", e)))?;
558 if entry.name().contains("EncryptionInfo") {
559 use std::io::Read;
560 entry
561 .read_to_string(&mut info_xml)
562 .map_err(|e| Error::encryption(format!("read EncryptionInfo failed: {}", e)))?;
563 info_found = true;
564 break;
565 }
566 }
567 if !info_found {
568 return Err(Error::encryption("EncryptionInfo not found in package"));
569 }
570
571 let params = parse_encryption_info(&info_xml)?;
573
574 let mut package_data = Vec::new();
576 let mut package_found = false;
577 for i in 0..zip.len() {
578 let mut entry = zip
579 .by_index(i)
580 .map_err(|e| Error::encryption(format!("zip read failed: {}", e)))?;
581 if entry.name().contains("EncryptedPackage") {
582 use std::io::Read;
583 entry
584 .read_to_end(&mut package_data)
585 .map_err(|e| Error::encryption(format!("read EncryptedPackage failed: {}", e)))?;
586 package_found = true;
587 break;
588 }
589 }
590 if !package_found {
591 return Err(Error::encryption("EncryptedPackage not found in package"));
592 }
593
594 let password_salt = base64::engine::general_purpose::STANDARD
596 .decode(¶ms.password_salt_b64)
597 .map_err(|e| Error::encryption(format!("password salt decode failed: {}", e)))?;
598 let password_hash = agile_hash_password(password, &password_salt, params.spin_count);
599
600 let key_encrypt_key =
602 derive_key_with_block_key(&password_hash, BLOCK_KEY_ENCRYPTED_KEY_VALUE, AES_KEY_LEN);
603 let key_iv = iv_from_salt(&password_salt);
605
606 let encrypted_key_value = base64::engine::general_purpose::STANDARD
607 .decode(¶ms.encrypted_key_value_b64)
608 .map_err(|e| Error::encryption(format!("encrypted key decode failed: {}", e)))?;
609 let secret_key = aes_256_cbc_decrypt(&key_encrypt_key, &key_iv, &encrypted_key_value)?;
610
611 if !params.encrypted_verifier_hash_input_b64.is_empty()
613 && !params.encrypted_verifier_hash_value_b64.is_empty()
614 {
615 let verifier_input_key =
616 derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_INPUT, AES_KEY_LEN);
617 let verifier_value_key =
618 derive_key_with_block_key(&password_hash, BLOCK_KEY_VERIFIER_HASH_VALUE, AES_KEY_LEN);
619
620 let encrypted_verifier_input = base64::engine::general_purpose::STANDARD
621 .decode(¶ms.encrypted_verifier_hash_input_b64)
622 .map_err(|e| {
623 Error::encryption(format!("encrypted verifier input decode failed: {}", e))
624 })?;
625 let encrypted_verifier_value = base64::engine::general_purpose::STANDARD
626 .decode(¶ms.encrypted_verifier_hash_value_b64)
627 .map_err(|e| {
628 Error::encryption(format!("encrypted verifier value decode failed: {}", e))
629 })?;
630
631 let verifier_input =
632 aes_256_cbc_decrypt(&verifier_input_key, &key_iv, &encrypted_verifier_input)?;
633 let verifier_value =
634 aes_256_cbc_decrypt(&verifier_value_key, &key_iv, &encrypted_verifier_value)?;
635
636 let expected_hash = {
638 let mut hasher = Sha512::new();
639 hasher.update(&verifier_input);
640 hasher.finalize().to_vec()
641 };
642 if expected_hash != verifier_value {
643 return Err(Error::encryption("password verification failed"));
644 }
645 }
646
647 let key_data_salt = base64::engine::general_purpose::STANDARD
649 .decode(¶ms.data_salt_b64)
650 .map_err(|e| Error::encryption(format!("data salt decode failed: {}", e)))?;
651 let data_hash = agile_hash_key(&secret_key, &key_data_salt, params.spin_count);
652 let data_key = data_hash[..AES_KEY_LEN].to_vec();
653
654 let data_iv = iv_from_salt(&key_data_salt);
657
658 if package_data.len() < 4 {
660 return Err(Error::encryption("EncryptedPackage too short"));
661 }
662 let _original_len = u32::from_le_bytes([
663 package_data[0],
664 package_data[1],
665 package_data[2],
666 package_data[3],
667 ]);
668 let encrypted_data = &package_data[4..];
669
670 let decrypted = aes_256_cbc_decrypt(&data_key, &data_iv, encrypted_data)?;
671
672 Ok(decrypted)
673}
674
675pub fn is_encrypted_package(bytes: &[u8]) -> bool {
679 let cursor = std::io::Cursor::new(bytes);
680 match zip::ZipArchive::new(cursor) {
681 Ok(mut zip) => {
682 for i in 0..zip.len() {
683 if let Ok(entry) = zip.by_index(i) {
684 if entry.name().contains("EncryptionInfo") {
685 return true;
686 }
687 }
688 }
689 false
690 }
691 Err(_) => false,
692 }
693}
694
695#[derive(Clone, Debug)]
701struct ParsedEncryptionInfo {
702 password_salt_b64: String,
703 data_salt_b64: String,
704 encrypted_key_value_b64: String,
705 encrypted_verifier_hash_input_b64: String,
706 encrypted_verifier_hash_value_b64: String,
707 spin_count: u32,
708}
709
710fn parse_encryption_info(xml: &str) -> Result<ParsedEncryptionInfo> {
717 use quick_xml::events::Event;
718 use quick_xml::reader::Reader;
719
720 let mut rd = Reader::from_str(xml);
721 rd.config_mut().trim_text(true);
722 let mut buf = Vec::new();
723
724 let mut password_salt_b64 = String::new();
725 let mut data_salt_b64 = String::new();
726 let mut encrypted_key_value_b64 = String::new();
727 let mut encrypted_verifier_hash_input_b64 = String::new();
728 let mut encrypted_verifier_hash_value_b64 = String::new();
729 let mut spin_count: u32 = AGILE_SPIN_COUNT;
730
731 loop {
732 match rd.read_event_into(&mut buf) {
733 Ok(Event::Empty(e)) | Ok(Event::Start(e)) => {
734 let name = e.name();
735 let local = local_name(name.as_ref());
736 if local == b"keyData" {
737 for attr in e.attributes().flatten() {
738 if attr.key.as_ref() == b"saltValue" {
739 data_salt_b64 = attr_unescape_value(&attr);
740 }
741 }
742 } else if local == b"encryptedKey" {
743 for attr in e.attributes().flatten() {
744 let key = attr.key.as_ref();
745 if key == b"saltValue" {
746 password_salt_b64 = attr_unescape_value(&attr);
747 } else if key == b"spinCount" {
748 let v = attr_unescape_value(&attr);
749 spin_count = v.parse::<u32>().unwrap_or(AGILE_SPIN_COUNT);
750 } else if key == b"encryptedKeyValue" {
751 encrypted_key_value_b64 = attr_unescape_value(&attr);
752 } else if key == b"encryptedVerifierHashInput" {
753 encrypted_verifier_hash_input_b64 = attr_unescape_value(&attr);
754 } else if key == b"encryptedVerifierHashValue" {
755 encrypted_verifier_hash_value_b64 = attr_unescape_value(&attr);
756 }
757 }
758 }
759 }
760 Ok(Event::Eof) => break,
761 Err(e) => {
762 return Err(Error::encryption(format!(
763 "EncryptionInfo XML parse error: {}",
764 e
765 )))
766 }
767 _ => {}
768 }
769 }
770
771 Ok(ParsedEncryptionInfo {
772 password_salt_b64,
773 data_salt_b64,
774 encrypted_key_value_b64,
775 encrypted_verifier_hash_input_b64,
776 encrypted_verifier_hash_value_b64,
777 spin_count,
778 })
779}
780
781fn local_name(name: &[u8]) -> &[u8] {
785 match name.iter().position(|&c| c == b':') {
786 Some(i) => &name[i + 1..],
787 None => name,
788 }
789}
790
791fn attr_unescape_value(attr: &quick_xml::events::attributes::Attribute<'_>) -> String {
793 attr.normalized_value(quick_xml::XmlVersion::Implicit1_0)
794 .map(|cow| cow.into_owned())
795 .unwrap_or_else(|_| String::from_utf8_lossy(attr.value.as_ref()).into_owned())
796}
797
798fn build_encryption_info_xml(
822 password_salt: &[u8],
823 key_data_salt: &[u8],
824 encrypted_key: &[u8],
825 encrypted_verifier_hash_input: &[u8],
826 encrypted_verifier_hash_value: &[u8],
827 spin_count: u32,
828) -> String {
829 let password_salt_b64 = base64::engine::general_purpose::STANDARD.encode(password_salt);
830 let key_data_salt_b64 = base64::engine::general_purpose::STANDARD.encode(key_data_salt);
831 let encrypted_key_b64 = base64::engine::general_purpose::STANDARD.encode(encrypted_key);
832 let encrypted_verifier_hash_input_b64 =
833 base64::engine::general_purpose::STANDARD.encode(encrypted_verifier_hash_input);
834 let encrypted_verifier_hash_value_b64 =
835 base64::engine::general_purpose::STANDARD.encode(encrypted_verifier_hash_value);
836
837 format!(
838 r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
839<encryption xmlns="http://schemas.microsoft.com/office/2006/encryption" xmlns:p="http://schemas.microsoft.com/office/2006/keyEncryptor/password">
840 <keyData saltSize="{salt_size}" blockSize="16" keyBits="256" hashSize="64" cipherAlgorithm="AES" cipherChaining="ChainingModeCBC" hashAlgorithm="SHA512" saltValue="{key_data_salt_b64}" />
841 <dataIntegrity encryptedHmacKey="" encryptedHmacValue="" algorithmIdentifier="SHA512" />
842 <keyEncryptors>
843 <keyEncryptor uri="http://schemas.microsoft.com/office/2006/keyEncryptor/password">
844 <p:encryptedKey spinCount="{spin_count}" saltSize="{salt_size}" saltValue="{password_salt_b64}" hashAlgorithm="SHA512" cipherAlgorithm="AES" keyBits="256" blockSize="16" encryptedKeyValue="{encrypted_key_b64}" encryptedVerifierHashInput="{encrypted_verifier_hash_input_b64}" encryptedVerifierHashValue="{encrypted_verifier_hash_value_b64}" />
845 </keyEncryptor>
846 </keyEncryptors>
847</encryption>"#,
848 salt_size = SALT_LEN,
849 spin_count = spin_count,
850 key_data_salt_b64 = key_data_salt_b64,
851 password_salt_b64 = password_salt_b64,
852 encrypted_key_b64 = encrypted_key_b64,
853 encrypted_verifier_hash_input_b64 = encrypted_verifier_hash_input_b64,
854 encrypted_verifier_hash_value_b64 = encrypted_verifier_hash_value_b64,
855 )
856}
857
858fn build_encrypted_zip(encryption_info_xml: &str, encrypted_package: &[u8]) -> Result<Vec<u8>> {
866 let mut buf = Vec::new();
867 {
869 let cursor = std::io::Cursor::new(&mut buf);
870 let mut zip = zip::ZipWriter::new(cursor);
871 let opts: zip::write::FileOptions<'_, ()> = zip::write::FileOptions::default()
872 .compression_method(zip::CompressionMethod::Deflated)
873 .unix_permissions(0o644);
874
875 let ct_xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
877<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
878 <Default Extension="bin" ContentType="application/vnd.ms-office.encryptedPackage" />
879</Types>"#;
880 zip.start_file("[Content_Types].xml", opts)
881 .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
882 zip.write_all(ct_xml.as_bytes())
883 .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
884
885 let rels_xml = r#"<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
887<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
888 <Relationship Id="rId1" Type="http://schemas.microsoft.com/office/2006/relationships/encryption" Target="EncryptionInfo" />
889</Relationships>"#;
890 zip.start_file("_rels/.rels", opts)
891 .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
892 zip.write_all(rels_xml.as_bytes())
893 .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
894
895 zip.start_file("EncryptionInfo", opts)
897 .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
898 zip.write_all(encryption_info_xml.as_bytes())
899 .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
900
901 zip.start_file("EncryptedPackage", opts)
903 .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
904 zip.write_all(encrypted_package)
905 .map_err(|e| Error::encryption(format!("zip write failed: {}", e)))?;
906
907 zip.finish()
908 .map_err(|e| Error::encryption(format!("zip finish failed: {}", e)))?;
909 }
910 Ok(buf)
911}
912
913use std::io::Write;