Skip to content

Commit d6c7cd3

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

5 files changed

Lines changed: 101 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: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ use crate::{Cipher, Error, Result};
44
use cipher::KeyIvInit;
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"))]
10+
use cipher::SetIvState;
11+
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
1212
use cipher::{
13-
Block,
13+
Block, IvState,
1414
block::{BlockCipherDecrypt, BlockModeDecrypt},
1515
};
1616
#[cfg(feature = "tdes")]
1717
use des::TdesEde3;
18+
#[cfg(feature = "aes-ctr")]
19+
use {crate::encryptor::ctr_encrypt as ctr_decrypt, cipher::StreamCipherSeek, ctr::Ctr128BE};
1820

1921
/// Stateful decryptor object for unauthenticated SSH symmetric ciphers.
2022
///
@@ -43,6 +45,16 @@ enum Inner {
4345
TDesCbc(cbc::Decryptor<TdesEde3>),
4446
}
4547

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

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

129213
impl Debug for Decryptor {

ssh-cipher/src/encryptor.rs

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

77
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
88
use aes::{Aes128, Aes192, Aes256};
99
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
10-
use cipher::block::BlockModeEncrypt;
10+
use cipher::{Block, BlockModeEncrypt};
1111
#[cfg(feature = "tdes")]
1212
use des::TdesEde3;
1313
#[cfg(feature = "aes-ctr")]
1414
use {
15-
crate::Ctr128BE,
16-
cipher::{BlockSizeUser, StreamCipherCore, array::sizes::U16},
15+
cipher::{BlockSizeUser, StreamCipher, 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)