Skip to content

Commit b857dda

Browse files
authored
ssh-cipher: enable and fix workspace-level lints (#517)
Applies the workspace-level config added in #509 to this crate and fixes any failures.
1 parent 7334d70 commit b857dda

7 files changed

Lines changed: 95 additions & 49 deletions

File tree

ssh-cipher/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ zeroize = [
5252
"poly1305?/zeroize"
5353
]
5454

55+
[lints]
56+
workspace = true
57+
5558
[package.metadata.docs.rs]
5659
all-features = true
57-
rustdoc-args = ["--cfg", "docsrs"]

ssh-cipher/README.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,6 @@ ciphers.
1919
Built on the pure Rust cryptography implementations maintained by the
2020
[RustCrypto] organization.
2121

22-
## Minimum Supported Rust Version
23-
24-
This crate requires **Rust 1.85** at a minimum.
25-
26-
We may change the MSRV in the future, but it will be accompanied by a minor
27-
version bump.
28-
2922
## License
3023

3124
Licensed under either of:

ssh-cipher/src/chacha20poly1305.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use aead::{
99
inout::InOutBuf,
1010
};
1111
use cipher::{KeyIvInit, StreamCipher, StreamCipherSeek};
12+
use core::fmt::{self, Debug};
1213
use ctutils::CtEq;
1314
use poly1305::{Poly1305, universal_hash::UniversalHash};
1415

@@ -59,14 +60,13 @@ impl AeadCore for ChaCha20Poly1305 {
5960
}
6061

6162
impl AeadInOut for ChaCha20Poly1305 {
62-
// Required methods
6363
fn encrypt_inout_detached(
6464
&self,
6565
nonce: &ChaChaNonce,
6666
associated_data: &[u8],
6767
buffer: InOutBuf<'_, '_, u8>,
6868
) -> Result<Tag> {
69-
Cipher::new(&self.key, nonce).encrypt(associated_data, buffer)
69+
Cipher::new(&self.key, *nonce).encrypt(associated_data, buffer)
7070
}
7171

7272
fn decrypt_inout_detached(
@@ -76,7 +76,13 @@ impl AeadInOut for ChaCha20Poly1305 {
7676
buffer: InOutBuf<'_, '_, u8>,
7777
tag: &Tag,
7878
) -> Result<()> {
79-
Cipher::new(&self.key, nonce).decrypt(associated_data, buffer, tag)
79+
Cipher::new(&self.key, *nonce).decrypt(associated_data, buffer, tag)
80+
}
81+
}
82+
83+
impl Debug for ChaCha20Poly1305 {
84+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85+
f.debug_struct("ChaCha20Poly1305").finish_non_exhaustive()
8086
}
8187
}
8288

@@ -98,8 +104,8 @@ struct Cipher {
98104

99105
impl Cipher {
100106
/// Create a new cipher instance.
101-
pub fn new(key: &ChaChaKey, nonce: &ChaChaNonce) -> Self {
102-
let mut cipher = ChaCha20::new(key, nonce);
107+
fn new(key: &ChaChaKey, nonce: ChaChaNonce) -> Self {
108+
let mut cipher = ChaCha20::new(key, &nonce);
103109
let mut poly1305_key = poly1305::Key::default();
104110
cipher.apply_keystream(&mut poly1305_key);
105111

@@ -113,15 +119,15 @@ impl Cipher {
113119

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

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

127133
if expected_tag.ct_eq(tag).into() {

ssh-cipher/src/decryptor.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,19 @@
22
33
use crate::{Cipher, Error, Result};
44
use cipher::KeyIvInit;
5+
use core::fmt::{self, Debug};
56

67
#[cfg(feature = "aes-ctr")]
78
use crate::{Ctr128BE, encryptor::ctr_encrypt as ctr_decrypt};
8-
9-
#[cfg(feature = "tdes")]
10-
use des::TdesEde3;
11-
129
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
1310
use aes::{Aes128, Aes192, Aes256};
14-
1511
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
1612
use cipher::{
1713
Block,
1814
block::{BlockCipherDecrypt, BlockModeDecrypt},
1915
};
16+
#[cfg(feature = "tdes")]
17+
use des::TdesEde3;
2018

2119
/// Stateful decryptor object for unauthenticated SSH symmetric ciphers.
2220
///
@@ -46,7 +44,13 @@ enum Inner {
4644
}
4745

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

@@ -73,6 +77,7 @@ impl Decryptor {
7377
}
7478

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

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

129+
impl Debug for Decryptor {
130+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131+
f.debug_struct("Decryptor")
132+
.field("cipher", &self.cipher())
133+
.finish_non_exhaustive()
134+
}
135+
}
136+
123137
/// CBC mode decryption helper which assumes the input is unpadded and block-aligned.
124138
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
125139
fn cbc_decrypt<C>(decryptor: &mut cbc::Decryptor<C>, buffer: &mut [u8]) -> Result<()>

ssh-cipher/src/encryptor.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,20 @@
22
33
use crate::{Cipher, Error, Result};
44
use cipher::{Block, BlockCipherEncrypt, KeyIvInit};
5+
use core::fmt::{self, Debug};
56

7+
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
8+
use aes::{Aes128, Aes192, Aes256};
9+
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
10+
use cipher::block::BlockModeEncrypt;
11+
#[cfg(feature = "tdes")]
12+
use des::TdesEde3;
613
#[cfg(feature = "aes-ctr")]
714
use {
815
crate::Ctr128BE,
916
cipher::{BlockSizeUser, StreamCipherCore, array::sizes::U16},
1017
};
1118

12-
#[cfg(feature = "tdes")]
13-
use des::TdesEde3;
14-
15-
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr"))]
16-
use aes::{Aes128, Aes192, Aes256};
17-
18-
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
19-
use cipher::block::BlockModeEncrypt;
20-
2119
/// Stateful encryptor object for unauthenticated SSH symmetric ciphers.
2220
///
2321
/// Note that this deliberately does not support AEAD modes such as AES-GCM and ChaCha20Poly1305,
@@ -46,7 +44,13 @@ enum Inner {
4644
}
4745

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

@@ -73,6 +77,7 @@ impl Encryptor {
7377
}
7478

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

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

127+
impl Debug for Encryptor {
128+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129+
f.debug_struct("Encryptor")
130+
.field("cipher", &self.cipher())
131+
.finish_non_exhaustive()
132+
}
133+
}
134+
121135
/// CBC mode encryption helper which assumes the input is unpadded and block-aligned.
122136
#[cfg(any(feature = "aes-cbc", feature = "tdes"))]
123137
fn cbc_encrypt<C>(encryptor: &mut cbc::Encryptor<C>, buffer: &mut [u8]) -> Result<()>

ssh-cipher/src/lib.rs

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,10 @@
11
#![no_std]
2-
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
2+
#![cfg_attr(docsrs, feature(doc_cfg))]
33
#![doc = include_str!("../README.md")]
44
#![doc(
55
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
66
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
77
)]
8-
#![forbid(unsafe_code)]
9-
#![warn(
10-
clippy::alloc_instead_of_core,
11-
clippy::arithmetic_side_effects,
12-
clippy::mod_module_files,
13-
clippy::panic,
14-
clippy::panic_in_result_fn,
15-
clippy::std_instead_of_alloc,
16-
clippy::std_instead_of_core,
17-
clippy::unwrap_used,
18-
missing_docs,
19-
rust_2018_idioms,
20-
unused_lifetimes,
21-
unused_qualifications
22-
)]
238

249
mod error;
2510

@@ -138,12 +123,25 @@ impl Cipher {
138123
/// Decode cipher algorithm from the given `ciphername`.
139124
///
140125
/// # Supported cipher names
141-
/// - `aes256-ctr`
126+
/// `aes128-cbc`
127+
/// `aes192-cbc`
128+
/// `aes256-cbc`
129+
/// `aes128-ctr`
130+
/// `aes192-ctr`
131+
/// `aes256-ctr`
132+
/// `aes128-gcm@openssh.com`
133+
/// `aes256-gcm@openssh.com`
134+
/// `chacha20-poly1305@openssh.com`
135+
/// `3des-cbc`
136+
///
137+
/// # Errors
138+
/// Returns [`LabelError`] if the provided `ciphername` is unknown.
142139
pub fn new(ciphername: &str) -> core::result::Result<Self, LabelError> {
143140
ciphername.parse()
144141
}
145142

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

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

180179
/// Get the block size for this cipher in bytes.
180+
#[must_use]
181181
pub fn block_size(self) -> usize {
182182
match self {
183183
Self::None | Self::ChaCha20Poly1305 | Self::TDesCbc => 8,
@@ -195,14 +195,20 @@ impl Cipher {
195195
/// Compute the length of padding necessary to pad the given input to
196196
/// the block size.
197197
#[allow(clippy::arithmetic_side_effects)]
198+
#[must_use]
198199
pub fn padding_len(self, input_size: usize) -> usize {
200+
#[allow(
201+
clippy::integer_division_remainder_used,
202+
reason = "input_size is non-secret"
203+
)]
199204
match input_size % self.block_size() {
200205
0 => 0,
201206
input_rem => self.block_size() - input_rem,
202207
}
203208
}
204209

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

213219
/// Is this cipher `none`?
220+
#[must_use]
214221
pub fn is_none(self) -> bool {
215222
self == Self::None
216223
}
217224

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

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

288300
/// Encrypt the ciphertext in the `buffer` in-place using this cipher.
289301
///
302+
/// # Errors
290303
/// Returns [`Error::Length`] in the event that `buffer` is not a multiple of the cipher's
291304
/// block size.
292305
#[cfg_attr(
@@ -339,6 +352,9 @@ impl Cipher {
339352
///
340353
/// Only applicable to unauthenticated modes (e.g. AES-CBC, AES-CTR). Not usable with
341354
/// authenticated modes which are inherently one-shot (AES-GCM, ChaCha20Poly1305).
355+
///
356+
/// # Errors
357+
/// Propagates errors from [`Encryptor::new`].
342358
#[cfg(any(feature = "aes-cbc", feature = "aes-ctr", feature = "tdes"))]
343359
pub fn encryptor(self, key: &[u8], iv: &[u8]) -> Result<Encryptor> {
344360
Encryptor::new(self, key, iv)

ssh-cipher/tests/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
/// Integration tests for `ssh-cipher`.
1+
//! Integration tests for `ssh-cipher`.
22
// TODO(tarcieri): test vectors for each supported cipher
3+
34
use ssh_cipher::Cipher;
45

56
#[test]

0 commit comments

Comments
 (0)