Skip to content

Commit 81714e9

Browse files
author
Andreas Auernhammer
committed
make EncWriter implementation robust and add docs
This commit makes the EncWriter implementation robust against: - an unexpected panic of the inner writer - misbehaving calling code that invokes e.g. write multiple times even though an error has occurred. - code that explicitly completes the encryption process but then tries to use the EncWriter again. For panics the EncWriter has a boolean panic flag that is checked by the Drop implementation to not try to close the EncWriter. After a panic, the state (i.e. how many bytes have been written out) is undefined. When an error occurs the `EncWriter` sets the `errored` flag to true. Before performing another write/flush/close it will check whether an error has occurred in the past and fails if so. Finally, a `EncWriter` must be closed (by calling `close`) to complete the encryption process. If not done explicitly, it is closed during dropping. Now, the `close` method consumes the `EncWriter` to prevent calling code from using it later on. Additionally this commit adds documentation for the public API of the `EncWriter`.
1 parent 303b77a commit 81714e9

File tree

1 file changed

+174
-32
lines changed

1 file changed

+174
-32
lines changed

src/lib.rs

Lines changed: 174 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ pub const BUF_SIZE: usize = 1 << 14;
1616

1717
/// Wraps a writer and encrypts and authenticates everything written to it.
1818
///
19-
/// `EncrWriter` splits data into fixed-size fragments and encrypts and
19+
/// `EncWriter` splits data into fixed-size fragments and encrypts and
2020
/// authenticates each fragment separately. It appends any remaining data
2121
/// to its in-memory buffer until it has gathered a complete fragment.
2222
/// Therefore, using an `std::io::BufWriter` in addition usually does not
@@ -27,8 +27,9 @@ pub const BUF_SIZE: usize = 1 << 14;
2727
/// When the `EncWriter` is dropped, any buffered content will be encrypted
2828
/// as well as authenticated and written out. However, any errors that happen
2929
/// in the process of flushing the buffer when the `EncWriter` is dropped will
30-
/// be ignored. Therefore, code should call `flush` explicitly to ensure that
30+
/// be ignored. Therefore, code should call `close` explicitly to ensure that
3131
/// all encrypted data has been written out successfully.
32+
///
3233
/// # Examples
3334
///
3435
/// Let's encrypt a string and store the ciphertext in memory:
@@ -59,7 +60,7 @@ pub const BUF_SIZE: usize = 1 << 14;
5960
/// let mut writer = EncWriter::new(ciphertext, &key, nonce, aad);
6061
///
6162
/// writer.write_all(plaintext).unwrap();
62-
/// writer.flush().unwrap(); // Complete the encryption process explicitly.
63+
/// writer.close().unwrap(); // Complete the encryption process explicitly.
6364
/// ```
6465
pub struct EncWriter<A: Algorithm, W: Write> {
6566
inner: W,
@@ -68,14 +69,108 @@ pub struct EncWriter<A: Algorithm, W: Write> {
6869
buf_size: usize,
6970
nonce: Counter<A>,
7071
aad: [u8; 16 + 1], // TODO: replace with [u8; A::TAG_LEN + 1]
71-
flushed: bool,
72+
73+
// If an error occurs, we must fail any subsequent write of flush operation.
74+
// If set to true, this flag tells the write and flush implementation to fail
75+
// immediately.
76+
errored: bool,
77+
78+
// If `close` has been called explicitly, we must not try to close the
79+
// EncWriter again. This flag tells the Drop impl if it should skip the
80+
// close.
81+
closed: bool,
82+
83+
// If the inner writer panics in a call to write, we don't want to
84+
// write the buffered data a second time in BufWriter's destructor. This
85+
// flag tells the Drop impl if it should skip the close.
86+
panicked: bool,
7287
}
7388

7489
impl<A: Algorithm, W: Write> EncWriter<A, W> {
90+
/// Creates a new `EncWriter` with a default buffer size of 16 KiB.
91+
///
92+
/// Anything written to the `EncWriter` gets encrypted and authenticated
93+
/// using the provided `key` and `nonce`. The `aad` is only authenticated
94+
/// and neither encrypted nor written to the `inner` writer.
95+
///
96+
/// # Examples
97+
///
98+
/// ```
99+
/// use std::io::{Write, Read};
100+
/// use sio::{Key, Nonce, Aad, EncWriter};
101+
/// use sio::ring::AES_256_GCM;
102+
///
103+
/// // Load your secret keys from a secure location or derive
104+
/// // them using a secure (password-based) key-derivation-function, like Argon2id.
105+
/// // Obviously, don't use this all-zeros key for anything real.
106+
/// let key: Key<AES_256_GCM> = Key::new([0; Key::<AES_256_GCM>::SIZE]);
107+
///
108+
/// // Make sure you use an unique key-nonce combination!
109+
/// // Reusing a nonce value for the same secret key breaks
110+
/// // the security of the encryption algorithm.
111+
/// let nonce = Nonce::new([0; Nonce::<AES_256_GCM>::SIZE]);
112+
///
113+
/// // You must be able to re-generate this aad to decrypt
114+
/// // the ciphertext again. Usually, it's stored together with
115+
/// // the encrypted data.
116+
/// let aad = Aad::from("Some authenticated but not encrypted data".as_bytes());
117+
//////
118+
/// let mut ciphertext: Vec<u8> = Vec::default(); // Store the ciphertext in memory.
119+
/// let mut writer = EncWriter::new(ciphertext, &key, nonce, aad);
120+
///
121+
/// // Perform some write and flush operations
122+
/// // ...
123+
///
124+
/// writer.close().unwrap(); // Complete the encryption process explicitly.
125+
/// ```
75126
pub fn new(inner: W, key: &Key<A>, nonce: Nonce<A>, aad: Aad) -> Self {
76127
Self::with_buffer_size(inner, key, nonce, aad, BUF_SIZE).unwrap()
77128
}
78129

130+
/// Creates a new `EncWriter` with the specified buffer size as fragment
131+
/// size. The `buf_size` must not be `0` nor greater than `MAX_BUF_SIZE`.
132+
///
133+
/// Anything written to the `EncWriter` gets encrypted and authenticated
134+
/// using the provided `key` and `nonce`. The `aad` is only authenticated
135+
/// and neither encrypted nor written to the `inner` writer.
136+
///
137+
/// It's important to always use the same buffer/fragment size for
138+
/// encrypting and decrypting. Trying to decrypt data that has been
139+
/// encrypted with a different fragment size will fail. Therefore,
140+
/// the buffer size is usually fixed for one (kind of) application.
141+
///
142+
/// # Examples
143+
///
144+
/// Creating an `EncWriter` with a fragment size of 64 KiB.
145+
///
146+
/// ```
147+
/// use std::io::{Write, Read};
148+
/// use sio::{Key, Nonce, Aad, EncWriter};
149+
/// use sio::ring::AES_256_GCM;
150+
///
151+
/// // Load your secret keys from a secure location or derive
152+
/// // them using a secure (password-based) key-derivation-function, like Argon2id.
153+
/// // Obviously, don't use this all-zeros key for anything real.
154+
/// let key: Key<AES_256_GCM> = Key::new([0; Key::<AES_256_GCM>::SIZE]);
155+
///
156+
/// // Make sure you use an unique key-nonce combination!
157+
/// // Reusing a nonce value for the same secret key breaks
158+
/// // the security of the encryption algorithm.
159+
/// let nonce = Nonce::new([0; Nonce::<AES_256_GCM>::SIZE]);
160+
///
161+
/// // You must be able to re-generate this aad to decrypt
162+
/// // the ciphertext again. Usually, it's stored together with
163+
/// // the encrypted data.
164+
/// let aad = Aad::from("Some authenticated but not encrypted data".as_bytes());
165+
//////
166+
/// let mut ciphertext: Vec<u8> = Vec::default(); // Store the ciphertext in memory.
167+
/// let mut writer = EncWriter::with_buffer_size(ciphertext, &key, nonce, aad, 64 * 1024).unwrap();
168+
///
169+
/// // Perform some write and flush operations
170+
/// // ...
171+
///
172+
/// writer.close().unwrap(); // Complete the encryption process explicitly.
173+
/// ```
79174
pub fn with_buffer_size(
80175
inner: W,
81176
key: &Key<A>,
@@ -104,49 +199,84 @@ impl<A: Algorithm, W: Write> EncWriter<A, W> {
104199
buf_size: buf_size,
105200
nonce: nonce,
106201
aad: associated_data,
107-
flushed: false,
202+
errored: false,
203+
closed: false,
204+
panicked: false,
108205
})
109206
}
110207

208+
/// Completes the encryption process, writes the last ciphertext
209+
/// fragment to the inner writer and ensures that all buffered
210+
/// contents reach their destination.
211+
pub fn close(mut self) -> io::Result<()> {
212+
self.close_internal()
213+
}
214+
215+
fn close_internal(&mut self) -> io::Result<()> {
216+
if self.errored {
217+
return Err(io::Error::from(io::ErrorKind::Other));
218+
}
219+
self.closed = true;
220+
self.aad[0] = 0x80; // For the last fragment change the AAD
221+
222+
self.panicked = true;
223+
let r = self.write_buffer().and_then(|()| self.inner.flush());
224+
self.panicked = false;
225+
226+
self.errored = r.is_err();
227+
r
228+
}
229+
230+
// Encrypt and authenticate the buffer and write the ciphertext
231+
// to the inner writer.
111232
fn write_buffer(&mut self) -> io::Result<()> {
112233
self.buffer.resize(self.buffer.len() + A::TAG_LEN, 0);
113234
let ciphertext = self.algorithm.seal_in_place(
114235
&self.nonce.next()?,
115236
&self.aad,
116237
self.buffer.as_mut_slice(),
117238
)?;
118-
self.inner.write_all(ciphertext)?;
239+
240+
self.panicked = true;
241+
let r = self.inner.write_all(ciphertext);
242+
self.panicked = false;
243+
119244
self.buffer.clear();
120-
Ok(())
245+
r
121246
}
122247
}
123248

