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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ target
Cargo.lock
.cargo-ok
ca-test*
/.vscode
21 changes: 11 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,31 @@ members = ["russh", "russh-config", "cryptovec", "pageant", "russh-util"]
resolver = "2"

[workspace.dependencies]
aes = "0.8"
aes = "0.9"
async-trait = "0.1.50"
base16ct = "1"
byteorder = "1.4"
bytes = "1.7"
digest = "0.10"
digest = "0.11.0-rc.5"
delegate = "0.13"
env_logger = "0.11"
futures = "0.3"
hmac = "0.12"
hmac = "0.13"
log = "0.4.11"
rand = { version = "0.10", features = ["thread_rng"] }
sha1 = { version = "0.10.5", features = ["oid"] }
sha2 = { version = "0.10.6", features = ["oid"] }
sha1 = { version = "0.11", features = ["oid"] }
sha2 = { version = "0.11", features = ["oid"] }
signature = "3.0.0-rc.10"
ssh-encoding = { version = "0.2", features = ["bytes"] }
ssh-key = { version = "=0.6.18", features = [
ssh-encoding = { version = "=0.3.0-rc.9", features = ["bytes"] }
ssh-key = { version = "=0.7.0-rc.10", features = [
"ed25519",
"p256",
"p384",
"p521",
"encryption",
"ppk",
"hazmat-allow-insecure-rsa-keys",
], package = "internal-russh-forked-ssh-key" }
"sha1",
] }
thiserror = "2.0.18"
tokio = { version = "1.17.0" }
tokio-stream = { version = "0.1.3", features = ["net", "sync"] }
tokio-stream = { version = "0.1.3", features = ["net", "sync"] }
12 changes: 1 addition & 11 deletions cryptovec/src/ssh.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
use ssh_encoding::{Reader, Result, Writer};
use ssh_encoding::{Result, Writer};

use crate::CryptoVec;

impl Reader for CryptoVec {
fn read<'o>(&mut self, out: &'o mut [u8]) -> Result<&'o [u8]> {
(&self[..]).read(out)
}

fn remaining_len(&self) -> usize {
self.len()
}
}

