Skip to content

Commit f92f79d

Browse files
committed
ssh-cipher: add Decryptor::peek
As discussed in RustCrypto/block-modes#91
1 parent c860555 commit f92f79d

5 files changed

Lines changed: 98 additions & 27 deletions

File tree

Cargo.lock

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ssh-cipher/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ encoding = { package = "ssh-encoding", version = "0.3.0-rc.9" }
2626
aead = { version = "0.6.0-rc.10", optional = true, default-features = false }
2727
aes = { version = "0.9", optional = true, default-features = false }
2828
aes-gcm = { version = "0.11.0-rc.3", optional = true, default-features = false, features = ["aes"] }
29-
cbc = { version = "0.2", optional = true }
29+
cbc = { version = "0.2.1", optional = true }
3030
ctr = { version = "0.10", optional = true, default-features = false }
3131
ctutils = { version = "0.4", optional = true, default-features = false }
3232
chacha20 = { version = "0.10", optional = true, default-features = false, features = ["cipher", "legacy"] }

ssh-cipher/src/decryptor.rs

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
//! Stateful decryptor object.
22
33
use crate::{Cipher, Error, Result};
4-
use cipher::KeyIvInit;
4+
use cipher::{KeyIvInit, SetIvState};
55
use core::fmt::{self, Debug};
66

7-
#[cfg(feature = "aes-ctr")]
8-
use crate::{Ctr128BE, encryptor::ctr_encrypt as ctr_decrypt};
97
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
108
use aes::{Aes128, Aes192, Aes256};
119
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
1210
use cipher::{
13-
Block,
11+
Block, IvState, StreamCipherSeek,
1412
block::{BlockCipherDecrypt, BlockModeDecrypt},
1513
};
1614
#[cfg(feature = "tdes")]
1715
use des::TdesEde3;
16+
#[cfg(feature = "aes-ctr")]
17+
use {crate::encryptor::ctr_encrypt as ctr_decrypt, ctr::Ctr128BE};
1818

1919
/// Stateful decryptor object for unauthenticated SSH symmetric ciphers.
2020
///
@@ -43,6 +43,16 @@ enum Inner {
4343
TDesCbc(cbc::Decryptor<TdesEde3>),
4444
}
4545