124249
impl<A: Algorithm, W: Write> Write for EncWriter<A, W> {
125250
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
126-
if self.flushed {
251+
if self.errored {
127252
return Err(io::Error::from(io::ErrorKind::Other));
128253
}
129-
let n = buf.len();
130254

131-
let remaining = self.buf_size - self.buffer.len();
132-
if buf.len() <= remaining {
133-
return self.buffer.write_all(buf).and(Ok(n));
134-
}
255+
let r: io::Result<usize> = {
256+
let n = buf.len();
135257

136-
self.buffer.extend_from_slice(&buf[..remaining]);
137-
self.write_buffer()?;
258+
let remaining = self.buf_size - self.buffer.len();
259+
if buf.len() <= remaining {
260+
return self.buffer.write_all(buf).and(Ok(n));
261+
}
138262

139-
let buf = &buf[remaining..];
140-
let chunks = buf.chunks(self.buf_size);
141-
chunks
142-
.clone()
143-
.take(chunks.len() - 1) // Since we take only n-1 elements...
144-
.try_for_each(|chunk| {
145-
self.buffer.extend_from_slice(chunk);
146-
self.write_buffer()
147-
})?;
148-
self.buffer.extend_from_slice(chunks.last().unwrap()); // ... there is always a last one.
149-
Ok(n)
263+
self.buffer.extend_from_slice(&buf[..remaining]);
264+
self.write_buffer()?;
265+
266+
let buf = &buf[remaining..];
267+
let chunks = buf.chunks(self.buf_size);
268+
chunks
269+
.clone()
270+
.take(chunks.len() - 1) // Since we take only n-1 elements...
271+
.try_for_each(|chunk| {
272+
self.buffer.extend_from_slice(chunk);
273+
self.write_buffer()
274+
})?;
275+
self.buffer.extend_from_slice(chunks.last().unwrap()); // ... there is always a last one.
276+
Ok(n)
277+
};
278+
self.errored = r.is_err();
279+
r
150280
}
151281

152282
#[inline]
@@ -155,12 +285,24 @@ impl<A: Algorithm, W: Write> Write for EncWriter<A, W> {
155285
}
156286

157287
fn flush(&mut self) -> io::Result<()> {
158-
if self.flushed {
288+
if self.errored {
159289
return Err(io::Error::from(io::ErrorKind::Other));
160290
}
161-
self.flushed = true;
162-
self.aad[0] = 0x80;
163-
self.write_buffer().and_then(|()| self.inner.flush())
291+
self.panicked = true;
292+
let r = self.inner.flush();
293+
self.panicked = false;
294+
295+
self.errored = r.is_err();
296+
r
297+
}
298+
}
299+
300+
impl<A: Algorithm, W: Write> Drop for EncWriter<A, W> {
301+
fn drop(&mut self) {
302+
if !self.panicked && !self.closed {
303+
// dtors should not panic, so we ignore a failed close
304+
let _r = self.close_internal();
305+
}
164306
}
165307
}
166308

@@ -266,7 +408,7 @@ mod tests {
266408

267409
use super::ring::AES_256_GCM;
268410
use super::*;
269-
use std::io::{Read, Write};
411+
use std::io::Read;
270412

271413
#[test]
272414
fn test_it() {
@@ -280,6 +422,6 @@ mod tests {
280422
let mut ew = EncWriter::with_buffer_size(dw, &key, dec_nonce, Aad::empty(), 100).unwrap();
281423

282424
std::io::copy(&mut std::io::repeat('a' as u8).take(2000), &mut ew).unwrap();
283-
ew.flush().unwrap();
425+
ew.close().unwrap();
284426
}
285427
}

0 commit comments

Comments
 (0)