impl Writer for CryptoVec {
fn write(&mut self, bytes: &[u8]) -> Result<()> {
self.extend(bytes);
Expand Down
2 changes: 1 addition & 1 deletion pageant/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ rust-version = "1.85"
thiserror.workspace = true

[target.'cfg(windows)'.dependencies]
base16ct = { workspace = true, features = ["alloc"] }
futures.workspace = true
rand.workspace = true
byteorder.workspace = true
Expand Down Expand Up @@ -45,4 +46,3 @@ namedpipes = [
"windows/Win32_Security_Authentication_Identity",
"windows/Win32_Security_Cryptography",
]

3 changes: 2 additions & 1 deletion pageant/src/namedpipes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;

use base16ct::lower;
use delegate::delegate;
use log::debug;
use sha2::{Digest, Sha256};
Expand Down Expand Up @@ -109,7 +110,7 @@ impl PageantStream {
let mut hasher = Sha256::new();
hasher.update((cryptdata.len() as u32).to_be_bytes());
hasher.update(&cryptdata);
Ok(format!("{:x}", hasher.finalize()))
Ok(lower::encode_string(&hasher.finalize()))
}
}

Expand Down
24 changes: 6 additions & 18 deletions russh/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,36 +23,28 @@ des = ["dep:des"]
# Danger: DSA algorithm is insecure.
dsa = ["ssh-key/dsa"]
ring = ["dep:ring"] # Alternative crypto backend.
rsa = ["dep:rsa", "dep:pkcs1", "ssh-key/rsa", "ssh-key/rsa-sha1"]
rsa = ["dep:rsa", "dep:pkcs1", "ssh-key/rsa"]
serde = ["ssh-key/serde"]
_bench = ["dep:criterion"]

[dependencies]
# Compatibility floor pins for cargo-minimal-versions. These are otherwise
# transitive members of the RustCrypto prerelease stack; some are renamed to
# coexist with older direct dependency lines.
aead_0_6 = { package = "aead", version = "=0.6.0-rc.10" }
aes.workspace = true
aes_0_9 = { package = "aes", version = "0.9.0" }
aes_gcm_0_11 = { package = "aes-gcm", version = "=0.11.0-rc.3" }
async-trait = { workspace = true, optional = true }
aws-lc-rs = { version = "1.16.2", optional = true }
bitflags = "2.0"
block-padding = { version = "0.3", features = ["std"] }
block-padding = { version = "0.4" }
byteorder.workspace = true
bytes.workspace = true
cbc = { version = "0.1" }
cbc_0_2 = { package = "cbc", version = "0.2.0" }
cbc = { version = "0.2" }
cipher = "0.5.1" # only pinned due to a cargo-minimal-versions failure in 0.5.0
ctr = "0.9.2"
ctr_0_10 = { package = "ctr", version = "0.10.0" }
ctr = "0.10"
curve25519-dalek = "=5.0.0-pre.6"
crypto-bigint = { version = "0.7.3", features = ["alloc"] }
data-encoding = "2.3"
delegate.workspace = true
digest.workspace = true
der = "0.8"
des = { version = "0.8.1", optional = true }
des = { version = "0.9", optional = true }
ecdsa = "=0.17.0-rc.18"
ed25519-dalek = { version = "=3.0.0-pre.7", features = ["alloc", "rand_core", "pkcs8"] }
elliptic-curve = { version = "=0.14.0-rc.32", features = ["ecdh"] }
Expand All @@ -65,7 +57,6 @@ ghash = "0.6.0" # only pinned due to a cargo-minimal-versions failure in 0.6.0-r
hex-literal = "1"
hkdf = "0.13.0"
hmac.workspace = true
hmac_0_13 = { package = "hmac", version = "0.13.0" }
inout = { version = "0.1", features = ["std"] }
keccak = "0.2.0"
log.workspace = true
Expand All @@ -81,8 +72,7 @@ num_bigint_0_4 = { package = "num-bigint", version = "0.4.6" }
p256 = { version = "=0.14.0-rc.9", features = ["ecdh"] }
p384 = { version = "=0.14.0-rc.9", features = ["ecdh"] }
p521 = { version = "=0.14.0-rc.9", features = ["ecdh"] }
pbkdf2 = "0.12"
pbkdf2_0_13 = { package = "pbkdf2", version = "0.13.0" }
pbkdf2 = "0.13"
pkcs1 = { version = "=0.8.0-rc.4", optional = true }
pkcs5 = "0.8"
pkcs8 = { version = "0.11", features = ["encryption", "std"] }
Expand All @@ -99,9 +89,7 @@ salsa20 = "0.11.0"
scrypt = "0.12.0"
sec1 = { version = "0.8", features = ["der"] }
sha1.workspace = true
sha1_0_11 = { package = "sha1", version = "0.11.0" }
sha2.workspace = true
sha2_0_11 = { package = "sha2", version = "0.11.0" }
sha3 = "0.11.0"
signature.workspace = true
spki = "0.8"
Expand Down
1 change: 0 additions & 1 deletion russh/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ impl From<&NameList> for MethodSet {
fn from(value: &NameList) -> Self {
Self(
value
.0
.iter()
.filter_map(|x| MethodKind::from_str(x).ok())
.collect(),
Expand Down
100 changes: 80 additions & 20 deletions russh/src/cipher/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,91 @@
use std::convert::TryInto;
use std::marker::PhantomData;

use aes::cipher::{IvSizeUser, KeyIvInit, KeySizeUser, StreamCipher};
use aes::cipher::{
InOutBuf, Iv, IvSizeUser, Key, KeyIvInit, KeySizeUser, StreamCipher, StreamCipherError,
StreamCipherSeek,
};
#[allow(deprecated)]
use digest::generic_array::GenericArray as GenericArray_0_14;
use rand_core::Rng;

use super::super::Error;
use super::PACKET_LENGTH_LEN;
use crate::keys::key::safe_rng;
use crate::mac::{Mac, MacAlgorithm};

// Allow deprecated generic-array 0.14 usage until RustCrypto crates (cipher, digest, etc.)
// upgrade to generic-array 1.x. Remove this when dependencies no longer use 0.14.
#[allow(deprecated)]
fn new_cipher_from_slices<C: KeyIvInit>(k: &[u8], n: &[u8]) -> C {
#[allow(clippy::expect_used)]
C::new(
GenericArray_0_14::from_slice(k),
GenericArray_0_14::from_slice(n),
<&Key<C>>::try_from(k).expect("key length matches"),
<&Iv<C>>::try_from(n).expect("iv length matches"),
)
}

/// Cloneable wrapper for `Ctr128BE<>`
pub struct CtrWrapper<C>
where
C: KeyIvInit,
{
key: Key<C>,
initial_iv: Iv<C>,
pos: u64,
}

impl<C: KeyIvInit> Clone for CtrWrapper<C> {
fn clone(&self) -> Self {
Self {
key: self.key.clone(),
initial_iv: self.initial_iv.clone(),
pos: self.pos,
}
}
}

impl<C: KeyIvInit> KeySizeUser for CtrWrapper<C> {
type KeySize = <C as KeySizeUser>::KeySize;
}

impl<C: KeyIvInit> IvSizeUser for CtrWrapper<C> {
type IvSize = <C as IvSizeUser>::IvSize;
}

impl<C: KeyIvInit> KeyIvInit for CtrWrapper<C> {
fn new(key: &Key<Self>, iv: &Iv<Self>) -> Self {
Self {
key: key.clone(),
initial_iv: iv.clone(),
pos: 0,
}
}
}

impl<C: KeyIvInit + StreamCipher + StreamCipherSeek> StreamCipher for CtrWrapper<C> {
fn check_remaining(&self, _data_len: usize) -> Result<(), StreamCipherError> {
Ok(())
}

fn unchecked_apply_keystream_inout(&mut self, buf: InOutBuf<'_, '_, u8>) {
let mut cipher = C::new(&self.key, &self.initial_iv);
cipher.seek(self.pos);
cipher.unchecked_apply_keystream_inout(buf);
self.pos = cipher.current_pos();
}

fn unchecked_write_keystream(&mut self, buf: &mut [u8]) {
let mut cipher = C::new(&self.key, &self.initial_iv);
cipher.seek(self.pos);
cipher.unchecked_write_keystream(buf);
self.pos = cipher.current_pos();
}
}

pub struct SshBlockCipher<C: BlockStreamCipher + PacketLengthProbe + KeySizeUser + IvSizeUser>(
pub PhantomData<C>,
);

impl<C: BlockStreamCipher + PacketLengthProbe + KeySizeUser + IvSizeUser + KeyIvInit + Send + 'static>
super::Cipher for SshBlockCipher<C>
impl<
C: BlockStreamCipher + PacketLengthProbe + KeySizeUser + IvSizeUser + KeyIvInit + Send + 'static,
> super::Cipher for SshBlockCipher<C>
{
fn key_len(&self) -> usize {
C::key_size()
Expand Down Expand Up @@ -237,15 +296,15 @@ impl<T: StreamCipher + Clone> PacketLengthProbe for T {

#[cfg(test)]
mod tests {
use aes::Aes128;
use aes::cipher::KeyIvInit;
use aes::cipher::StreamCipher;
use aes::Aes128;
use aes::cipher::{IvSizeUser, KeySizeUser};
use ctr::Ctr128BE;
use digest::typenum::U16;
use tokio::io::AsyncWriteExt;

use super::{BlockStreamCipher, OpeningKey, PacketLengthProbe};
use super::{BlockStreamCipher, CtrWrapper, OpeningKey, PacketLengthProbe};
use crate::mac::MacAlgorithm;
use crate::sshbuffer::SSHBuffer;

Expand All @@ -255,11 +314,11 @@ mod tests {
let key = fixture_bytes::<16>(7);
let iv = fixture_bytes::<16>(3);

let mut encryptor = Ctr128BE::<Aes128>::new(&key.into(), &iv.into());
let mut encryptor = CtrWrapper::<Ctr128BE<Aes128>>::new(&key.into(), &iv.into());
let mut ciphertext = plaintext;
encryptor.apply_keystream(&mut ciphertext);

let cipher = Ctr128BE::<Aes128>::new(&key.into(), &iv.into());
let cipher = CtrWrapper::<Ctr128BE<Aes128>>::new(&key.into(), &iv.into());
let mut probed_block = ciphertext;
cipher.decrypt_packet_length_block(&mut probed_block);
assert_eq!(probed_block, plaintext);
Expand All @@ -281,13 +340,14 @@ mod tests {
};
let mut opening = opening;
let mut buffer = SSHBuffer::new();
let bytes_read = runtime.block_on(async {
let (mut writer, mut reader) = tokio::io::duplex(64);
writer.write_all(&[0; 17]).await?;
drop(writer);
crate::cipher::read(&mut reader, &mut buffer, &mut opening).await
})
.map_err(std::io::Error::other)?;
let bytes_read = runtime
.block_on(async {
let (mut writer, mut reader) = tokio::io::duplex(64);
writer.write_all(&[0; 17]).await?;
drop(writer);
crate::cipher::read(&mut reader, &mut buffer, &mut opening).await
})
.map_err(std::io::Error::other)?;

assert_eq!(bytes_read, 16);
Ok(())
Expand Down
Loading
Loading