git_simple_encrypt/crypt/
stream.rs1use std::io::{Read, Write};
2
3use chacha20poly1305_simd::XChaCha20Poly1305;
4use zeroize::Zeroizing;
5
6use crate::{
7 crypt::{
8 header::{CHUNK_SIZE, FILE_ID_LEN, FileHeader, HEADER_LEN, NONCE_LEN},
9 key::{derive_key, derive_nonce, split_keys},
10 },
11 error::{Error, Result},
12};
13
14fn encrypt_chunks(
17 reader: &mut dyn Read,
18 writer: &mut dyn Write,
19 cipher: &XChaCha20Poly1305,
20 key_mac: &[u8; 32],
21 file_id: &[u8; FILE_ID_LEN],
22 header_bytes: &[u8; HEADER_LEN],
23) -> Result<()> {
24 const TAG_LEN: usize = 16;
31 let mut buffer: Zeroizing<Vec<u8>> = Zeroizing::new(Vec::with_capacity(CHUNK_SIZE + TAG_LEN));
32 let mut aad = {
33 let mut aad = [0u8; HEADER_LEN + 9];
34 aad[..HEADER_LEN].copy_from_slice(header_bytes);
35 aad
36 };
37 let mut chunk_idx = 0u64;
38
39 loop {
40 buffer.resize(CHUNK_SIZE, 0);
43 let mut bytes_read = 0;
44 while bytes_read < CHUNK_SIZE {
45 let n = reader.read(&mut buffer[bytes_read..])?;
46 if n == 0 {
47 break;
48 }
49 bytes_read += n;
50 }
51
52 let is_last_chunk = bytes_read < CHUNK_SIZE;
53 aad[HEADER_LEN..HEADER_LEN + 8].copy_from_slice(&chunk_idx.to_le_bytes());
54 aad[HEADER_LEN + 8] = u8::from(is_last_chunk);
55
56 let nonce = derive_nonce(key_mac, file_id, &buffer[..bytes_read], chunk_idx);
59
60 buffer.truncate(bytes_read);
63 cipher
64 .encrypt_in_place(&nonce, &aad, &mut *buffer)
65 .map_err(|e| Error::EncryptFailed(e.to_string()))?;
66
67 writer.write_all(&nonce)?;
68 writer.write_all(&buffer)?;
69
70 chunk_idx += 1;
71
72 if is_last_chunk {
73 break;
74 }
75 }
76
77 Ok(())
78}
79
80fn decrypt_chunks(
85 reader: &mut dyn Read,
86 writer: &mut dyn Write,
87 cipher: &XChaCha20Poly1305,
88 header_bytes: &[u8; HEADER_LEN],
89) -> Result<()> {
90 const TAG_LEN: usize = 16;
95 let ct_len = CHUNK_SIZE + TAG_LEN;
96 let mut nonce_buf = [0u8; NONCE_LEN];
97 let mut ct_buffer: Zeroizing<Vec<u8>> = Zeroizing::new(Vec::with_capacity(ct_len));
98 let mut aad = {
99 let mut aad = [0u8; HEADER_LEN + 9];
100 aad[..HEADER_LEN].copy_from_slice(header_bytes);
101 aad
102 };
103 let mut last_chunk_was_final = false;
104 let mut chunk_idx = 0u64;
105
106 loop {
107 match reader.read_exact(&mut nonce_buf) {
108 Ok(()) => {},
109 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => break,
110 Err(e) => return Err(e.into()),
111 }
112
113 ct_buffer.resize(ct_len, 0);
114 let mut bytes_read = 0;
115 while bytes_read < ct_len {
116 let n = reader.read(&mut ct_buffer[bytes_read..])?;
117 if n == 0 {
118 break;
119 }
120 bytes_read += n;
121 }
122
123 if bytes_read < TAG_LEN {
124 return Err(Error::TruncatedChunk);
125 }
126
127 let is_last_chunk = bytes_read < ct_len;
128 ct_buffer.truncate(bytes_read);
129
130 aad[HEADER_LEN..HEADER_LEN + 8].copy_from_slice(&chunk_idx.to_le_bytes());
131 aad[HEADER_LEN + 8] = u8::from(is_last_chunk);
132
133 cipher
134 .decrypt_in_place(&nonce_buf, &aad, &mut *ct_buffer)
135 .map_err(|e| Error::DecryptFailed(e.to_string()))?;
136
137 writer.write_all(&ct_buffer)?;
138
139 chunk_idx += 1;
140
141 if is_last_chunk {
142 last_chunk_was_final = true;
143 break;
144 }
145 }
146
147 if !last_chunk_was_final {
148 return Err(Error::FileTruncated);
149 }
150
151 Ok(())
152}
153
154pub(super) fn decrypt_body(
156 reader: &mut dyn Read,
157 writer: &mut dyn Write,
158 cipher: &XChaCha20Poly1305,
159 header: &FileHeader,
160) -> Result<()> {
161 if header.is_compressed() {
162 let mut decoder = zstd::stream::write::Decoder::new(writer)?.auto_flush();
163 decrypt_chunks(reader, &mut decoder, cipher, header.as_bytes())?;
164 decoder.flush()?;
165 } else {
166 decrypt_chunks(reader, writer, cipher, header.as_bytes())?;
167 }
168 Ok(())
169}
170
171pub fn encrypt_into<R: Read, W: Write>(
173 reader: &mut R,
174 writer: &mut W,
175 derived_key: &[u8; 32],
176 salt: [u8; crate::crypt::header::SALT_LEN],
177 file_id: Option<[u8; FILE_ID_LEN]>,
178 zstd: Option<u8>,
179) -> Result<FileHeader> {
180 let file_id = file_id.unwrap_or_else(FileHeader::generate_file_id);
181 let header = FileHeader::new(zstd.is_some(), salt, file_id);
182 header.write_to(writer)?;
183
184 let (key_enc, key_mac) = split_keys(derived_key);
185 let cipher = XChaCha20Poly1305::new(*key_enc);
186
187 if let Some(level) = zstd {
188 let mut encoder = zstd::stream::read::Encoder::new(reader, i32::from(level))?;
189 encrypt_chunks(
190 &mut encoder,
191 writer,
192 &cipher,
193 &key_mac,
194 &file_id,
195 header.as_bytes(),
196 )?;
197 } else {
198 encrypt_chunks(
199 reader,
200 writer,
201 &cipher,
202 &key_mac,
203 &file_id,
204 header.as_bytes(),
205 )?;
206 }
207
208 Ok(header)
209}
210
211pub fn decrypt_into<R: Read, W: Write>(
213 reader: &mut R,
214 writer: &mut W,
215 master_key: &[u8],
216) -> Result<FileHeader> {
217 let header = FileHeader::read_from(reader)?;
218
219 let derived_key = derive_key(master_key, &header.salt)?;
220 let (key_enc, _) = split_keys(&derived_key);
221 let cipher = XChaCha20Poly1305::new(*key_enc);
222
223 decrypt_body(reader, writer, &cipher, &header)?;
224 Ok(header)
225}