Skip to content
Merged
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: 3 additions & 1 deletion ssh-cipher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ zeroize = [
"poly1305?/zeroize"
]

[lints]
workspace = true

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]
7 changes: 0 additions & 7 deletions ssh-cipher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@ ciphers.
Built on the pure Rust cryptography implementations maintained by the
[RustCrypto] organization.

## Minimum Supported Rust Version

This crate requires **Rust 1.85** at a minimum.

We may change the MSRV in the future, but it will be accompanied by a minor
version bump.

## License

Licensed under either of:
Expand Down
20 changes: 13 additions & 7 deletions ssh-cipher/src/chacha20poly1305.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use aead::{
inout::InOutBuf,
};
use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
use core::fmt::{self, Debug};
use ctutils::CtEq;
use poly1305::{Poly1305, universal_hash::UniversalHash};

Expand Down Expand Up @@ -59,14 +60,13 @@ impl AeadCore for ChaCha20Poly1305 {
}

impl AeadInOut for ChaCha20Poly1305 {
// Required methods
fn encrypt_inout_detached(
&self,
nonce: &ChaChaNonce,
associated_data: &[u8],
buffer: InOutBuf<'_, '_, u8>,
) -> Result<Tag> {
Cipher::new(&self.key, nonce).encrypt(associated_data, buffer)
Cipher::new(&self.key, *nonce).encrypt(associated_data, buffer)
}

fn decrypt_inout_detached(
Expand All @@ -76,7 +76,13 @@ impl AeadInOut for ChaCha20Poly1305 {
buffer: InOutBuf<'_, '_, u8>,
tag: &Tag,
) -> Result<()> {
Cipher::new(&self.key, nonce).decrypt(associated_data, buffer, tag)
Cipher::new(&self.key, *nonce).decrypt(associated_data, buffer, tag)
}
}

impl Debug for ChaCha20Poly1305 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ChaCha20Poly1305").finish_non_exhaustive()
}
}

