Skip to content

Commit 165274b

Browse files
authored
ssh-cipher: impl BlockMode* for Decryptor/Encryptor (#534)
Impls the `BlockMode*` traits from the `cipher` crate, bringing the implementation closer in line with our other block mode implementations. This does have one weird quirk: we can't impl `BlockModeDecrypt::decrypt_with_backend` for `Decryptor` because that only gives us access to `BlockCipherDecBackend`, whereas to implement CTR mode we need access to `BlockCipherEncBackend` too as encryption and decryption are the same operation in CTR mode. So this foregoes such an impl with an `unimplemented!` and directly implements the `decrypt_block` and `decrypt_blocks` methods instead. This doesn't currently support any form of padding, although with the `BlockMode*` traits implemented a downstream user can provide whatever padding they wish with `encrypt_padded`/`decrypt_padded`.
1 parent bb1db47 commit 165274b

4 files changed

Lines changed: 188 additions & 65 deletions

File tree

ssh-cipher/src/block_cipher.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@ mod decryptor;
99
mod encryptor;
1010
mod state;
1111

12+
pub use self::{decryptor::Decryptor, encryptor::Encryptor};
13+
pub use ::cipher::{
14+
Block, BlockModeDecrypt, BlockModeEncrypt, common::BlockSizeUser, common::InvalidLength,
15+
};
16+
1217
#[cfg(feature = "aes")]
1318
pub use self::aes::Aes;
14-
pub use self::{decryptor::Decryptor, encryptor::Encryptor};
1519
#[cfg(feature = "tdes")]
1620
pub use ::des::TdesEde3 as Tdes;
1721

1822
use self::state::State;
1923

2024
#[cfg(feature = "tdes")]
21-
use {
22-
crate::Cipher,
23-
::cipher::common::{InvalidLength, KeyInit},
24-
};
25+
use {crate::Cipher, ::cipher::common::KeyInit};
2526

2627
/// Seal the `BlockCipher` trait so others cannot implement it.
2728
pub(crate) mod sealed {

ssh-cipher/src/block_cipher/decryptor.rs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
33
use super::{BlockMode, State, sealed::BlockCipher};
44
use crate::{Cipher, Error, Result};
5-
use ::cipher::{Block, typenum::Unsigned};
5+
use ::cipher::{
6+
Block, BlockModeDecClosure, BlockModeDecrypt,
7+
common::{BlockSizeUser, InnerUser},
8+
};
69
use core::fmt::{self, Debug};
710

811
/// Stateful decryptor object for unauthenticated symmetric ciphers used in the SSH packet
@@ -38,24 +41,6 @@ impl<C: BlockCipher> Decryptor<C> {
3841
Ok(Self { cipher, state })
3942
}
4043

41-
/// Decrypt the given buffer in place.
42-
///
43-
/// # Errors
44-
/// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's
45-
/// block size.
46-
pub fn decrypt(&mut self, buffer: &mut [u8]) -> Result<()> {
47-
#[allow(clippy::integer_division_remainder_used, reason = "non-secret length")]
48-
if buffer.len() % C::BlockSize::USIZE != 0 {
49-
return Err(Error::Length);
50-
}
51-
52-
for block in Block::<C>::slice_as_chunks_mut(buffer).0 {
53-
self.decrypt_block(block);
54-
}
55-
56-
Ok(())
57-
}
58-
5944
/// Call the provided function with an ephemeral [`Decryptor`] state which will be reset upon
6045
/// completion, returning the result of the function.
6146
pub fn peek<T, F>(&mut self, mut f: F) -> T
@@ -67,12 +52,14 @@ impl<C: BlockCipher> Decryptor<C> {
6752
self.state = state;
6853
ret
6954
}
55+
}
56+
57+
impl<C: BlockCipher> BlockModeDecrypt for Decryptor<C> {
58+
fn decrypt_with_backend(&mut self, _f: impl BlockModeDecClosure<BlockSize = Self::BlockSize>) {
59+
unimplemented!("CTR mode support is incompatible with BlockModeDecrypt")
60+
}
7061

71-
/// Decrypt a single block.
72-
///
73-
/// # Panics
74-
/// If `block` is not the correct block size for this cipher.
75-
fn decrypt_block(&mut self, block: &mut Block<C>) {
62+
fn decrypt_block(&mut self, block: &mut Block<Self>) {
7663
match self.state.mode() {
7764
BlockMode::Cbc => {
7865
let pad = self.state.clone();
@@ -88,10 +75,24 @@ impl<C: BlockCipher> Decryptor<C> {
8875
}
8976
}
9077
}
78+
79+
fn decrypt_blocks(&mut self, blocks: &mut [Block<Self>]) {
80+
// TODO(tarcieri): parallel decryption support
81+
for block in blocks {
82+
self.decrypt_block(block);
83+
}
84+
}
85+
}
86+
impl<C: BlockCipher> BlockSizeUser for Decryptor<C> {
87+
type BlockSize = C::BlockSize;
9188
}
9289

9390
impl<C: BlockCipher> Debug for Decryptor<C> {
9491
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
9592
f.debug_struct("Decryptor").finish_non_exhaustive()
9693
}
9794
}
95+
96+
impl<C: BlockCipher> InnerUser for Decryptor<C> {
97+
type Inner = C;
98+
}

ssh-cipher/src/block_cipher/encryptor.rs

Lines changed: 102 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,15 @@
22
33
use super::{BlockMode, State, sealed::BlockCipher};
44
use crate::{Cipher, Error, Result};
5-
use ::cipher::{Block, typenum::Unsigned};
5+
use ::cipher::{
6+
Block, BlockCipherEncBackend, BlockCipherEncClosure, BlockModeEncBackend, BlockModeEncClosure,
7+
BlockModeEncrypt,
8+
common::{
9+
BlockSizeUser, InnerUser, ParBlocksSizeUser,
10+
array::{ArraySize, sizes::U1},
11+
},
12+
inout::InOut,
13+
};
614
use core::fmt::{self, Debug};
715

816
/// Stateful encryptor object for unauthenticated symmetric ciphers used in the SSH packet
@@ -34,48 +42,114 @@ impl<C: BlockCipher> Encryptor<C> {
3442
let state = State::new_from_slice(mode, iv)?;
3543
Ok(Self { cipher, state })
3644
}
45+
}
3746

38-
/// Encrypt the given buffer in-place.
39-
///
40-
/// # Errors
41-
/// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's
42-
/// block size.
43-
pub fn encrypt(&mut self, buffer: &mut [u8]) -> Result<()> {
44-
#[allow(clippy::integer_division_remainder_used, reason = "non-secret length")]
45-
if buffer.len() % C::BlockSize::USIZE != 0 {
46-
return Err(Error::Length);
47+
impl<C: BlockCipher> BlockModeEncrypt for Encryptor<C> {
48+
fn encrypt_with_backend(&mut self, f: impl BlockModeEncClosure<BlockSize = Self::BlockSize>) {
49+
struct Closure<'a, BS, BC>
50+
where
51+
BS: ArraySize,
52+
BC: BlockModeEncClosure<BlockSize = BS>,
53+
{
54+
state: &'a mut State<BS>,
55+
f: BC,
56+
}
57+
58+
impl<BS, BC> BlockSizeUser for Closure<'_, BS, BC>
59+
where
60+
BS: ArraySize,
61+
BC: BlockModeEncClosure<BlockSize = BS>,
62+
{
63+
type BlockSize = BS;
4764
}
4865

49-
for block in Block::<C>::slice_as_chunks_mut(buffer).0 {
50-
self.encrypt_block(block);
66+
impl<BS, BC> BlockCipherEncClosure for Closure<'_, BS, BC>
67+
where
68+
BS: ArraySize,
69+
BC: BlockModeEncClosure<BlockSize = BS>,
70+
{
71+
#[inline(always)]
72+
fn call<B: BlockCipherEncBackend<BlockSize = Self::BlockSize>>(
73+
self,
74+
cipher_backend: &B,
75+
) {
76+
let Self { state, f } = self;
77+
f.call(&mut Backend {
78+
state,
79+
cipher_backend,
80+
});
81+
}
5182
}
5283

53-
Ok(())
84+
let Self { cipher, state } = self;
85+
cipher.encrypt_with_backend(Closure { state, f });
5486
}
87+
}
88+
89+
impl<C: BlockCipher> BlockSizeUser for Encryptor<C> {
90+
type BlockSize = C::BlockSize;
91+
}
92+
93+
impl<C: BlockCipher> Debug for Encryptor<C> {
94+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95+
f.debug_struct("Encryptor").finish_non_exhaustive()
96+
}
97+
}
98+
99+
impl<C: BlockCipher> InnerUser for Encryptor<C> {
100+
type Inner = C;
101+
}
102+
103+
struct Backend<'a, BS, BK>
104+
where
105+
BS: ArraySize,
106+
BK: BlockCipherEncBackend<BlockSize = BS>,
107+
{
108+
state: &'a mut State<BS>,
109+
cipher_backend: &'a BK,
110+
}
111+
112+
impl<BS, BK> BlockSizeUser for Backend<'_, BS, BK>
113+
where
114+
BS: ArraySize,
115+
BK: BlockCipherEncBackend<BlockSize = BS>,
116+
{
117+
type BlockSize = BS;
118+
}
119+
120+
impl<BS, BK> ParBlocksSizeUser for Backend<'_, BS, BK>
121+
where
122+
BS: ArraySize,
123+
BK: BlockCipherEncBackend<BlockSize = BS>,
124+
{
125+
// CBC encryption cannot be performed in parallel
126+
// TODO(tarcieri): parallel encryption support for CTR mode, serial for CBC
127+
type ParBlocksSize = U1;
128+
}
129+
130+
impl<BS, BK> BlockModeEncBackend for Backend<'_, BS, BK>
131+
where
132+
BS: ArraySize,
133+
BK: BlockCipherEncBackend<BlockSize = BS>,
134+
{
135+
#[inline(always)]
136+
fn encrypt_block(&mut self, mut block: InOut<'_, '_, Block<Self>>) {
137+
let mut t = block.clone_in();
55138

56-
/// Encrypt a single block.
57-
///
58-
/// # Panics
59-
/// If `block` is not the correct block size for this cipher.
60-
fn encrypt_block(&mut self, block: &mut Block<C>) {
61139
match self.state.mode() {
62140
BlockMode::Cbc => {
63-
self.state.xor_into(block);
64-
self.cipher.encrypt_block(block);
65-
self.state.as_mut().copy_from_slice(block);
141+
self.state.xor_into(&mut t);
142+
self.cipher_backend.encrypt_block(InOut::from(&mut t));
143+
self.state.as_mut().copy_from_slice(&t);
66144
}
67145
BlockMode::Ctr => {
68146
let mut pad = self.state.clone();
69-
self.cipher.encrypt_block(pad.as_mut());
70-
pad.xor_into(block);
147+
self.cipher_backend.encrypt_block(pad.as_mut().into());
148+
pad.xor_into(&mut t);
71149
self.state.increment_counter();
72150
}
73151
}
74-
}
75-
}
76152

77-
impl<C: BlockCipher> Debug for Encryptor<C> {
78-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79-
f.debug_struct("Encryptor").finish_non_exhaustive()
153+
*block.get_out() = t;
80154
}
81155
}

ssh-cipher/src/lib.rs

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,13 @@ use encoding::{Label, LabelError};
2727
use self::block_cipher::Aes;
2828
#[cfg(feature = "tdes")]
2929
use self::block_cipher::Tdes;
30-
#[cfg(any(feature = "aes", feature = "tdes"))]
31-
use self::block_cipher::{BlockMode, sealed::BlockCipher};
3230
#[cfg(any(feature = "aes", feature = "chacha20poly1305"))]
3331
use ::aead::{AeadInOut, KeyInit};
32+
#[cfg(any(feature = "aes", feature = "tdes"))]
33+
use {
34+
self::block_cipher::{BlockMode, sealed::BlockCipher},
35+
::cipher::{Block, BlockModeDecrypt, BlockModeEncrypt},
36+
};
3437
#[cfg(feature = "aes")]
3538
use {
3639
aead::array::typenum::U12,
@@ -302,22 +305,43 @@ impl Cipher {
302305
if tag.is_some() {
303306
return Err(Error::Crypto);
304307
}
305-
306-
self.decryptor::<Aes>(key, iv)?.decrypt(buffer)
308+
self.decrypt_with_block_cipher::<Aes>(key, iv, buffer)
307309
}
308310
#[cfg(feature = "tdes")]
309311
Self::TdesCbc => {
310312
// Non-AEAD modes don't take a tag.
311313
if tag.is_some() {
312314
return Err(Error::Crypto);
313315
}
314-
315-
self.decryptor::<Tdes>(key, iv)?.decrypt(buffer)
316+
self.decrypt_with_block_cipher::<Tdes>(key, iv, buffer)
316317
}
317318
_ => Err(Error::UnsupportedCipher(self)),
318319
}
319320
}
320321

322+
/// Perform decryption using a dynamically selected block cipher mode of operation.
323+
///
324+
/// Note that this does not support any form of padding currently.
325+
///
326+
/// # Errors
327+
/// Returns [`Error::Length`] unless the length of `buffer` is a multiple of the block size.
328+
#[cfg(any(feature = "aes", feature = "tdes"))]
329+
fn decrypt_with_block_cipher<C: BlockCipher>(
330+
self,
331+
key: &[u8],
332+
iv: &[u8],
333+
buffer: &mut [u8],
334+
) -> Result<()> {
335+
let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer);
336+
337+
if !remaining.is_empty() {
338+
return Err(Error::Length);
339+
}
340+
341+
self.decryptor::<C>(key, iv)?.decrypt_blocks(blocks);
342+
Ok(())
343+
}
344+
321345
/// Get a stateful [`block_cipher::Decryptor`] for the given key and IV.
322346
///
323347
/// Only applicable to unauthenticated modes (e.g. AES-CBC, AES-CTR). Not usable with
@@ -377,18 +401,41 @@ impl Cipher {
377401
| Self::Aes128Ctr
378402
| Self::Aes192Ctr
379403
| Self::Aes256Ctr => {
380-
self.encryptor::<Aes>(key, iv)?.encrypt(buffer)?;
404+
self.encrypt_with_block_cipher::<Aes>(key, iv, buffer)?;
381405
Ok(None)
382406
}
383407
#[cfg(feature = "tdes")]
384408
Self::TdesCbc => {
385-
self.encryptor::<Tdes>(key, iv)?.encrypt(buffer)?;
409+
self.encrypt_with_block_cipher::<Tdes>(key, iv, buffer)?;
386410
Ok(None)
387411
}
388412
_ => Err(Error::UnsupportedCipher(self)),
389413
}
390414
}
391415

416+
/// Perform decryption using a dynamically selected block cipher mode of operation.
417+
///
418+
/// Note that this does not support any form of padding currently.
419+
///
420+
/// # Errors
421+
/// Returns [`Error::Length`] unless the length of `buffer` is a multiple of the block size.
422+
#[cfg(any(feature = "aes", feature = "tdes"))]
423+
fn encrypt_with_block_cipher<C: BlockCipher>(
424+
self,
425+
key: &[u8],
426+
iv: &[u8],
427+
buffer: &mut [u8],
428+
) -> Result<()> {
429+
let (blocks, remaining) = Block::<C>::slice_as_chunks_mut(buffer);
430+
431+
if !remaining.is_empty() {
432+
return Err(Error::Length);
433+
}
434+
435+
self.encryptor::<C>(key, iv)?.encrypt_blocks(blocks);
436+
Ok(())
437+
}
438+
392439
/// Get a stateful [`block_cipher::Encryptor`] for the given key and IV.
393440
///
394441
/// Only applicable to unauthenticated modes (e.g. AES-CBC, AES-CTR). Not usable with

0 commit comments

Comments
 (0)