Skip to content

Commit 00a29f8

Browse files
authored
cipher: fix seeking implementation in the stream cipher wrapper (#2052)
The change forbids seeking to the last keystream block and its application. Additionally, unchecked methods added as an escape hatch.
1 parent bd85081 commit 00a29f8

File tree

4 files changed

+220
-118
lines changed

4 files changed

+220
-118
lines changed

cipher/src/stream.rs

Lines changed: 147 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Traits which define functionality of stream ciphers.
22
//!
3-
//! See [RustCrypto/stream-ciphers](https://github.com/RustCrypto/stream-ciphers)
3+
//! See the [RustCrypto/stream-ciphers](https://github.com/RustCrypto/stream-ciphers) repository
44
//! for ciphers implementation.
55
66
use crate::block::{BlockModeDecrypt, BlockModeEncrypt};
@@ -20,7 +20,7 @@ pub use errors::{OverflowError, StreamCipherError};
2020
#[cfg(feature = "stream-wrapper")]
2121
pub use wrapper::StreamCipherCoreWrapper;
2222

23-
/// Marker trait for block-level asynchronous stream ciphers
23+
/// Asynchronous stream cipher trait.
2424
pub trait AsyncStreamCipher: Sized {
2525
/// Encrypt data using `InOutBuf`.
2626
fn encrypt_inout(mut self, data: InOutBuf<'_, '_, u8>)
@@ -86,34 +86,127 @@ pub trait AsyncStreamCipher: Sized {
8686
}
8787
}
8888

89-
/// Synchronous stream cipher core trait.
89+
/// Stream cipher trait.
90+
///
91+
/// This trait applies only to synchronous stream ciphers, which generate a keystream and
92+
/// XOR data with it during both encryption and decryption. Therefore, instead of separate methods
93+
/// for encryption and decryption, this trait provides methods for keystream application.
94+
///
95+
/// # Notes on Keystream Repetition
96+
/// All stream ciphers have a finite state, so the generated keystream inevitably repeats itself,
97+
/// making the cipher vulnerable to chosen plaintext attack. Typically, the repetition period is
98+
/// astronomically large, rendering keystream repetition impossible to encounter in practice.
99+
///
100+
/// However, counter-based stream ciphers allow seeking across the keystream, and some also use
101+
/// small counters (e.g. 32 bits). This can result in triggering keystream repetition in practice.
102+
///
103+
/// To guard against this, methods either panic (e.g. [`StreamCipher::apply_keystream`]) or
104+
/// return [`StreamCipherError`] (e.g. [`StreamCipher::try_apply_keystream`]) when
105+
/// keystream repetition occurs. We also provide a number of "unchecked" methods
106+
/// (e.g. [`StreamCipher::unchecked_apply_keystream`]), but they should be used with extreme care.
107+
///
108+
/// For efficiency reasons, the check for keystream repetition is typically implemented by
109+
/// forbidding the generation of the last keystream block in both the keystream application methods
110+
/// and the seeking methods defined in the [`StreamCipherSeek`] trait.
90111
pub trait StreamCipher {
112+
/// Check that the cipher can generate a keystream with a length of `data_len` bytes.
113+
fn check_remaining(&self, data_len: usize) -> Result<(), StreamCipherError>;
114+
115+
/// Apply keystream to `inout` without checking for keystream repetition.
116+
///
117+
/// # WARNING
118+
/// This method should be used with extreme caution! Triggering keystream repetition can expose
119+
/// the stream cipher to chosen plaintext attacks.
120+
fn unchecked_apply_keystream_inout(&mut self, buf: InOutBuf<'_, '_, u8>);
121+
122+
/// Apply keystream to `buf` without checking for keystream repetition.
123+
///
124+
/// # WARNING
125+
/// This method should be used with extreme caution! Triggering keystream repetition can expose
126+
/// the stream cipher to chosen plaintext attacks.
127+
fn unchecked_write_keystream(&mut self, buf: &mut [u8]);
128+
129+
/// Apply keystream to data behind `buf` without checking for keystream repetition.
130+
///
131+
/// # WARNING
132+
/// This method should be used with extreme caution! Triggering keystream repetition can expose
133+
/// the stream cipher to chosen plaintext attacks.
134+
#[inline]
135+
fn unchecked_apply_keystream(&mut self, buf: &mut [u8]) {
136+
self.unchecked_apply_keystream_inout(buf.into())
137+
}
138+
139+
/// Apply keystream to data buffer-to-buffer without checking for keystream repetition.
140+
///
141+
/// It will XOR generated keystream with data from the `input` buffer
142+
/// and will write result to the `output` buffer.
143+
///
144+
/// Returns [`NotEqualError`] if the `input` and `output` buffers have different lengths.
145+
///
146+
/// # WARNING
147+
/// This method should be used with extreme caution! Triggering keystream repetition can expose
148+
/// the stream cipher to chosen plaintext attacks.
149+
#[inline]
150+
fn unchecked_apply_keystream_b2b(
151+
&mut self,
152+
input: &[u8],
153+
output: &mut [u8],
154+
) -> Result<(), NotEqualError> {
155+
let buf = InOutBuf::new(input, output)?;
156+
self.unchecked_apply_keystream_inout(buf);
157+
Ok(())
158+
}
159+
91160
/// Apply keystream to `inout` data.
92161
///
93-
/// If end of the keystream will be achieved with the given data length,
94-
/// method will return [`StreamCipherError`] without modifying provided `data`.
162+
/// If the end of the keystream is reached with the given buffer length,
163+
/// the method will return [`StreamCipherError`] without modifying `buf`.
95164
fn try_apply_keystream_inout(
96165
&mut self,
97166
buf: InOutBuf<'_, '_, u8>,
98-
) -> Result<(), StreamCipherError>;
167+
) -> Result<(), StreamCipherError> {
168+
self.check_remaining(buf.len())?;
169+
self.unchecked_apply_keystream_inout(buf);
170+
Ok(())
171+
}
99172

100173
/// Apply keystream to data behind `buf`.
101174
///
102-
/// If end of the keystream will be achieved with the given data length,
103-
/// method will return [`StreamCipherError`] without modifying provided `data`.
175+
/// If the end of the keystream is reached with the given buffer length,
176+
/// the method will return [`StreamCipherError`] without modifying `buf`.
104177
#[inline]
105178
fn try_apply_keystream(&mut self, buf: &mut [u8]) -> Result<(), StreamCipherError> {
106179
self.try_apply_keystream_inout(buf.into())
107180
}
108181

182+
/// Apply keystream to data buffer-to-buffer.
183+
///
184+
/// It will XOR generated keystream with data from the `input` buffer
185+
/// and will write result to the `output` buffer.
186+
///
187+
/// Returns [`StreamCipherError`] without modifying the buffers if the `input` and `output`
188+
/// buffers have different lengths, or if the end of the keystream is reached with
189+
/// the given data length.
190+
#[inline]
191+
fn try_apply_keystream_b2b(
192+
&mut self,
193+
input: &[u8],
194+
output: &mut [u8],
195+
) -> Result<(), StreamCipherError> {
196+
InOutBuf::new(input, output)
197+
.map_err(|_| StreamCipherError)
198+
.and_then(|buf| self.try_apply_keystream_inout(buf))
199+
}
200+
109201
/// Write keystream to `buf`.
110202
///
111-
/// If end of the keystream will be achieved with the given data length,
112-
/// method will return [`StreamCipherError`] without modifying provided `data`.
203+
/// If the end of the keystream is reached with the given buffer length,
204+
/// the method will return [`StreamCipherError`] without modifying `buf`.
113205
#[inline]
114206
fn try_write_keystream(&mut self, buf: &mut [u8]) -> Result<(), StreamCipherError> {
115-
buf.fill(0);
116-
self.try_apply_keystream(buf)
207+
self.check_remaining(buf.len())?;
208+
self.unchecked_write_keystream(buf);
209+
Ok(())
117210
}
118211

119212
/// Apply keystream to `inout` data.
@@ -122,8 +215,7 @@ pub trait StreamCipher {
122215
/// and will write result to `out` pointer.
123216
///
124217
/// # Panics
125-
/// If end of the keystream will be reached with the given data length,
126-
/// method will panic without modifying the provided `data`.
218+
/// If the end of the keystream is reached with the given buffer length.
127219
#[inline]
128220
fn apply_keystream_inout(&mut self, buf: InOutBuf<'_, '_, u8>) {
129221
self.try_apply_keystream_inout(buf).unwrap();
@@ -135,84 +227,84 @@ pub trait StreamCipher {
135227
/// to the same buffer.
136228
///
137229
/// # Panics
138-
/// If end of the keystream will be reached with the given data length,
139-
/// method will panic without modifying the provided `data`.
230+
/// If the end of the keystream is reached with the given buffer length.
140231
#[inline]
141232
fn apply_keystream(&mut self, buf: &mut [u8]) {
142233
self.try_apply_keystream(buf).unwrap();
143234
}
144235

145-
/// Write keystream to `buf`.
236+
/// Apply keystream to data buffer-to-buffer.
237+
///
238+
/// It will XOR generated keystream with data from the `input` buffer
239+
/// and will write result to the `output` buffer.
146240
///
147241
/// # Panics
148-
/// If end of the keystream will be reached with the given data length,
149-
/// method will panic without modifying the provided `data`.
242+
/// If the end of the keystream is reached with the given buffer length,
243+
/// of if the `input` and `output` buffers have different lengths.
150244
#[inline]
151-
fn write_keystream(&mut self, buf: &mut [u8]) {
152-
self.try_write_keystream(buf).unwrap();
245+
fn apply_keystream_b2b(&mut self, input: &[u8], output: &mut [u8]) {
246+
let Ok(buf) = InOutBuf::new(input, output) else {
247+
panic!("Lengths of input and output buffers are not equal to each other!");
248+
};
249+
self.apply_keystream_inout(buf)
153250
}
154251

155-
/// Apply keystream to data buffer-to-buffer.
156-
///
157-
/// It will XOR generated keystream with data from the `input` buffer
158-
/// and will write result to the `output` buffer.
252+
/// Write keystream to `buf`.
159253
///
160-
/// Returns [`StreamCipherError`] if provided `in_blocks` and `out_blocks`
161-
/// have different lengths or if end of the keystream will be reached with
162-
/// the given input data length.
254+
/// # Panics
255+
/// If the end of the keystream is reached with the given buffer length.
163256
#[inline]
164-
fn apply_keystream_b2b(
165-
&mut self,
166-
input: &[u8],
167-
output: &mut [u8],
168-
) -> Result<(), StreamCipherError> {
169-
InOutBuf::new(input, output)
170-
.map_err(|_| StreamCipherError)
171-
.and_then(|buf| self.try_apply_keystream_inout(buf))
257+
fn write_keystream(&mut self, buf: &mut [u8]) {
258+
self.try_write_keystream(buf).unwrap();
172259
}
173260
}
174261

175262
/// Trait for seekable stream ciphers.
176263
///
177-
/// Methods of this trait are generic over the [`SeekNum`] trait, which is
178-
/// implemented for primitive numeric types, i.e.: `i32`, `u32`, `u64`,
179-
/// `u128`, and `usize`.
264+
/// Methods of this trait are generic over the [`SeekNum`] trait,
265+
/// i.e. they can be used with `i32`, `u32`, `u64`, `u128`, and `usize`.
180266
pub trait StreamCipherSeek {
181-
/// Try to get current keystream position
267+
/// Try to get current keystream position in bytes.
182268
///
183-
/// Returns [`OverflowError`] if position can not be represented by type `T`
269+
/// Returns [`OverflowError`] if the position value can not be represented by type `T`.
184270
fn try_current_pos<T: SeekNum>(&self) -> Result<T, OverflowError>;
185271

186-
/// Try to seek to the given position
272+
/// Try to seek to the provided position in bytes.
187273
///
188-
/// Returns [`StreamCipherError`] if provided position value is bigger than
189-
/// keystream length.
274+
/// Returns [`StreamCipherError`] if the position value is bigger than keystream length.
190275
fn try_seek<T: SeekNum>(&mut self, pos: T) -> Result<(), StreamCipherError>;
191276

192-
/// Get current keystream position
277+
/// Get current keystream position in bytes.
193278
///
194279
/// # Panics
195-
/// If position can not be represented by type `T`
280+
/// If the position value can not be represented by type `T`.
196281
fn current_pos<T: SeekNum>(&self) -> T {
197282
self.try_current_pos().unwrap()
198283
}
199284

200-
/// Seek to the given position
285+
/// Seek to the provided keystream position in bytes.
201286
///
202287
/// # Panics
203-
/// If provided position value is bigger than keystream length
288+
/// If the position value is bigger than keystream length.
204289
fn seek<T: SeekNum>(&mut self, pos: T) {
205290
self.try_seek(pos).unwrap()
206291
}
207292
}
208293

209294
impl<C: StreamCipher> StreamCipher for &mut C {
210295
#[inline]
211-
fn try_apply_keystream_inout(
212-
&mut self,
213-
buf: InOutBuf<'_, '_, u8>,
214-
) -> Result<(), StreamCipherError> {
215-
C::try_apply_keystream_inout(self, buf)
296+
fn check_remaining(&self, data_len: usize) -> Result<(), StreamCipherError> {
297+
C::check_remaining(self, data_len)
298+
}
299+
300+
#[inline]
301+
fn unchecked_apply_keystream_inout(&mut self, buf: InOutBuf<'_, '_, u8>) {
302+
C::unchecked_apply_keystream_inout(self, buf)
303+
}
304+
305+
#[inline]
306+
fn unchecked_write_keystream(&mut self, buf: &mut [u8]) {
307+
C::unchecked_write_keystream(self, buf)
216308
}
217309
}
218310

@@ -249,8 +341,8 @@ macro_rules! impl_seek_num {
249341
}
250342

251343
fn into_block_byte<T: StreamCipherCounter>(self, block_size: u8) -> Result<(T, u8), OverflowError> {
252-
let bs: Self = block_size.into();
253-
let byte = (self % bs) as u8;
344+
let bs = Self::from(block_size);
345+
let byte = u8::try_from(self % bs).expect("bs fits into u8");
254346
let block = T::try_from(self / bs).map_err(|_| OverflowError)?;
255347
Ok((block, byte))
256348
}

cipher/src/stream/core_api.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,10 @@ pub trait StreamCipherClosure: BlockSizeUser {
3737

3838
/// Block-level synchronous stream ciphers.
3939
pub trait StreamCipherCore: BlockSizeUser + Sized {
40-
/// Return number of remaining blocks before cipher wraps around.
40+
/// Return number of remaining blocks before the cipher wraps around.
4141
///
4242
/// Returns `None` if number of remaining blocks can not be computed
43-
/// (e.g. in ciphers based on the sponge construction) or it's too big
44-
/// to fit into `usize`.
43+
/// (e.g. in the case of sponge-based stream ciphers) or it’s too big to fit into `usize`.
4544
fn remaining_blocks(&self) -> Option<usize>;
4645

4746
/// Process data using backend provided to the rank-2 closure.
@@ -91,23 +90,23 @@ pub trait StreamCipherCore: BlockSizeUser + Sized {
9190

9291
/// Try to apply keystream to data not divided into blocks.
9392
///
94-
/// Consumes cipher since it may consume final keystream block only
95-
/// partially.
93+
/// Consumes cipher since it may consume the final keystream block only partially.
9694
///
97-
/// Returns an error if number of remaining blocks is not sufficient
98-
/// for processing the input data.
95+
/// Returns an error if the number of remaining blocks is not sufficient
96+
/// for processing of the input data.
9997
#[inline]
10098
fn try_apply_keystream_partial(
10199
mut self,
102100
mut buf: InOutBuf<'_, '_, u8>,
103101
) -> Result<(), StreamCipherError> {
104-
if let Some(rem) = self.remaining_blocks() {
105-
let blocks = if buf.len() % Self::BlockSize::USIZE == 0 {
106-
buf.len() % Self::BlockSize::USIZE
107-
} else {
108-
buf.len() % Self::BlockSize::USIZE + 1
109-
};
110-
if blocks > rem {
102+
if let Some(rem_blocks) = self.remaining_blocks() {
103+
// Note that if `rem_blocks` is equal to zero, it means that
104+
// the next generated block will be the last in the keystream and
105+
// the cipher core will wrap to its initial state.
106+
// Since we consume `self`, it's fine to generate the last keystream block,
107+
// so we can use division instead of `div_ceil` to compute `req_blocks`.
108+
let req_blocks = buf.len() / Self::BlockSize::USIZE;
109+
if req_blocks > rem_blocks {
111110
return Err(StreamCipherError);
112111
}
113112
}
@@ -164,7 +163,10 @@ pub trait StreamCipherCounter:
164163
+ TryInto<u64>
165164
+ TryInto<u128>
166165
+ TryInto<usize>
166+
+ Copy
167167
{
168+
/// Returns `true` if `self` is equal to the max counter value.
169+
fn is_max(&self) -> bool;
168170
}
169171

170172
/// Block-level seeking trait for stream ciphers.
@@ -181,7 +183,13 @@ pub trait StreamCipherSeekCore: StreamCipherCore {
181183

182184
macro_rules! impl_counter {
183185
{$($t:ty )*} => {
184-
$( impl StreamCipherCounter for $t { } )*
186+
$(
187+
impl StreamCipherCounter for $t {
188+
fn is_max(&self) -> bool {
189+
*self == <$t>::MAX
190+
}
191+
}
192+
)*
185193
};
186194
}
187195

0 commit comments

Comments
 (0)