Expand All @@ -98,8 +104,8 @@ struct Cipher {

impl Cipher {
/// Create a new cipher instance.
pub fn new(key: &ChaChaKey, nonce: &ChaChaNonce) -> Self {
let mut cipher = ChaCha20::new(key, nonce);
fn new(key: &ChaChaKey, nonce: ChaChaNonce) -> Self {
let mut cipher = ChaCha20::new(key, &nonce);
let mut poly1305_key = poly1305::Key::default();
cipher.apply_keystream(&mut poly1305_key);

Expand All @@ -113,15 +119,15 @@ impl Cipher {

/// Encrypt the provided `buffer` in-place, returning the Poly1305 authentication tag.
#[inline]
pub fn encrypt(mut self, aad: &[u8], mut buffer: InOutBuf<'_, '_, u8>) -> Result<Tag> {
fn encrypt(mut self, aad: &[u8], mut buffer: InOutBuf<'_, '_, u8>) -> Result<Tag> {
self.cipher.apply_keystream_inout(buffer.reborrow());
compute_mac(self.mac, aad, buffer.get_out())
}

/// Decrypt the provided `buffer` in-place, verifying it against the provided Poly1305
/// authentication `tag`.
#[inline]
pub fn decrypt(mut self, aad: &[u8], buffer: InOutBuf<'_, '_, u8>, tag: &Tag) -> Result<()> {
fn decrypt(mut self, aad: &[u8], buffer: InOutBuf<'_, '_, u8>, tag: &Tag) -> Result<()> {
let expected_tag = compute_mac(self.mac, aad, buffer.get_in())?;

if expected_tag.ct_eq(tag).into() {
Expand Down
26 changes: 20 additions & 6 deletions ssh-cipher/src/decryptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@

use crate::{Cipher, Error, Result};
use cipher::KeyIvInit;
use core::fmt::{self, Debug};

#[cfg(feature = "aes-ctr")]
use crate::{Ctr128BE, encryptor::ctr_encrypt as ctr_decrypt};

#[cfg(feature = "tdes")]
use des::TdesEde3;

#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
use aes::{Aes128, Aes192, Aes256};

#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
use cipher::{
Block,
block::{BlockCipherDecrypt, BlockModeDecrypt},
};
#[cfg(feature = "tdes")]
use des::TdesEde3;

/// Stateful decryptor object for unauthenticated SSH symmetric ciphers.
///
Expand Down Expand Up @@ -46,7 +44,13 @@ enum Inner {
}

impl Decryptor {
/// Create a new decryptor object with the given [`Cipher`], key, and IV.
/// Create a new decryptor object with the given [`Cipher`], `key`, and `iv` (i.e.
/// initialization vector).
///
/// # Errors
/// - Returns [`Error::Length`] if `key` or `iv` are the wrong length for the given `cipher`.
/// - Returns [`Error::UnsupportedCipher`] if support for the given `cipher` is not enabled
/// in the crate features.
pub fn new(cipher: Cipher, key: &[u8], iv: &[u8]) -> Result<Self> {
cipher.check_key_and_iv(key, iv)?;

Expand All @@ -73,6 +77,7 @@ impl Decryptor {
}

/// Get the cipher for this decryptor.
#[must_use]
pub fn cipher(&self) -> Cipher {
match &self.inner {
#[cfg(feature = "aes-cbc")]
Expand All @@ -94,6 +99,7 @@ impl Decryptor {

/// Decrypt the given buffer in place.
///
/// # Errors
/// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's
/// block size.
pub fn decrypt(&mut self, buffer: &mut [u8]) -> Result<()> {
Expand All @@ -120,6 +126,14 @@ impl Decryptor {
}
}

impl Debug for Decryptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Decryptor")
.field("cipher", &self.cipher())
.finish_non_exhaustive()
}
}

/// CBC mode decryption helper which assumes the input is unpadded and block-aligned.
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
fn cbc_decrypt<C>(decryptor: &mut cbc::Decryptor<C>, buffer: &mut [u8]) -> Result<()>
Expand Down
34 changes: 24 additions & 10 deletions ssh-cipher/src/encryptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@

use crate::{Cipher, Error, Result};
use cipher::{Block, BlockCipherEncrypt, KeyIvInit};
use core::fmt::{self, Debug};

#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
use aes::{Aes128, Aes192, Aes256};
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
use cipher::block::BlockModeEncrypt;
#[cfg(feature = "tdes")]
use des::TdesEde3;
#[cfg(feature = "aes-ctr")]
use {
crate::Ctr128BE,
cipher::{BlockSizeUser, StreamCipherCore, array::sizes::U16},
};

#[cfg(feature = "tdes")]
use des::TdesEde3;

#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
use aes::{Aes128, Aes192, Aes256};

#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
use cipher::block::BlockModeEncrypt;

/// Stateful encryptor object for unauthenticated SSH symmetric ciphers.
///
/// Note that this deliberately does not support AEAD modes such as AES-GCM and ChaCha20Poly1305,
Expand Down Expand Up @@ -46,7 +44,13 @@ enum Inner {
}

impl Encryptor {
/// Create a new encryptor object with the given [`Cipher`], key, and IV.
/// Create a new encryptor object with the given [`Cipher`], `key`, and `iv` (i.e.
/// initialization vector).
///
/// # Errors
/// - Returns [`Error::Length`] if `key` or `iv` are the wrong length for the given `cipher`.
/// - Returns [`Error::UnsupportedCipher`] if support for the given `cipher` is not enabled
/// in the crate features.
pub fn new(cipher: Cipher, key: &[u8], iv: &[u8]) -> Result<Self> {
cipher.check_key_and_iv(key, iv)?;

Expand All @@ -73,6 +77,7 @@ impl Encryptor {
}

/// Get the cipher for this encryptor.
#[must_use]
pub fn cipher(&self) -> Cipher {
match &self.inner {
#[cfg(feature = "aes-cbc")]
Expand All @@ -94,6 +99,7 @@ impl Encryptor {

/// Encrypt the given buffer in place.
///
/// # Errors
/// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's
/// block size.
pub fn encrypt(&mut self, buffer: &mut [u8]) -> Result<()> {
Expand All @@ -118,6 +124,14 @@ impl Encryptor {
}
}

impl Debug for Encryptor {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Encryptor")
.field("cipher", &self.cipher())
.finish_non_exhaustive()
}
}

/// CBC mode encryption helper which assumes the input is unpadded and block-aligned.
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
fn cbc_encrypt<C>(encryptor: &mut cbc::Encryptor<C>, buffer: &mut [u8]) -> Result<()>
Expand Down
50 changes: 33 additions & 17 deletions ssh-cipher/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,10 @@
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
)]
#![forbid(unsafe_code)]
#![warn(
clippy::alloc_instead_of_core,
clippy::arithmetic_side_effects,
clippy::mod_module_files,
clippy::panic,
clippy::panic_in_result_fn,
clippy::std_instead_of_alloc,
clippy::std_instead_of_core,
clippy::unwrap_used,
missing_docs,
rust_2018_idioms,
unused_lifetimes,
unused_qualifications
)]

mod error;

Expand Down Expand Up @@ -138,12 +123,25 @@ impl Cipher {
/// Decode cipher algorithm from the given `ciphername`.
///
/// # Supported cipher names
/// - `aes256-ctr`
/// `aes128-cbc`
/// `aes192-cbc`
/// `aes256-cbc`
/// `aes128-ctr`
/// `aes192-ctr`
/// `aes256-ctr`
/// `aes128-gcm@openssh.com`
/// `aes256-gcm@openssh.com`
/// `chacha20-poly1305@openssh.com`
/// `3des-cbc`
///
/// # Errors
/// Returns [`LabelError`] if the provided `ciphername` is unknown.
pub fn new(ciphername: &str) -> core::result::Result<Self, LabelError> {
ciphername.parse()
}

/// Get the string identifier which corresponds to this algorithm.
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::None => "none",
Expand All @@ -161,6 +159,7 @@ impl Cipher {
}

/// Get the key and IV size for this cipher in bytes.
#[must_use]
pub fn key_and_iv_size(self) -> Option<(usize, usize)> {
match self {
Self::None => None,
Expand All @@ -178,6 +177,7 @@ impl Cipher {
}

/// Get the block size for this cipher in bytes.
#[must_use]
pub fn block_size(self) -> usize {
match self {
Self::None | Self::ChaCha20Poly1305 | Self::TDesCbc => 8,
Expand All @@ -195,14 +195,20 @@ impl Cipher {
/// Compute the length of padding necessary to pad the given input to
/// the block size.
#[allow(clippy::arithmetic_side_effects)]
#[must_use]
pub fn padding_len(self, input_size: usize) -> usize {
#[allow(
clippy::integer_division_remainder_used,
reason = "input_size is non-secret"
)]
match input_size % self.block_size() {
0 => 0,
input_rem => self.block_size() - input_rem,
}
}

/// Does this cipher have an authentication tag? (i.e. is it an AEAD mode?)
#[must_use]
pub fn has_tag(self) -> bool {
matches!(
self,
Expand All @@ -211,17 +217,20 @@ impl Cipher {
}

/// Is this cipher `none`?
#[must_use]
pub fn is_none(self) -> bool {
self == Self::None
}

/// Is the cipher anything other than `none`?
#[must_use]
pub fn is_some(self) -> bool {
!self.is_none()
}

/// Decrypt the ciphertext in the `buffer` in-place using this cipher.
///
/// # Errors
/// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's
/// block size.
#[cfg_attr(
Expand Down Expand Up @@ -280,13 +289,17 @@ impl Cipher {
///
/// Only applicable to unauthenticated modes (e.g. AES-CBC, AES-CTR). Not usable with
/// authenticated modes which are inherently one-shot (AES-GCM, ChaCha20Poly1305).
///
/// # Errors
/// Propagates errors from [`Decryptor::new`].
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
pub fn decryptor(self, key: &[u8], iv: &[u8]) -> Result<Decryptor> {
Decryptor::new(self, key, iv)
}

/// Encrypt the ciphertext in the `buffer` in-place using this cipher.
///
/// # Errors
/// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's
/// block size.
#[cfg_attr(
Expand Down Expand Up @@ -339,6 +352,9 @@ impl Cipher {
///
/// Only applicable to unauthenticated modes (e.g. AES-CBC, AES-CTR). Not usable with
/// authenticated modes which are inherently one-shot (AES-GCM, ChaCha20Poly1305).
///
/// # Errors
/// Propagates errors from [`Encryptor::new`].
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
pub fn encryptor(self, key: &[u8], iv: &[u8]) -> Result<Encryptor> {
Encryptor::new(self, key, iv)
Expand Down
3 changes: 2 additions & 1 deletion ssh-cipher/tests/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/// Integration tests for `ssh-cipher`.
//! Integration tests for `ssh-cipher`.
// TODO(tarcieri): test vectors for each supported cipher

use ssh_cipher::Cipher;

#[test]
Expand Down
Loading