Skip to content

Commit

Permalink
add zlib, [email protected] support
Browse files Browse the repository at this point in the history
  • Loading branch information
HsuJv committed Sep 20, 2023
1 parent e765d6e commit 71b98c0
Show file tree
Hide file tree
Showing 17 changed files with 284 additions and 10 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "ssh-rs"
version = "0.4.0"
version = "0.4.1"
edition = "2021"
authors = [
"Gao Xiang Kang <[email protected]>",
Expand All @@ -24,6 +24,7 @@ deprecated-rsa-sha1 = ["dep:sha1"]
deprecated-dh-group1-sha1 = ["dep:sha1"]
deprecated-aes-cbc = ["dep:cbc", "dep:cipher"]
deprecated-des-cbc = ["dep:cbc", "dep:cipher", "dep:des"]
deprecated-zlib = []
scp = ["dep:filetime"]

[lib]
Expand Down Expand Up @@ -57,6 +58,9 @@ ssh-key = { version = "0.6", features = ["rsa", "ed25519", "alloc"]}
signature = "2.1"
ring = "0.16"

## compression
flate2 = "^1.0"

## utils
filetime = { version = "0.2", optional = true }

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ match ssh::create_session()
### 5. Compression algorithms

* `none`
* `[email protected]`
* `zlib` (behind feature "zlib")

---

Expand Down
1 change: 1 addition & 0 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ ssh::create_session().timeout(Some(std::time::Duration::from_secs(5)));
#### 5. 压缩算法

* `none`
* `zlib` (behind feature "zlib")

---

Expand Down
3 changes: 3 additions & 0 deletions changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
v0.4.1 (2023-09-20)
1. Add zlib, [email protected] support

v0.4.0 (2023-09-16)
1. remove chinese comments
2. add RFC links
Expand Down
43 changes: 43 additions & 0 deletions src/algorithm/compression/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use super::Compress;
use crate::SshResult;

mod zlib;
/// <https://www.rfc-editor.org/rfc/rfc4253#section-6.2>
pub(crate) trait Compression: Send + Sync {
fn new() -> Self
where
Self: Sized;
// The "[email protected]" method operates identically to the "zlib"
// method described in [RFC4252] except that packet compression does not
// start until the server sends a SSH_MSG_USERAUTH_SUCCESS packet
// so
// fn start();
fn compress(&mut self, buf: &[u8]) -> SshResult<Vec<u8>>;
fn decompress(&mut self, buf: &[u8]) -> SshResult<Vec<u8>>;
}

pub(crate) fn from(comp: &Compress) -> Box<dyn Compression> {
match comp {
Compress::None => Box::new(CompressNone::new()),
#[cfg(feature = "deprecated-zlib")]
Compress::Zlib => Box::new(zlib::CompressZlib::new()),
Compress::ZlibOpenSsh => Box::new(zlib::CompressZlib::new()),
}
}

#[derive(Default)]
pub(crate) struct CompressNone {}

impl Compression for CompressNone {
fn new() -> Self {
Self {}
}

fn compress(&mut self, buf: &[u8]) -> SshResult<Vec<u8>> {
Ok(buf.to_vec())
}

fn decompress(&mut self, buf: &[u8]) -> SshResult<Vec<u8>> {
Ok(buf.to_vec())
}
}
172 changes: 172 additions & 0 deletions src/algorithm/compression/zlib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// pub struct Zlib<'a> {
// decompressor: flate2::Decompress,
// compressor: flate2::Compress,
// input: &'a [u8],
// }

// impl<'a> Zlib<'a> {
// pub fn new(decompressor: flate2::Decompress, input: &'a [u8]) -> Zlib<'a> {
// Zlib {
// decompressor,
// input,
// }
// }

// pub fn into_inner(self) -> Result<flate2::Decompress> {
// if self.input.is_empty() {
// Ok(self.decompressor)
// } else {
// Err(std::io::Error::new(
// std::io::ErrorKind::InvalidData,
// "leftover zlib byte data",
// ))
// }
// }

// pub fn read_u8(&mut self) -> std::io::Result<u8> {
// let mut buf = [0; 1];
// self.read_exact(&mut buf)?;
// Ok(buf[0])
// }
// }

// impl<'a> Read for Zlib<'a> {
// fn read(&mut self, output: &mut [u8]) -> std::io::Result<usize> {
// let in_before = self.decompressor.total_in();
// let out_before = self.decompressor.total_out();
// let result =
// self.decompressor
// .decompress(self.input, output, flate2::FlushDecompress::None);
// let consumed = (self.decompressor.total_in() - in_before) as usize;
// let produced = (self.decompressor.total_out() - out_before) as usize;

// self.input = &self.input[consumed..];
// match result {
// Ok(flate2::Status::Ok) => Ok(produced),
// Ok(flate2::Status::BufError) => Ok(0),
// Err(error) => Err(std::io::Error::new(std::io::ErrorKind::InvalidData, error)),
// Ok(flate2::Status::StreamEnd) => Err(std::io::Error::new(
// std::io::ErrorKind::InvalidData,
// "zlib stream end",
// )),
// }
// }
// }
use flate2;

use crate::SshError;

use super::Compression;

/// The "zlib" compression is described in [RFC1950] and in [RFC1951].
/// The compression context is initialized after each key exchange, and
/// is passed from one packet to the next, with only a partial flush
/// being performed at the end of each packet. A partial flush means
/// that the current compressed block is ended and all data will be
/// output. If the current block is not a stored block, one or more
/// empty blocks are added after the current block to ensure that there
/// are at least 8 bits, counting from the start of the end-of-block code
/// of the current block to the end of the packet payload.
///
/// <https://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt>
/// The "[email protected]" method operates identically to the "zlib"
/// method described in [RFC4252] except that packet compression does not
/// start until the server sends a SSH_MSG_USERAUTH_SUCCESS packet,
/// replacing the "zlib" method's start of compression when the server
/// sends SSH_MSG_NEWKEYS.
pub(super) struct CompressZlib {
decompressor: flate2::Decompress,
compressor: flate2::Compress,
}

impl Compression for CompressZlib {
fn new() -> Self
where
Self: Sized,
{
Self {
decompressor: flate2::Decompress::new(true),
compressor: flate2::Compress::new(flate2::Compression::fast(), true),
}
}

fn decompress(&mut self, buf: &[u8]) -> crate::SshResult<Vec<u8>> {
let mut buf_in = buf;
let mut buf_once = [0; 4096];
let mut buf_out = vec![];
loop {
let in_before = self.decompressor.total_in();
let out_before = self.decompressor.total_out();

let result =
self.decompressor
.decompress(buf_in, &mut buf_once, flate2::FlushDecompress::Sync);

let consumed = (self.decompressor.total_in() - in_before) as usize;
let produced = (self.decompressor.total_out() - out_before) as usize;

match result {
Ok(flate2::Status::Ok) => {
buf_in = &buf_in[consumed..];
buf_out.extend(&buf_once[..produced]);
}
Ok(flate2::Status::StreamEnd) => {
return Err(SshError::CompressionError(
"Stream ends during the decompress".to_owned(),
));
}
Ok(flate2::Status::BufError) => {
break;
}
Err(e) => return Err(SshError::CompressionError(e.to_string())),
}
}

Ok(buf_out)
}

fn compress(&mut self, buf: &[u8]) -> crate::SshResult<Vec<u8>> {
let mut buf_in = buf;
let mut buf_once = [0; 4096];
let mut buf_out = vec![];
loop {
let in_before = self.compressor.total_in();
let out_before = self.compressor.total_out();

let result =
self.compressor
.compress(buf_in, &mut buf_once, flate2::FlushCompress::Partial);

let consumed = (self.compressor.total_in() - in_before) as usize;
let produced = (self.compressor.total_out() - out_before) as usize;

// tracing::info!(consumed);
// tracing::info!(produced);

// means an empty compress
// 2 bytes ZLIB header at the start of the stream
// 4 bytes CRC checksum at the end of the stream
if produced == 6 {
break;
}

match result {
Ok(flate2::Status::Ok) => {
buf_in = &buf_in[consumed..];
buf_out.extend(&buf_once[..produced]);
}
Ok(flate2::Status::StreamEnd) => {
return Err(SshError::CompressionError(
"Stream ends during the compress".to_owned(),
));
}
Ok(flate2::Status::BufError) => {
break;
}
Err(e) => return Err(SshError::CompressionError(e.to_string())),
}
}

Ok(buf_out)
}
}
6 changes: 6 additions & 0 deletions src/algorithm/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub(crate) mod compression;
pub(crate) mod encryption;
pub(crate) mod hash;
pub(crate) mod key_exchange;
Expand Down Expand Up @@ -79,6 +80,11 @@ pub enum Mac {
pub enum Compress {
#[strum(serialize = "none")]
None,
#[cfg(feature = "deprecated-zlib")]
#[strum(serialize = "zlib")]
Zlib,
#[strum(serialize = "[email protected]")]
ZlibOpenSsh,
}

#[derive(Default)]
Expand Down
11 changes: 10 additions & 1 deletion src/client/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::config::algorithm::AlgList;
use crate::{
algorithm::compression::{CompressNone, Compression},
config::algorithm::AlgList,
};
use crate::{algorithm::encryption::Encryption, config::Config};
use crate::{algorithm::encryption::EncryptionNone, model::Sequence};
use std::time::Duration;
Expand All @@ -9,6 +12,7 @@ pub(crate) struct Client {
pub(super) config: Config,
pub(super) negotiated: AlgList,
pub(super) encryptor: Box<dyn Encryption>,
pub(super) compressor: Box<dyn Compression>,
pub(super) session_id: Vec<u8>,
}

Expand All @@ -17,6 +21,7 @@ impl Client {
Self {
config,
encryptor: Box::<EncryptionNone>::default(),
compressor: Box::<CompressNone>::default(),
negotiated: AlgList::new(),
session_id: vec![],
sequence: Sequence::new(),
Expand All @@ -27,6 +32,10 @@ impl Client {
self.encryptor.as_mut()
}

pub fn get_compressor(&mut self) -> &mut dyn Compression {
self.compressor.as_mut()
}

pub fn get_seq(&mut self) -> &mut Sequence {
&mut self.sequence
}
Expand Down
5 changes: 4 additions & 1 deletion src/client/client_auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::io::{Read, Write};
use tracing::*;

use crate::{
algorithm::Digest,
algorithm::{compression, Digest},
constant::{ssh_connection_code, ssh_str, ssh_transport_code, ssh_user_auth_code},
error::{SshError, SshResult},
model::{Data, Packet, SecPacket},
Expand Down Expand Up @@ -58,6 +58,9 @@ impl Client {
}
ssh_user_auth_code::SUCCESS => {
info!("user auth successful.");
// <https://www.openssh.com/txt/draft-miller-secsh-compression-delayed-00.txt>
// Now we need turn on the compressor if any
self.compressor = compression::from(&self.negotiated.c_compress[0]);
return Ok(());
}
ssh_connection_code::GLOBAL_REQUEST => {
Expand Down
11 changes: 11 additions & 0 deletions src/client/client_kex.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#[cfg(feature = "deprecated-zlib")]
use crate::algorithm::{compression, Compress};
use crate::{
algorithm::{
encryption,
Expand Down Expand Up @@ -78,6 +80,15 @@ impl Client {
self.session_id = session_id;
self.negotiated = negotiated;
self.encryptor = encryption;

#[cfg(feature = "deprecated-zlib")]
{
if let Compress::Zlib = self.negotiated.c_compress[0] {
let comp = compression::from(&Compress::Zlib);
self.compressor = comp;
}
}

digest.key_exchange = Some(key_exchange);

info!("key negotiation successful.");
Expand Down
4 changes: 2 additions & 2 deletions src/config/algorithm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ impl AlgList {
.into(),
c_mac: vec![Mac::HmacSha2_256, Mac::HmacSha2_512, Mac::HmacSha1].into(),
s_mac: vec![Mac::HmacSha2_256, Mac::HmacSha2_512, Mac::HmacSha1].into(),
c_compress: vec![Compress::None].into(),
s_compress: vec![Compress::None].into(),
c_compress: vec![Compress::None, Compress::ZlibOpenSsh].into(),
s_compress: vec![Compress::None, Compress::ZlibOpenSsh].into(),
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/constant.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/// The client version
pub(crate) const CLIENT_VERSION: &str = "SSH-2.0-SSH_RS-0.4.0";
pub(crate) const CLIENT_VERSION: &str = "SSH-2.0-SSH_RS-0.4.1";
pub(crate) const SSH_MAGIC: &[u8] = b"SSH-";

/// The constant strings that used for ssh communication
Expand Down
2 changes: 2 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ pub enum SshError {
DataFormatError(#[from] std::string::FromUtf8Error),
#[error("Encryption error: {0}")]
EncryptionError(String),
#[error("Compression error: {0}")]
CompressionError(String),
#[cfg(feature = "scp")]
#[error(transparent)]
SystemTimeError(#[from] std::time::SystemTimeError),
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Dependencies
//! ```toml
//! ssh-rs = "0.4.0"
//! ssh-rs = "0.4.1"
//! ```
//!
//!Rust implementation of ssh2.0 client.
Expand Down
Loading

0 comments on commit 71b98c0

Please sign in to comment.