ffsend_api/pipe/crypto/
ece.rs

1//! ECE AES-GCM 128 encrypter/decrypter pipe implementation for Firefox Send v3.
2
3use std::cmp::min;
4use std::io::{self, Read, Write};
5
6use byteorder::{BigEndian, ByteOrder};
7use bytes::BytesMut;
8#[cfg(feature = "crypto-openssl")]
9use openssl::symm;
10#[cfg(feature = "crypto-ring")]
11use ring::aead;
12
13use super::{Crypt, CryptMode};
14use crate::config::{self, TAG_LEN};
15use crate::crypto::{hkdf::hkdf, rand_bytes};
16use crate::pipe::{prelude::*, DEFAULT_BUF_SIZE};
17
18/// The default record size in bytes to use for encryption.
19///
20/// This value matches the default configured in the Firefox Send v3 source code.
21pub const RS: u32 = config::ECE_RECORD_SIZE;
22
23/// The crypto key length.
24const KEY_LEN: usize = 16;
25
26/// The crypto nonce length.
27const NONCE_LEN: usize = 12;
28
29/// The length in bytes of the header.
30pub const HEADER_LEN: u32 = 21;
31
32/// The length in bytes of the crypto salt.
33const SALT_LEN: usize = 16;
34
35/// The length in bytes of the record size, as encoded in the ECE header.
36const RS_LEN: usize = 4;
37
38/// The key info text.
39const KEY_INFO: &str = "Content-Encoding: aes128gcm\0";
40
41/// The nonce info text.
42const NONCE_INFO: &str = "Content-Encoding: nonce\0";
43
44/// Something that can encrypt or decrypt given data using ECE.
45pub struct EceCrypt {
46    /// The crypto mode, make this encrypt or decrypt data.
47    mode: CryptMode,
48
49    /// The crypto input key material.
50    ikm: Vec<u8>,
51
52    /// The crypto key if known.
53    key: Option<Vec<u8>>,
54
55    /// The crypto base nonce if known, chunk nonces are derived from.
56    nonce: Option<Vec<u8>>,
57
58    /// The crypto salt if known.
59    salt: Option<Vec<u8>>,
60
61    /// Sequence number of the current chunk.
62    ///
63    /// This number increases when transforming chunks.
64    /// The ciphertext header is excluded from this sequence.
65    seq: u32,
66
67    /// The number of bytes fed into this crypter.
68    ///
69    /// When encrypting, this corresponds to the number of plaintext bytes (matches `cur`).
70    /// When decrypting, this corresponds to the number of ciphertext bytes including the header.
71    ///
72    /// Used to determine when the last chunk is reached.
73    cur_in: usize,
74
75    /// The number of encrypted/decrypted plaintext bytes.
76    cur: usize,
77
78    /// The total size in bytes of the plaintext.
79    len: usize,
80
81    /// The record size used for crypto chunks.
82    ///
83    /// This value is dynamic and changes depending on the crypto mode and current progress.
84    rs: u32,
85}
86
87impl EceCrypt {
88    /// Construct a new ECE crypter pipe.
89    ///
90    /// It is highly recommended to use the [`encrypt()`](Self::encrypt) and
91    /// [`decrypt()`](Self::decrypt) methods instead for constructing a new crypter.
92    ///
93    /// The size in bytes of the plaintext data must be given as `len`.
94    /// The input key material must be given as `ikm`.
95    /// When encrypting, a `salt` must be specified.
96    pub fn new(mode: CryptMode, len: usize, ikm: Vec<u8>, salt: Option<Vec<u8>>) -> Self {
97        Self {
98            mode,
99            ikm,
100            key: None,
101            nonce: None,
102            salt,
103            seq: 0,
104            cur_in: 0,
105            cur: 0,
106            len,
107            rs: RS,
108        }
109    }
110
111    /// Create an ECE encryptor.
112    ///
113    /// The size in bytes of the plaintext data that is encrypted decrypt must be given as `len`.
114    /// The input key material must be given as `ikm`.
115    /// The `salt` is optional and will be randomly generated if `None`.
116    pub fn encrypt(len: usize, ikm: Vec<u8>, salt: Option<Vec<u8>>) -> Self {
117        // Construct the encrypter, generate random salt if not set
118        let mut crypt = Self::new(
119            CryptMode::Encrypt,
120            len,
121            ikm,
122            salt.or_else(|| Some(generate_salt())),
123        );
124
125        // Derive the key and nonce
126        crypt.derive_key_and_nonce();
127
128        crypt
129    }
130
131    /// Create an ECE decryptor.
132    ///
133    /// The size in bytes of the plaintext data that is decrypted decrypt must be given as `len`.
134    /// The input key material must be given as `ikm`.
135    pub fn decrypt(len: usize, ikm: Vec<u8>) -> Self {
136        Self::new(CryptMode::Decrypt, len, ikm, None)
137    }
138
139    /// Get the current desired size of a payload chunk.
140    ///
141    /// This value is dynamic and changes depending on the crypto mode, and the current stage.
142    /// Data passed to the crypter must match the chunk size.
143    #[inline(always)]
144    fn chunk_size(&self) -> u32 {
145        match self.mode {
146            // Record size with tag length and delimiter
147            CryptMode::Encrypt => self.rs - TAG_LEN as u32 - 1,
148
149            // Record size, header length for initial header chunk
150            CryptMode::Decrypt => {
151                if self.has_header() {
152                    self.rs
153                } else {
154                    HEADER_LEN
155                }
156            }
157        }
158    }
159
160    /// Encrypt the given `plaintext` data using this configured crypter.
161    ///
162    /// If a header hasn't been created yet, it is included in the output as well.
163    ///
164    /// This function returns `(read, out)` where `read` represents the number of read bytes from
165    /// `plaintext`, and `out` is a vector of now encrypted bytes.
166    ///
167    /// # Panics
168    ///
169    /// Panics if attempted to write more bytes than the length specified while configuring the
170    /// crypter.
171    fn pipe_encrypt(&mut self, input: Vec<u8>) -> (usize, Option<Vec<u8>>) {
172        // Encrypt the chunk, if the first chunk, the header must be created and prefixed
173        if !self.has_header() {
174            // Create header
175            let mut ciphertext = self.create_header();
176
177            // Encrypt chunk and append to header base ciphertext
178            let (read, chunk) = self.encrypt_chunk(input);
179            if let Some(chunk) = chunk {
180                ciphertext.extend_from_slice(&chunk)
181            }
182
183            // Increase chunk sequence number
184            self.increase_seq();
185
186            (read, Some(ciphertext))
187        } else {
188            // Encrypt chunk, increase chunk sequence number
189            let result = self.encrypt_chunk(input);
190            self.increase_seq();
191            result
192        }
193    }
194
195    /// Decrypt the given `ciphertext` using ECE crypto.
196    ///
197    /// If the header has not been read yet, it is parsed first to initialize the proper salt and
198    /// record size.
199    ///
200    /// This function returns `(read, plaintext)` where `read` represents the number of read bytes from
201    /// `ciphertext`, and `out` is a vector of the producted plaintext.
202    ///
203    /// # Panics
204    ///
205    /// Panics if attempted to write more bytes than the length specified while configuring the
206    /// crypter, or if decryption of a chunk failed.
207    /// size.
208    fn pipe_decrypt(&mut self, input: &[u8]) -> (usize, Option<Vec<u8>>) {
209        // Parse the header before decrypting anything
210        if !self.has_header() {
211            self.parse_header(input);
212            return (input.len(), None);
213        }
214
215        // Decrypt the chunk, increase chunk sequence number
216        let result = self.decrypt_chunk(input);
217        self.increase_seq();
218        result
219    }
220
221    /// Encrypt the given `plaintext` chunk data using this configured crypter.
222    ///
223    /// This function returns `(read, out)` where `read` represents the number of read bytes from
224    /// `plaintext`, and `out` is a vector of now encrypted bytes.
225    ///
226    /// # Panics
227    ///
228    /// Panics if attempted to write more bytes than the length specified while configuring the
229    /// crypter.
230    #[inline(always)]
231    fn encrypt_chunk(&mut self, mut plaintext: Vec<u8>) -> (usize, Option<Vec<u8>>) {
232        // // Don't allow encrypting more than specified, when tag is obtained
233        // if self.has_tag() && !plaintext.is_empty() {
234        //     panic!("could not write to AES-GCM encrypter, exceeding specified length");
235        // }
236
237        // Update transformed length
238        let read = plaintext.len();
239        self.cur += read;
240
241        // Generate the encryption nonce
242        let nonce = self.generate_nonce(self.seq);
243
244        // Pad the plaintext, encrypt the chunk, append tag
245        pad(&mut plaintext, self.rs as usize, self.is_last());
246
247        #[cfg(feature = "crypto-openssl")]
248        {
249            // Encrypt the chunk, append tag
250            let mut tag = vec![0u8; TAG_LEN];
251            let mut ciphertext = symm::encrypt_aead(
252                symm::Cipher::aes_128_gcm(),
253                self.key
254                    .as_ref()
255                    .expect("failed to encrypt ECE chunk, missing crypto key"),
256                Some(&nonce),
257                &[],
258                &plaintext,
259                &mut tag,
260            )
261            .expect("failed to encrypt ECE chunk");
262            ciphertext.extend_from_slice(&tag);
263
264            (read, Some(ciphertext))
265        }
266
267        #[cfg(feature = "crypto-ring")]
268        {
269            // Prepare sealing key
270            let nonce = aead::Nonce::try_assume_unique_for_key(&nonce)
271                .expect("failed to encrypt ECE chunk, invalid nonce");
272            let aad = aead::Aad::empty();
273            let key = self
274                .key
275                .as_ref()
276                .expect("failed to encrypt ECE chunk, missing crypto key");
277            let unbound_key = aead::UnboundKey::new(&aead::AES_128_GCM, key).unwrap();
278            let key = aead::LessSafeKey::new(unbound_key);
279
280            // Seal in place, return sealed
281            key.seal_in_place_append_tag(nonce, aad, &mut plaintext)
282                .expect("failed to encrypt ECE chunk");
283
284            (read, Some(plaintext.to_vec()))
285        }
286    }
287
288    /// Decrypt the given `ciphertext` chunk using ECE crypto.
289    ///
290    /// This function returns `(read, plaintext)` where `read` represents the number of read bytes
291    /// from `ciphertext`, and `out` is a vector of the producted plaintext.
292    ///
293    /// # Panics
294    ///
295    /// Panics if attempted to write more bytes than the length specified while configuring the
296    /// crypter, or if decryption of a chunk failed.
297    #[inline(always)]
298    fn decrypt_chunk(&mut self, ciphertext: &[u8]) -> (usize, Option<Vec<u8>>) {
299        // // Don't allow decrypting more than specified, when tag is obtained
300        // if self.has_tag() && !ciphertext.is_empty() {
301        //     panic!("could not write to AES-GCM decrypter, exceeding specified lenght");
302        // }
303
304        // Generate encryption nonce
305        let nonce = self.generate_nonce(self.seq);
306
307        #[cfg(feature = "crypto-openssl")]
308        {
309            // Split payload and tag
310            let (payload, tag) = ciphertext.split_at(ciphertext.len() - TAG_LEN);
311
312            // Decrypt the chunk, and unpad decrypted payload
313            let mut plaintext = symm::decrypt_aead(
314                symm::Cipher::aes_128_gcm(),
315                self.key
316                    .as_ref()
317                    .expect("failed to decrypt ECE chunk, missing crypto key"),
318                Some(&nonce),
319                &[],
320                payload,
321                tag,
322            )
323            .expect("failed to decrypt ECE chunk");
324            unpad(&mut plaintext, self.is_last());
325
326            // Update transformed length
327            self.cur += plaintext.len();
328
329            (ciphertext.len(), Some(plaintext))
330        }
331
332        #[cfg(feature = "crypto-ring")]
333        {
334            // Clone ciphertext so we can modify in-place
335            let mut ciphertext = ciphertext.to_vec();
336
337            // Prepare opening key
338            let nonce = aead::Nonce::try_assume_unique_for_key(&nonce)
339                .expect("failed to decrypt ECE chunk, invalid nonce");
340            let aad = aead::Aad::empty();
341            let key = self
342                .key
343                .as_ref()
344                .expect("failed to decrypt ECE chunk, missing crypto key");
345            let unbound_key = aead::UnboundKey::new(&aead::AES_128_GCM, key).unwrap();
346            let key = aead::LessSafeKey::new(unbound_key);
347
348            // Decrypt the chunk, and unpad decrypted payload
349            let mut plaintext = key
350                .open_in_place(nonce, aad, &mut ciphertext)
351                .expect("failed to decrypt ECE chunk")
352                .to_vec();
353            unpad(&mut plaintext, self.is_last());
354
355            // Update transformed length
356            self.cur += plaintext.len();
357
358            (ciphertext.len(), Some(plaintext))
359        }
360    }
361
362    /// Create the ECE crypto header.
363    ///
364    /// This header includes the salt and record size as configured in this crypter instance.
365    /// The header bytes are returned.
366    ///
367    /// # Panics
368    ///
369    /// Panics if the salt is not set.
370    #[inline(always)]
371    fn create_header(&self) -> Vec<u8> {
372        // Allocate the header
373        let mut header = Vec::with_capacity(HEADER_LEN as usize);
374
375        // Add the salt
376        let salt = self
377            .salt
378            .as_ref()
379            .expect("failed to create ECE header, no crypto salt specified");
380        assert_eq!(salt.len(), SALT_LEN);
381        header.extend_from_slice(salt);
382
383        // Add the record size
384        let mut rs = [0u8; 4];
385        BigEndian::write_u32(&mut rs, self.rs);
386        header.extend_from_slice(&rs);
387
388        // Add length of unused key ID length
389        header.push(0);
390
391        header
392    }
393
394    /// Parse the given header bytes as ECE crypto header.
395    ///
396    /// This function attemts to parse the given header bytes.
397    /// A salt and record size is parsed, a key and nonce are derived from them.
398    /// The values are set in the inner `EceCrypt` instance and are automatically used for further
399    /// decryption.
400    ///
401    /// # Panics
402    ///
403    /// Panics if the given header bytes have an invalid size, or if the given header is not fully
404    /// parsed.
405    #[inline(always)]
406    fn parse_header(&mut self, header: &[u8]) {
407        // Assert the header size
408        assert_eq!(
409            header.len() as u32,
410            HEADER_LEN,
411            "failed to decrypt, ECE header is not 21 bytes long",
412        );
413
414        // Parse the salt, record size and length
415        let (salt, header) = header.split_at(SALT_LEN);
416        let (rs, header) = header.split_at(RS_LEN);
417        self.salt = Some(salt.to_vec());
418        self.rs = BigEndian::read_u32(rs);
419
420        // Extracted in Send v3 code, but doesn't seem to be used
421        let (key_id_data, header) = header.split_at(1);
422        let key_id_len = key_id_data[0] as usize;
423        let _length = key_id_len + KEY_LEN + 5;
424
425        // Derive the key and nonce based on extracted salt
426        self.derive_key_and_nonce();
427
428        // Assert all header bytes have been consumed
429        // If this fails, update `len_encrypted` as well
430        assert!(
431            header.is_empty(),
432            "failed to decrypt, not all ECE header bytes are used",
433        );
434    }
435
436    /// Derive the crypto key and base nonce.
437    ///
438    /// These are derived based on `self.salt` and `self.ikm`, and must be configured.
439    ///
440    /// # Panics
441    ///
442    /// panics if either `self.salt` or `self.ikm` is not configured.
443    #[inline(always)]
444    fn derive_key_and_nonce(&mut self) {
445        self.key = Some(hkdf(
446            self.salt.as_ref().map(|s| s.as_slice()),
447            KEY_LEN,
448            &self.ikm,
449            Some(KEY_INFO.as_bytes()),
450        ));
451        self.nonce = Some(hkdf(
452            self.salt.as_ref().map(|s| s.as_slice()),
453            NONCE_LEN,
454            &self.ikm,
455            Some(NONCE_INFO.as_bytes()),
456        ));
457    }
458
459    /// Generate crypto nonce for sequence with index `seq`.
460    ///
461    /// Each payload chunk uses a different nonce.
462    /// This method generates the nonce to use.
463    #[inline(always)]
464    fn generate_nonce(&self, seq: u32) -> Vec<u8> {
465        // Get the base nonce which we need to modify
466        let mut nonce = self
467            .nonce
468            .clone()
469            .expect("failed to generate nonce, no base nonce available");
470
471        // TODO: slice `nonce` only once, use that for mutating
472
473        let nonce_len = nonce.len();
474        let m = BigEndian::read_u32(&nonce[nonce_len - 4..nonce_len]);
475        let xor = m ^ seq;
476
477        BigEndian::write_u32(&mut nonce[nonce_len - 4..nonce_len], xor);
478
479        nonce
480    }
481
482    /// Check whehter the header is read.
483    ///
484    /// This checks whether the header has been read from the input while decrypting.
485    /// The header contains important information for the rest of the decryption process and must
486    /// be obtained and parsed first.
487    ///
488    /// TODO: better docs
489    #[inline(always)]
490    fn has_header(&self) -> bool {
491        match self.mode {
492            CryptMode::Encrypt => self.cur > 0,
493            CryptMode::Decrypt => self.salt.is_some(),
494        }
495    }
496
497    /// Check if working with the last crypto chunk.
498    ///
499    /// This checks whether all data for the last chunk, determined by the plaintext length in
500    /// bytes, has entered this crypto pipe.
501    #[inline(always)]
502    fn is_last(&self) -> bool {
503        self.is_last_with(0)
504    }
505
506    /// Check if working with the last crypto chunk including given `extra` bytes.
507    ///
508    /// This checks whether all data for the last chunk including `extra`, determined by the
509    /// plaintext length in bytes, has entered this crypto pipe.
510    #[inline(always)]
511    fn is_last_with(&self, extra: usize) -> bool {
512        self.cur_in + extra >= self.len_in()
513    }
514
515    /// Increase the chunk sequence number.
516    ///
517    /// Called automatically by the `pipe_encrypt` and `pipe_decrypt` methods when a chunk is read.
518    /// This should never be invoked manually.
519    ///
520    /// # Panics
521    ///
522    /// Panics if the sequence number exceeds the maximum.
523    #[inline(always)]
524    fn increase_seq(&mut self) {
525        self.seq = self
526            .seq
527            .checked_add(1)
528            .expect("failed to crypt ECE payload, record sequence number exceeds limit");
529    }
530}
531
532impl Pipe for EceCrypt {
533    type Reader = EceReader;
534    type Writer = EceWriter;
535
536    fn pipe(&mut self, input: &[u8]) -> (usize, Option<Vec<u8>>) {
537        // Increase input byte counter
538        self.cur_in += input.len();
539
540        // Use mode specific pipe function
541        match self.mode {
542            CryptMode::Encrypt => self.pipe_encrypt(input.to_vec()),
543            CryptMode::Decrypt => self.pipe_decrypt(input),
544        }
545    }
546}
547
548impl Crypt for EceCrypt {}
549
550impl PipeLen for EceCrypt {
551    fn len_in(&self) -> usize {
552        match self.mode {
553            CryptMode::Encrypt => self.len,
554            CryptMode::Decrypt => len_encrypted(self.len, self.rs as usize),
555        }
556    }
557
558    fn len_out(&self) -> usize {
559        match self.mode {
560            CryptMode::Encrypt => len_encrypted(self.len, self.rs as usize),
561            CryptMode::Decrypt => self.len,
562        }
563    }
564}
565
566pub struct EceReader {
567    crypt: EceCrypt,
568    inner: Box<dyn Read>,
569    buf_in: BytesMut,
570    buf_out: BytesMut,
571}
572
573pub struct EceWriter {
574    crypt: EceCrypt,
575    inner: Box<dyn Write>,
576    buf: BytesMut,
577}
578
579impl PipeRead<EceCrypt> for EceReader {
580    fn new(crypt: EceCrypt, inner: Box<dyn Read>) -> Self {
581        let chunk_size = crypt.chunk_size() as usize;
582
583        Self {
584            crypt,
585            inner,
586            buf_in: BytesMut::with_capacity(chunk_size),
587            buf_out: BytesMut::with_capacity(DEFAULT_BUF_SIZE),
588        }
589    }
590}
591
592impl PipeWrite<EceCrypt> for EceWriter {
593    fn new(crypt: EceCrypt, inner: Box<dyn Write>) -> Self {
594        let chunk_size = crypt.chunk_size() as usize;
595
596        Self {
597            crypt,
598            inner,
599            buf: BytesMut::with_capacity(chunk_size),
600        }
601    }
602}
603
604impl Read for EceReader {
605    fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
606        // Number of bytes written to given buffer
607        let mut total = 0;
608
609        // Write any output buffer bytes first
610        if !self.buf_out.is_empty() {
611            // Copy as much as possible from inner to output buffer, increase total
612            let write = min(self.buf_out.len(), buf.len());
613            total += write;
614            buf[..write].copy_from_slice(&self.buf_out.split_to(write));
615
616            // Return if given buffer is full, or slice to unwritten buffer
617            if total >= buf.len() {
618                return Ok(total);
619            }
620            buf = &mut buf[write..];
621        }
622
623        // Attempt to fill input buffer if has capacity upto the chunk size
624        let capacity = self.crypt.chunk_size() as usize - self.buf_in.len();
625        if capacity > 0 {
626            // Read from inner to input buffer
627            let mut inner_buf = vec![0u8; capacity];
628            let read = self.inner.read(&mut inner_buf)?;
629            self.buf_in.extend_from_slice(&inner_buf[..read]);
630
631            // Break if:
632            // - no new data was read
633            // - buffer doesn't have enough data to crypt, while there's data left to read
634            if read == 0 || (read != capacity && !self.crypt.is_last_with(read)) {
635                return Ok(total);
636            }
637        }
638
639        // Move input buffer into the crypter
640        let (read, out) = self.crypt.crypt(&self.buf_in);
641        let _ = self.buf_in.split_to(read);
642
643        // Write any crypter output to given buffer and remaining to output buffer
644        if let Some(out) = out {
645            // Copy as much data as possible from crypter output to read buffer
646            let write = min(out.len(), buf.len());
647            total += write;
648            buf[..write].copy_from_slice(&out[..write]);
649
650            // Copy remaining bytes into output buffer
651            if write < out.len() {
652                self.buf_out.extend_from_slice(&out[write..]);
653            }
654
655            // Return if given buffer is full, or slice to unwritten buffer
656            if write >= buf.len() {
657                return Ok(total);
658            }
659            buf = &mut buf[write..];
660        }
661
662        // Try again with remaining given buffer
663        self.read(buf).map(|n| n + total)
664    }
665}
666
667impl Write for EceWriter {
668    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
669        // Get the chunk size to use
670        let chunk_size = self.crypt.chunk_size() as usize;
671
672        // Attempt to fill input buffer if has capacity upto the chunk size
673        let capacity = chunk_size - self.buf.len();
674        let read = min(capacity, buf.len());
675        if capacity > 0 {
676            self.buf.extend_from_slice(&buf[..read]);
677        }
678
679        // Transform input data through crypter if chunk data is available
680        if self.buf.len() >= chunk_size {
681            let (read, data) = self.crypt.crypt(&self.buf.split_off(0));
682            assert_eq!(read, chunk_size, "ECE crypto did not transform full chunk");
683            if let Some(data) = data {
684                self.inner.write_all(&data)?;
685            }
686        }
687
688        // If all expected data is provided, make sure to finish the last partial chunk
689        if self.crypt.is_last_with(self.buf.len()) {
690            if let (_, Some(data)) = self.crypt.crypt(&self.buf.split_off(0)) {
691                self.inner.write_all(&data)?;
692            }
693        }
694
695        Ok(read)
696    }
697
698    fn flush(&mut self) -> io::Result<()> {
699        self.inner.flush()
700    }
701}
702
703impl PipeLen for EceReader {
704    fn len_in(&self) -> usize {
705        self.crypt.len_in()
706    }
707
708    fn len_out(&self) -> usize {
709        self.crypt.len_out()
710    }
711}
712
713impl ReadLen for EceReader {}
714
715impl PipeLen for EceWriter {
716    fn len_in(&self) -> usize {
717        self.crypt.len_in()
718    }
719
720    fn len_out(&self) -> usize {
721        self.crypt.len_out()
722    }
723}
724
725impl WriteLen for EceWriter {}
726
727unsafe impl Send for EceReader {}
728unsafe impl Send for EceWriter {}
729
730/// Pad a plaintext chunk for ECE encryption.
731///
732/// Padding is a required step for ECE encryption.
733/// This modifies the block in-place.
734///
735/// The padding length in number of bytes must be passed to `pad_len`.
736/// If this is the last chunk that will be encrypted, `last` must be `true`.
737///
738/// This internally suffixes a padding delimiter to the block, and the padding bytes itself.
739fn pad(block: &mut Vec<u8>, rs: usize, last: bool) {
740    // Assert the data fits the records
741    assert!(
742        block.len() + TAG_LEN < rs,
743        "failed to pad ECE ciphertext, data too large for record size"
744    );
745
746    // Pad chunks with 1 delimiter and zeros, pad last chunk with single 2 delimiter
747    if !last {
748        let mut pad = vec![0u8; rs - block.len() - TAG_LEN];
749        pad[0] = 1;
750        block.extend(pad);
751    } else {
752        block.push(2);
753    }
754}
755
756/// Unpad an decrypted ECE ciphertext chunk.
757///
758/// Unpadding is a required step to transform ECE decrypted data into plain text.
759/// This modifies the block in-place.
760///
761/// If this is the last chunk that will be decrypted, `last` must be `false`.
762fn unpad(block: &mut Vec<u8>, last: bool) {
763    let pos = match block.iter().rposition(|&b| b != 0) {
764        Some(pos) => pos,
765        None => panic!("ciphertext is zero"),
766    };
767    let expected_delim = if last { 2 } else { 1 };
768    assert_eq!(block[pos], expected_delim, "ECE decrypt unpadding failure");
769
770    // Truncate the padded bytes
771    block.truncate(pos);
772}
773
774/// Generate a random salt for encryption.
775pub fn generate_salt() -> Vec<u8> {
776    let mut salt = vec![0u8; SALT_LEN];
777    rand_bytes(&mut salt).expect("failed to generate encryption salt");
778    salt
779}
780
781/// Calcualte length of ECE encrypted data.
782///
783/// This function calculates the length in bytes of the ECE ciphertext.
784/// The record size and length in bytes of the plaintext must be given as `rs` and `len`.
785pub fn len_encrypted(len: usize, rs: usize) -> usize {
786    let chunk_meta = TAG_LEN + 1;
787    let chunk_data = rs - chunk_meta;
788    let header = HEADER_LEN as usize;
789    let chunks = (len as f64 / chunk_data as f64).ceil() as usize;
790
791    header + len + chunk_meta * chunks
792}