diff --git a/.github/workflows/chacha20.yml b/.github/workflows/chacha20.yml index 5015cfb9..bbe35eb8 100644 --- a/.github/workflows/chacha20.yml +++ b/.github/workflows/chacha20.yml @@ -37,7 +37,8 @@ jobs: with: toolchain: ${{ matrix.rust }} targets: ${{ matrix.target }} - - run: cargo build --target ${{ matrix.target }} + - run: cargo build --target ${{ matrix.target }} --features xchacha,legacy + - run: bash -c '[[ $(cargo test --features xchacha,legacy overflow -- --include-ignored --list --format=terse 2>/dev/null | wc -l) -eq 15 ]]' - run: cargo build --target ${{ matrix.target }} --features zeroize minimal-versions: @@ -73,7 +74,7 @@ jobs: targets: ${{ matrix.target }} - run: ${{ matrix.deps }} - run: cargo check --target ${{ matrix.target }} --all-features - - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features xchacha,legacy - run: cargo test --target ${{ matrix.target }} --features zeroize # Tests for the AVX2 backend @@ -106,7 +107,7 @@ jobs: targets: ${{ matrix.target }} - run: ${{ matrix.deps }} - run: cargo check --target ${{ matrix.target }} --all-features - - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features xchacha,legacy - run: cargo test --target ${{ matrix.target }} --features zeroize # Tests for the SSE2 backend @@ -139,7 +140,7 @@ jobs: targets: ${{ matrix.target }} - run: ${{ matrix.deps }} - run: cargo check --target ${{ matrix.target }} --all-features - - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features xchacha,legacy - run: cargo test --target ${{ matrix.target }} --features zeroize # Tests for the portable software backend @@ -172,7 +173,7 @@ jobs: targets: ${{ matrix.target }} - run: ${{ matrix.deps }} - run: cargo check --target ${{ matrix.target }} --all-features - - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features xchacha,legacy - run: cargo test --target ${{ matrix.target }} --features zeroize # Cross-compiled tests @@ -206,4 +207,4 @@ jobs: toolchain: ${{ matrix.rust }} targets: ${{ matrix.target }} - uses: RustCrypto/actions/cross-install@master - - run: RUSTFLAGS="${{ matrix.rustflags }}" cross test --package chacha20 --target ${{ matrix.target }} + - run: RUSTFLAGS="${{ matrix.rustflags }}" cross test --package chacha20 --target ${{ matrix.target }} --features xchacha,legacy diff --git a/chacha20/src/backends/soft.rs b/chacha20/src/backends/soft.rs index e0614138..627f5788 100644 --- a/chacha20/src/backends/soft.rs +++ b/chacha20/src/backends/soft.rs @@ -28,7 +28,15 @@ impl StreamCipherBackend for Backend<'_, R, V> { #[inline(always)] fn gen_ks_block(&mut self, block: &mut Block) { let res = run_rounds::(&self.0.state); - self.0.state[12] = self.0.state[12].wrapping_add(1); + self.0.counter = self.0.counter.saturating_add(1); + if let Some(ctr) = self.0.state[12].checked_add(1) { + self.0.state[12] = ctr; + } else if V::COUNTER_SIZE > 1 { + if let Some(ctr) = self.0.state[13].checked_add(1) { + self.0.state[12] = 0; + self.0.state[13] = ctr; + } + } for (chunk, val) in block.chunks_exact_mut(4).zip(res.iter()) { chunk.copy_from_slice(&val.to_le_bytes()); @@ -42,7 +50,11 @@ impl Backend<'_, R, V> { pub(crate) fn gen_ks_blocks(&mut self, buffer: &mut [u32; 64]) { for i in 0..4 { let res = run_rounds::(&self.0.state); + self.0.counter = self.0.counter.wrapping_add(1) & V::MAX_USABLE_COUNTER; self.0.state[12] = self.0.state[12].wrapping_add(1); + if self.0.state[12] == 0 && V::COUNTER_SIZE > 1 { + self.0.state[13] = self.0.state[13].wrapping_add(1); + } for (word, val) in buffer[i << 4..(i + 1) << 4].iter_mut().zip(res.iter()) { *word = val.to_le(); diff --git a/chacha20/src/backends/sse2.rs b/chacha20/src/backends/sse2.rs index 66ddc00f..c77c5269 100644 --- a/chacha20/src/backends/sse2.rs +++ b/chacha20/src/backends/sse2.rs @@ -1,8 +1,8 @@ #![allow(unsafe_op_in_unsafe_fn)] -use crate::Rounds; +use crate::{Rounds,Variant}; #[cfg(feature = "rng")] -use crate::{ChaChaCore, Variant}; +use crate::{ChaChaCore}; #[cfg(feature = "cipher")] use crate::{chacha::Block, STATE_WORDS}; @@ -23,49 +23,77 @@ const PAR_BLOCKS: usize = 4; #[inline] #[target_feature(enable = "sse2")] #[cfg(feature = "cipher")] -pub(crate) unsafe fn inner(state: &mut [u32; STATE_WORDS], f: F) +pub(crate) unsafe fn inner(counter: &mut u64, state: &mut [u32; STATE_WORDS], f: F) where R: Rounds, + V: Variant, F: StreamCipherClosure, { let state_ptr = state.as_ptr() as *const __m128i; - let mut backend = Backend:: { + let mut backend = Backend:: { v: [ _mm_loadu_si128(state_ptr.add(0)), _mm_loadu_si128(state_ptr.add(1)), _mm_loadu_si128(state_ptr.add(2)), _mm_loadu_si128(state_ptr.add(3)), ], + counter: *counter, _pd: PhantomData, }; f.call(&mut backend); - state[12] = _mm_cvtsi128_si32(backend.v[3]) as u32; + *counter = backend.counter; + + if V::COUNTER_SIZE == 1 { + state[12] = _mm_cvtsi128_si32(backend.v[3]) as u32; + } else { + let ctr = _mm_cvtsi128_si64(backend.v[3]) as u64; + + state[12] = (ctr&(u32::MAX as u64)) as u32; + state[13] = (ctr>>32) as u32; + } + } -struct Backend { +struct Backend { v: [__m128i; 4], - _pd: PhantomData, + counter: u64, + _pd: PhantomData<(R,V)>, } #[cfg(feature = "cipher")] -impl BlockSizeUser for Backend { +impl BlockSizeUser for Backend { type BlockSize = U64; } #[cfg(feature = "cipher")] -impl ParBlocksSizeUser for Backend { +impl ParBlocksSizeUser for Backend { type ParBlocksSize = U4; } #[cfg(feature = "cipher")] -impl StreamCipherBackend for Backend { +impl StreamCipherBackend for Backend { #[inline(always)] fn gen_ks_block(&mut self, block: &mut Block) { + self.counter = self.counter.saturating_add(1); unsafe { - let res = rounds::(&self.v); - self.v[3] = _mm_add_epi32(self.v[3], _mm_set_epi32(0, 0, 0, 1)); + let res = rounds::(&self.v); + if V::COUNTER_SIZE == 1 { + let mask = _mm_add_epi32( + _mm_and_si128( + _mm_cmpeq_epi32(self.v[3], _mm_set_epi32(0,0,0,-1)), + _mm_set_epi32(0,0,0,-1)), + _mm_set_epi32(0,0,0,1)); + self.v[3] = _mm_add_epi32(self.v[3], _mm_and_si128(mask, _mm_set_epi32(0, 0, 0, 1))); + } else { + let mask = _mm_add_epi64( + _mm_and_si128( + _mm_cmpeq_epi64(self.v[3], _mm_set_epi64x(0,-1)), + _mm_set_epi64x(0,-1)), + _mm_set_epi64x(0,1)); + self.v[3] = _mm_add_epi64(self.v[3], _mm_and_si128(mask, _mm_set_epi64x(0, 1))); + } let block_ptr = block.as_mut_ptr() as *mut __m128i; for i in 0..4 { @@ -75,9 +103,34 @@ impl StreamCipherBackend for Backend { } #[inline(always)] fn gen_par_ks_blocks(&mut self, blocks: &mut cipher::ParBlocks) { + if V::COUNTER_SIZE == 1 { + self.counter = core::cmp::min(V::MAX_USABLE_COUNTER+1, + self.counter.saturating_add(PAR_BLOCKS as u64)); + } else { + self.counter = self.counter.saturating_add(PAR_BLOCKS as u64); + } unsafe { - let res = rounds::(&self.v); - self.v[3] = _mm_add_epi32(self.v[3], _mm_set_epi32(0, 0, 0, PAR_BLOCKS as i32)); + let res = rounds::(&self.v); + if V::COUNTER_SIZE == 1 { + + let new_v3 = _mm_add_epi32(self.v[3], _mm_set_epi32(0, 0, 0, PAR_BLOCKS as i32)); + let shifted = _mm_add_epi32(self.v[3], _mm_set_epi32(0,0,0,i32::MIN)); + let new_shifted = _mm_add_epi32(new_v3, _mm_set_epi32(0,0,0,i32::MIN)); + let mask = _mm_cmpgt_epi32(shifted,new_shifted); + let max_val = _mm_and_si128(mask,_mm_set_epi32(0,0,0,-1)); + let new_val = _mm_andnot_si128(mask,new_v3); + self.v[3] = _mm_or_si128(max_val,new_val); + + } else { + let new_v3 = _mm_add_epi64(self.v[3], _mm_set_epi64x(0, PAR_BLOCKS as i64)); + + let shifted = _mm_add_epi64(self.v[3], _mm_set_epi64x(0,i64::MIN)); + let new_shifted = _mm_add_epi64(new_v3, _mm_set_epi64x(0,i64::MIN)); + let mask = _mm_cmpgt_epi64(shifted,new_shifted); + let max_val = _mm_and_si128(mask,_mm_set_epi64x(0,-1)); + let new_val = _mm_andnot_si128(mask,new_v3); + self.v[3] = _mm_or_si128(max_val,new_val); + } let blocks_ptr = blocks.as_mut_ptr() as *mut __m128i; for block in 0..PAR_BLOCKS { @@ -98,28 +151,50 @@ where V: Variant, { let state_ptr = core.state.as_ptr() as *const __m128i; - let mut backend = Backend:: { + let mut backend = Backend:: { v: [ _mm_loadu_si128(state_ptr.add(0)), _mm_loadu_si128(state_ptr.add(1)), _mm_loadu_si128(state_ptr.add(2)), _mm_loadu_si128(state_ptr.add(3)), ], + counter: core.counter, _pd: PhantomData, }; backend.gen_ks_blocks(buffer); - core.state[12] = _mm_cvtsi128_si32(backend.v[3]) as u32; + core.counter = backend.counter; + if V::COUNTER_SIZE == 1 { + core.state[12] = _mm_cvtsi128_si32(backend.v[3]) as u32; + } else { + let ctr = _mm_cvtsi128_si64(backend.v[3]) as u64; + + core.state[12] = (ctr&(u32::MAX as u64)) as u32; + core.state[13] = (ctr>>32) as u32; + } } #[cfg(feature = "rng")] -impl Backend { +impl Backend { #[inline(always)] fn gen_ks_blocks(&mut self, block: &mut [u32]) { + if V::COUNTER_SIZE == 1 { + self.counter = V::MAX_USABLE_COUNTER & + self.counter.saturating_add(PAR_BLOCKS as u64); + } else { + self.counter = self.counter.saturating_add(PAR_BLOCKS as u64); + } + unsafe { - let res = rounds::(&self.v); - self.v[3] = _mm_add_epi32(self.v[3], _mm_set_epi32(0, 0, 0, PAR_BLOCKS as i32)); + let res = rounds::(&self.v); + if V::COUNTER_SIZE == 1 { + + self.v[3] = _mm_add_epi32(self.v[3], _mm_set_epi32(0, 0, 0, PAR_BLOCKS as i32)); + + } else { + self.v[3] = _mm_add_epi64(self.v[3], _mm_set_epi64x(0, PAR_BLOCKS as i64)); + } let blocks_ptr = block.as_mut_ptr() as *mut __m128i; for block in 0..PAR_BLOCKS { @@ -133,10 +208,15 @@ impl Backend { #[inline] #[target_feature(enable = "sse2")] -unsafe fn rounds(v: &[__m128i; 4]) -> [[__m128i; 4]; PAR_BLOCKS] { +unsafe fn rounds(v: &[__m128i; 4]) -> [[__m128i; 4]; PAR_BLOCKS] { let mut res = [*v; 4]; for block in 1..PAR_BLOCKS { - res[block][3] = _mm_add_epi32(res[block][3], _mm_set_epi32(0, 0, 0, block as i32)); + if V::COUNTER_SIZE == 1 { + res[block][3] = _mm_add_epi32(res[block][3], _mm_set_epi32(0, 0, 0, block as i32)); + } else { + res[block][3] = _mm_add_epi64(res[block][3], _mm_set_epi64x(0, block as i64)); + } + } for _ in 0..R::COUNT { @@ -148,7 +228,11 @@ unsafe fn rounds(v: &[__m128i; 4]) -> [[__m128i; 4]; PAR_BLOCKS] { res[block][i] = _mm_add_epi32(res[block][i], v[i]); } // add the counter since `v` is lacking updated counter values - res[block][3] = _mm_add_epi32(res[block][3], _mm_set_epi32(0, 0, 0, block as i32)); + if V::COUNTER_SIZE == 1 { + res[block][3] = _mm_add_epi32(res[block][3], _mm_set_epi32(0, 0, 0, block as i32)); + } else { + res[block][3] = _mm_add_epi64(res[block][3], _mm_set_epi64x(0, block as i64)); + } } res diff --git a/chacha20/src/lib.rs b/chacha20/src/lib.rs index bb400a2c..a4c9227d 100644 --- a/chacha20/src/lib.rs +++ b/chacha20/src/lib.rs @@ -215,6 +215,8 @@ pub struct ChaChaCore { /// CPU target feature tokens #[allow(dead_code)] tokens: Tokens, + /// Current counter position + counter: u64, /// Number of rounds to perform rounds: PhantomData, /// the variant of the implementation @@ -254,9 +256,11 @@ impl ChaChaCore { let tokens = (); } } + debug_assert_eq!(state[12], 0); Self { state, tokens, + counter: 0, rounds: PhantomData, variant: PhantomData, } @@ -265,16 +269,31 @@ impl ChaChaCore { #[cfg(feature = "cipher")] impl StreamCipherSeekCore for ChaChaCore { - type Counter = u32; + type Counter = u64; #[inline(always)] fn get_block_pos(&self) -> Self::Counter { - self.state[12] + let le_pos = self.counter.to_le_bytes(); + let max_val = V::MAX_USABLE_COUNTER; + if self.counter <= max_val { + for i in 0..V::COUNTER_SIZE { + debug_assert_eq!( + self.state[12 + i], + u32::from_le_bytes(<_>::try_from(&le_pos[4 * i..(4 * (i + 1))]).unwrap()) + ); + } + } + self.counter } #[inline(always)] fn set_block_pos(&mut self, pos: Self::Counter) { - self.state[12] = pos + self.counter = pos; + let le_pos = pos.to_le_bytes(); + for i in 0..V::COUNTER_SIZE { + self.state[12 + i] = + u32::from_le_bytes(<_>::try_from(&le_pos[4 * i..(4 * (i + 1))]).unwrap()); + } } } @@ -282,7 +301,8 @@ impl StreamCipherSeekCore for ChaChaCore { impl StreamCipherCore for ChaChaCore { #[inline(always)] fn remaining_blocks(&self) -> Option { - let rem = u32::MAX - self.get_block_pos(); + let max_val = V::MAX_USABLE_COUNTER; + let rem = max_val.saturating_sub(self.get_block_pos()); rem.try_into().ok() } @@ -301,7 +321,7 @@ impl StreamCipherCore for ChaChaCore { } } else if #[cfg(chacha20_force_sse2)] { unsafe { - backends::sse2::inner::(&mut self.state, f); + backends::sse2::inner::(&mut self.counter, &mut self.state, f); } } else { let (avx2_token, sse2_token) = self.tokens; @@ -311,7 +331,7 @@ impl StreamCipherCore for ChaChaCore { } } else if sse2_token.get() { unsafe { - backends::sse2::inner::(&mut self.state, f); + backends::sse2::inner::(&mut self.counter, &mut self.state, f); } } else { f.call(&mut backends::soft::Backend(self)); diff --git a/chacha20/src/variants.rs b/chacha20/src/variants.rs index 58043a75..ad96e0ca 100644 --- a/chacha20/src/variants.rs +++ b/chacha20/src/variants.rs @@ -6,6 +6,10 @@ pub trait Variant: Clone { /// the size of the Nonce in u32s const NONCE_INDEX: usize; + /// the number of u32s used for the counter + const COUNTER_SIZE: usize; + /// the maximum counter value with available keystream + const MAX_USABLE_COUNTER: u64 = (u64::MAX >> ((2 - Self::COUNTER_SIZE) * 32)); } #[derive(Clone)] @@ -13,6 +17,17 @@ pub trait Variant: Clone { pub struct Ietf(); impl Variant for Ietf { const NONCE_INDEX: usize = 13; + const COUNTER_SIZE: usize = 1; +} + +#[derive(Clone)] +#[cfg(feature = "xchacha")] +pub struct XChaCha(); + +#[cfg(feature = "xchacha")] +impl Variant for XChaCha { + const NONCE_INDEX: usize = 14; + const COUNTER_SIZE: usize = 2; } #[derive(Clone)] @@ -22,4 +37,5 @@ pub struct Legacy(); #[cfg(feature = "legacy")] impl Variant for Legacy { const NONCE_INDEX: usize = 14; + const COUNTER_SIZE: usize = 2; } diff --git a/chacha20/src/xchacha.rs b/chacha20/src/xchacha.rs index c1fa6571..ff76e387 100644 --- a/chacha20/src/xchacha.rs +++ b/chacha20/src/xchacha.rs @@ -8,7 +8,7 @@ use cipher::{ }; use crate::{ - CONSTANTS, ChaChaCore, R8, R12, R20, Rounds, STATE_WORDS, quarter_round, variants::Ietf, + CONSTANTS, ChaChaCore, R8, R12, R20, Rounds, STATE_WORDS, quarter_round, variants::XChaCha, }; #[cfg(feature = "zeroize")] @@ -42,7 +42,7 @@ pub type XChaCha12 = StreamCipherCoreWrapper>; pub type XChaCha8 = StreamCipherCoreWrapper>; /// The XChaCha core function. -pub struct XChaChaCore(ChaChaCore); +pub struct XChaChaCore(ChaChaCore); impl KeySizeUser for XChaChaCore { type KeySize = U32; @@ -60,11 +60,9 @@ impl KeyIvInit for XChaChaCore { fn new(key: &Key, iv: &XNonce) -> Self { let subkey = hchacha::(key, iv[..16].as_ref().try_into().unwrap()); - let mut nonce = [0u8; 12]; - // first 4 bytes are 0, last 8 bytes are last 8 from the iv - // according to draft-arciszewski-xchacha-03 - nonce[4..].copy_from_slice(&iv[16..]); - Self(ChaChaCore::::new(subkey.as_ref(), &nonce)) + let mut nonce = [0u8; 8]; + nonce[..].copy_from_slice(&iv[16..]); + Self(ChaChaCore::::new(subkey.as_ref(), &nonce)) } } @@ -81,15 +79,15 @@ impl StreamCipherCore for XChaChaCore { } impl StreamCipherSeekCore for XChaChaCore { - type Counter = u32; + type Counter = u64; #[inline(always)] - fn get_block_pos(&self) -> u32 { + fn get_block_pos(&self) -> u64 { self.0.get_block_pos() } #[inline(always)] - fn set_block_pos(&mut self, pos: u32) { + fn set_block_pos(&mut self, pos: u64) { self.0.set_block_pos(pos); } } diff --git a/chacha20/tests/mod.rs b/chacha20/tests/mod.rs index 4e4aa33c..74308f10 100644 --- a/chacha20/tests/mod.rs +++ b/chacha20/tests/mod.rs @@ -234,3 +234,232 @@ mod legacy { } } } + +mod overflow { + use chacha20::KeyIvInit; + use cipher::{StreamCipher, StreamCipherSeek}; + + const OFFSET_256GB: u64 = 256u64 << 30; + #[cfg(feature = "xchacha")] + const OFFSET_256PB: u64 = 256u64 << 50; + #[cfg(feature = "xchacha")] + const OFFSET_1ZB: u128 = (64u128) << 64; + + #[test] + fn bad_overflow_check1() { + let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_256GB - 1) + .expect("Couldn't seek to nearly 256GB"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the last byte of 256GB"); + assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect_err("Could encrypt past the last byte of 256GB"); + } + + #[test] + fn test_starting_pos() { + let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); + assert_eq!(cipher.try_current_pos::().unwrap(), 0); + cipher.try_seek(0).unwrap(); + assert_eq!(cipher.try_current_pos::().unwrap(), 0); + } + + #[test] + fn test_current_pos() { + let offset: u64 = 256u64 << 30; + + let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); + cipher + .try_seek(offset - 64) + .expect("Couldn't seek to nearly 256GB"); + assert_eq!(cipher.try_current_pos::().unwrap(), offset - (1 << 6)); + cipher + .try_seek(offset - 63) + .expect("Couldn't seek to nearly 256GB"); + cipher.try_current_pos::().unwrap(); + } + + #[test] + fn bad_overflow_check2() { + let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_256GB - 1) + .expect("Couldn't seek to nearly 256GB"); + let mut data = [0u8; 2]; + cipher + .try_apply_keystream(&mut data) + .expect_err("Could encrypt over the 256GB boundary"); + } + + #[test] + fn bad_overflow_check3() { + let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_256GB - 1) + .expect("Couldn't seek to nearly 256GB"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the last byte of 256GB"); + assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); + let mut data = [0u8; 63]; + cipher + .try_apply_keystream(&mut data) + .expect_err("Could encrypt past the last byte of 256GB"); + } + + #[test] + fn bad_overflow_check4() { + let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_256GB - 1) + .expect("Couldn't seek to nearly 256GB"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the last byte of 256GB"); + assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); + let mut data = [0u8; 64]; + cipher + .try_apply_keystream(&mut data) + .expect_err("Could encrypt past the last byte of 256GB"); + } + + #[test] + fn bad_overflow_check5() { + let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_256GB - 1) + .expect("Couldn't seek to nearly 256GB"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the last byte of 256GB"); + assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); + let mut data = [0u8; 65]; + cipher + .try_apply_keystream(&mut data) + .expect_err("Could encrypt past the last byte of 256GB"); + } + + // currently non-working due to the 64-bit counter hack + #[test] + #[ignore] + fn bad_overflow_check6() { + let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_256GB) + .expect_err("Could seek to 256GB"); + } + + // currently non-working due to the 64-bit counter hack + #[test] + #[ignore] + fn bad_overflow_check7() { + let mut cipher = chacha20::ChaCha20::new(&Default::default(), &Default::default()); + if let Ok(()) = cipher.try_seek(OFFSET_256GB + 63) { + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect_err("Could encrypt the 64th byte past the 256GB boundary"); + } + } + + #[test] + #[cfg(feature = "xchacha")] + fn xchacha_256gb() { + let mut cipher = chacha20::XChaCha20::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_256GB - 1) + .expect("Couldn't seek to nearly 256GB"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the last byte of 256GB"); + assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt past the last byte of 256GB"); + } + + #[test] + #[cfg(feature = "xchacha")] + fn xchacha_upper_limit() { + let mut cipher = chacha20::XChaCha20::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_1ZB - 1) + .expect("Couldn't seek to nearly 1 zebibyte"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the last byte of 1 zebibyte"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect_err("Could encrypt past 1 zebibyte"); + } + + #[test] + #[cfg(feature = "xchacha")] + fn xchacha_has_a_big_counter() { + let mut cipher = chacha20::XChaCha20::new(&Default::default(), &Default::default()); + cipher.try_seek(OFFSET_256PB).expect("Could seek to 256PB"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the next byte after 256PB"); + } + + #[cfg(feature = "legacy")] + #[test] + fn legacy_256gb() { + let mut cipher = chacha20::ChaCha20Legacy::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_256GB - 1) + .expect("Couldn't seek to nearly 256GB"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the last byte of 256GB"); + assert_eq!(cipher.try_current_pos::().unwrap(), OFFSET_256GB); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt past the last byte of 256GB"); + } + + #[cfg(feature = "legacy")] + #[test] + fn legacy_upper_limit() { + let mut cipher = chacha20::ChaCha20Legacy::new(&Default::default(), &Default::default()); + cipher + .try_seek(OFFSET_1ZB - 1) + .expect("Couldn't seek to nearly 1 zebibyte"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the last byte of 1 zebibyte"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect_err("Could encrypt past 1 zebibyte"); + } + + #[cfg(feature = "legacy")] + #[test] + fn legacy_has_a_big_counter() { + let mut cipher = chacha20::ChaCha20Legacy::new(&Default::default(), &Default::default()); + cipher.try_seek(OFFSET_256PB).expect("Could seek to 256PB"); + let mut data = [0u8; 1]; + cipher + .try_apply_keystream(&mut data) + .expect("Couldn't encrypt the next byte after 256PB"); + } +}