Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cipher/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ macro_rules! impl_seek_num {
}

fn into_block_byte<T: StreamCipherCounter>(self, block_size: u8) -> Result<(T, u8), OverflowError> {
let bs: Self = block_size.into();
let byte = (self % bs) as u8;
let bs = Self::from(block_size);
let byte = u8::try_from(self % bs).expect("bs fits into u8");
let block = T::try_from(self / bs).map_err(|_| OverflowError)?;
Ok((block, byte))
}
Expand Down
38 changes: 23 additions & 15 deletions cipher/src/stream/core_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,10 @@ pub trait StreamCipherClosure: BlockSizeUser {

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

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

/// Try to apply keystream to data not divided into blocks.
///
/// Consumes cipher since it may consume final keystream block only
/// partially.
/// Consumes cipher since it may consume the final keystream block only partially.
///
/// Returns an error if number of remaining blocks is not sufficient
/// for processing the input data.
/// Returns an error if the number of remaining blocks is not sufficient
/// for processing of the input data.
#[inline]
fn try_apply_keystream_partial(
mut self,
mut buf: InOutBuf<'_, '_, u8>,
) -> Result<(), StreamCipherError> {
if let Some(rem) = self.remaining_blocks() {
let blocks = if buf.len() % Self::BlockSize::USIZE == 0 {
buf.len() % Self::BlockSize::USIZE
} else {
buf.len() % Self::BlockSize::USIZE + 1
};
if blocks > rem {
if let Some(rem_blocks) = self.remaining_blocks() {
// Note that if `rem_blocks` is equal to zero, it means that
// the next generated block will be the last in the keystream and
// the cipher core will wrap to its initial state.
// Since we consume `self`, it's fine to generate the last keystream block,
// so we can use division instead of `div_ceil` to compute `req_blocks`.
let req_blocks = buf.len() / Self::BlockSize::USIZE;
if req_blocks > rem_blocks {
return Err(StreamCipherError);
}
}
Expand Down Expand Up @@ -164,7 +163,10 @@ pub trait StreamCipherCounter:
+ TryInto<u64>
+ TryInto<u128>
+ TryInto<usize>
+ Copy
{
/// Returns `true` if `self` is equal to the max counter value.
fn is_max(&self) -> bool;
}

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

macro_rules! impl_counter {
{$($t:ty )*} => {
$( impl StreamCipherCounter for $t { } )*
$(
impl StreamCipherCounter for $t {
fn is_max(&self) -> bool {
*self == <$t>::MAX
}
}
)*
};
}

Expand Down
27 changes: 13 additions & 14 deletions cipher/src/stream/wrapper.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::StreamCipherCounter;

use super::{
OverflowError, SeekNum, StreamCipher, StreamCipherCore, StreamCipherSeek, StreamCipherSeekCore,
errors::StreamCipherError,
Expand Down Expand Up @@ -51,20 +53,14 @@ impl<T: StreamCipherCore> StreamCipherCoreWrapper<T> {
}

fn check_remaining(&self, data_len: usize) -> Result<(), StreamCipherError> {
let rem_blocks = match self.core.remaining_blocks() {
Some(v) => v,
None => return Ok(()),
let Some(rem_blocks) = self.core.remaining_blocks() else {
return Ok(());
};

let buf_rem = self.buffer.remaining();
let data_len = match data_len.checked_sub(buf_rem) {
Some(0) | None => return Ok(()),
Some(res) => res,
let Some(data_len) = data_len.checked_sub(self.buffer.remaining()) else {
return Ok(());
};

let bs = T::BlockSize::USIZE;
let blocks = data_len.div_ceil(bs);
if blocks > rem_blocks {
let req_blocks = data_len.div_ceil(T::BlockSize::USIZE);
if req_blocks > rem_blocks {
Err(StreamCipherError)
} else {
Ok(())
Expand Down Expand Up @@ -129,8 +125,11 @@ impl<T: StreamCipherSeekCore> StreamCipherSeek for StreamCipherCoreWrapper<T> {
}

fn try_seek<SN: SeekNum>(&mut self, new_pos: SN) -> Result<(), StreamCipherError> {
let (block_pos, byte_pos) = new_pos.into_block_byte(T::BlockSize::U8)?;
// For correct implementations of `SeekNum` compiler should be able to
let (block_pos, byte_pos) = new_pos.into_block_byte::<T::Counter>(T::BlockSize::U8)?;
if byte_pos != 0 && block_pos.is_max() {
return Err(StreamCipherError);
}
// For correct implementations of `SeekNum` the compiler should be able to
// eliminate this assert
assert!(byte_pos < T::BlockSize::U8);

Expand Down
51 changes: 32 additions & 19 deletions cipher/tests/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ fn dummy_stream_cipher_core() {

#[cfg(feature = "stream-wrapper")]
mod wrapper {
use core::panic;

use super::*;
use cipher::{StreamCipher, StreamCipherCoreWrapper, StreamCipherSeek};

Expand All @@ -127,26 +129,37 @@ mod wrapper {
#[test]
fn dummy_stream_cipher_seek_limit() {
let mut cipher = DummyStreamCipher::new(&KEY.into(), &IV.into());
let mut buf = [0u8; 64];

let block_size = DummyStreamCipherCore::block_size();
let block_size_u128 = u128::try_from(block_size).unwrap();
let keystream_end = 1u128 << 68;
let last_block_pos = keystream_end - block_size_u128;

// Seeking to the last block or past it should return error
for offset in 0..block_size_u128 {
let res = cipher.try_seek(keystream_end - offset);
assert!(res.is_err());
let res = cipher.try_seek(keystream_end + offset);
assert!(res.is_err());
}

let pos = ((u64::MAX as u128) << 4) - 20;
cipher.try_seek(pos).unwrap();

let mut buf = [0u8; 30];
let res = cipher.try_apply_keystream(&mut buf);
assert!(res.is_err());
let cur_pos: u128 = cipher.current_pos();
assert_eq!(cur_pos, pos);

let res = cipher.try_apply_keystream(&mut buf[..19]);
assert!(res.is_ok());
let cur_pos: u128 = cipher.current_pos();
assert_eq!(cur_pos, pos + 19);

cipher.try_seek(pos).unwrap();

// TODO: fix as part of https://github.com/RustCrypto/traits/issues/1808
// let res = cipher.try_apply_keystream(&mut buf[..20]);
// assert!(res.is_err());
// Trying to apply the last keystream block should return error
for offset in block_size..buf.len() {
for len in 0..buf.len() {
let pos = keystream_end - u128::try_from(offset).unwrap();
let res = cipher.try_seek(pos);
assert!(res.is_ok());
let res = cipher.try_apply_keystream(&mut buf[..len]);
let expected_pos = pos + u128::try_from(len).unwrap();
if expected_pos > last_block_pos {
assert!(res.is_err());
} else {
assert!(res.is_ok());
assert_eq!(cipher.current_pos::<u128>(), expected_pos);
}
}
}
}

#[cfg(feature = "dev")]
Expand Down