46+
/// Current IV state or position within the cipher.
47+
enum State {
48+
#[cfg(feature = "aes-cbc")]
49+
AesCbc(aes::Block),
50+
#[cfg(feature = "aes-ctr")]
51+
AesCtr(u64),
52+
#[cfg(feature = "tdes")]
53+
TDesCbc(Block<TdesEde3>),
54+
}
55+
4656
impl Decryptor {
4757
/// Create a new decryptor object with the given [`Cipher`], `key`, and `iv` (i.e.
4858
/// initialization vector).
@@ -124,6 +134,77 @@ impl Decryptor {
124134

125135
Ok(())
126136
}
137+
138+
/// Call the provided function with an ephemeral [`Decryptor`] state which will be reset upon
139+
/// completion, returning the result of the function.
140+
pub fn peek<T, F>(&mut self, mut f: F) -> Result<T>
141+
where
142+
F: FnMut(&mut Self) -> Result<T>,
143+
{
144+
let state = self.state();
145+
let ret = f(self);
146+
self.set_state(state)?;
147+
ret
148+
}
149+
150+
/// Get the current cipher state, i.e. IV or position within the stream cipher.
151+
fn state(&self) -> State {
152+
match &self.inner {
153+
#[cfg(feature = "aes-cbc")]
154+
Inner::Aes128Cbc(cipher) => State::AesCbc(cipher.iv_state()),
155+
#[cfg(feature = "aes-cbc")]
156+
Inner::Aes192Cbc(cipher) => State::AesCbc(cipher.iv_state()),
157+
#[cfg(feature = "aes-cbc")]
158+
Inner::Aes256Cbc(cipher) => State::AesCbc(cipher.iv_state()),
159+
#[cfg(feature = "aes-ctr")]
160+
Inner::Aes128Ctr(cipher) => State::AesCtr(cipher.current_pos()),
161+
#[cfg(feature = "aes-ctr")]
162+
Inner::Aes192Ctr(cipher) => State::AesCtr(cipher.current_pos()),
163+
#[cfg(feature = "aes-ctr")]
164+
Inner::Aes256Ctr(cipher) => State::AesCtr(cipher.current_pos()),
165+
#[cfg(feature = "tdes")]
166+
Inner::TDesCbc(cipher) => State::TDesCbc(cipher.iv_state()),
167+
}
168+
}
169+
170+
/// Set the current cipher state.
171+
fn set_state(&mut self, state: State) -> Result<()> {
172+
match (&mut self.inner, state) {
173+
#[cfg(feature = "aes-cbc")]
174+
(Inner::Aes128Cbc(cipher), State::AesCbc(iv)) => {
175+
cipher.set_iv(&iv);
176+
Ok(())
177+
}
178+
#[cfg(feature = "aes-cbc")]
179+
(Inner::Aes192Cbc(cipher), State::AesCbc(iv)) => {
180+
cipher.set_iv(&iv);
181+
Ok(())
182+
}
183+
#[cfg(feature = "aes-cbc")]
184+
(Inner::Aes256Cbc(cipher), State::AesCbc(iv)) => {
185+
cipher.set_iv(&iv);
186+
Ok(())
187+
}
188+
#[cfg(feature = "aes-ctr")]
189+
(Inner::Aes128Ctr(cipher), State::AesCtr(pos)) => {
190+
cipher.try_seek(pos).map_err(|_| Error::Crypto)
191+
}
192+
#[cfg(feature = "aes-ctr")]
193+
(Inner::Aes192Ctr(cipher), State::AesCtr(pos)) => {
194+
cipher.try_seek(pos).map_err(|_| Error::Crypto)
195+
}
196+
#[cfg(feature = "aes-ctr")]
197+
(Inner::Aes256Ctr(cipher), State::AesCtr(pos)) => {
198+
cipher.try_seek(pos).map_err(|_| Error::Crypto)
199+
}
200+
#[cfg(feature = "tdes")]
201+
(Inner::TDesCbc(cipher), State::TDesCbc(iv)) => {
202+
cipher.set_iv(&iv);
203+
Ok(())
204+
}
205+
_ => Err(Error::Crypto), // should be unreachable
206+
}
207+
}
127208
}
128209

129210
impl Debug for Decryptor {

ssh-cipher/src/encryptor.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Stateful encryptor object.
22
33
use crate::{Cipher, Error, Result};
4-
use cipher::{Block, BlockCipherEncrypt, KeyIvInit};
4+
use cipher::{Block, BlockCipherEncrypt, KeyIvInit, StreamCipher};
55
use core::fmt::{self, Debug};
66

77
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
@@ -12,8 +12,8 @@ use cipher::block::BlockModeEncrypt;
1212
use des::TdesEde3;
1313
#[cfg(feature = "aes-ctr")]
1414
use {
15-
crate::Ctr128BE,
16-
cipher::{BlockSizeUser, StreamCipherCore, array::sizes::U16},
15+
cipher::{BlockSizeUser, array::sizes::U16},
16+
ctr::Ctr128BE,
1717
};
1818

1919
/// Stateful encryptor object for unauthenticated SSH symmetric ciphers.
@@ -155,13 +155,7 @@ pub(crate) fn ctr_encrypt<C>(encryptor: &mut Ctr128BE<C>, buffer: &mut [u8]) ->
155155
where
156156
C: BlockCipherEncrypt + BlockSizeUser<BlockSize = U16>,
157157
{
158-
let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer);
159-
160-
// Ensure input is block-aligned.
161-
if !remaining.is_empty() {
162-
return Err(Error::Length);
163-
}
164-
165-
encryptor.apply_keystream_blocks(blocks);
166-
Ok(())
158+
encryptor
159+
.try_apply_keystream(buffer)
160+
.map_err(|_| Error::Crypto)
167161
}

ssh-cipher/src/lib.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ pub type AesGcmNonce = Array<u8, U12>;
7777
/// `chacha20-poly1305@openssh.com`.
7878
pub type Tag = Array<u8, U16>;
7979

80-
/// Counter mode with a 128-bit big endian counter.
81-
#[cfg(feature = "aes-ctr")]
82-
type Ctr128BE<Cipher> = ctr::CtrCore<Cipher, ctr::flavors::Ctr128BE>;
83-
8480
/// Cipher algorithms.
8581
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
8682
#[non_exhaustive]

0 commit comments

Comments
 (0)