Rust实现最简版的SSH通讯
源起
SSH几乎每天都用,也知道是安全的、加密的、可以远程操作shell的。随着对安全的越来越重视,突然有一天有了疑问,这是如何做到的?到底是否真的安全?因此,开启了一段SSH学习之旅。
目标
- 学习/理解/实践SSH协议(原理)
- 验证理解的内容:根据SSH协议的理解,实现一个最简版的ssh通讯机制
SSH协议简要理解
通过搜索阅读网络上一些文章,SSH协议基于TCP协议,主要分为三个部分:
- 密钥协商(包含服务端指纹验证)
- 身份验证
- 数据加密传输
另外ssh协议中还有一些涉及到shell的操作,协议中并没有单独区分,此处单独区分,只是本人觉得SSH做为数据加密传输的协议来讲,这些shell的设计可以单独列出来。当然,我也搜索了一下SSH协议中为什么会有shell的操作,这是因为SSH协议的设计目的就是为了替代telnet之类的明文远程操作工具,因此有shell相关的定义就不可避免,所以为什么SSH是S-SH了
整体代码时序图

russh组件介绍
Server and client SSH asynchronous library, based on tokio/futures
russh组件中客户端和服务端的相关的分别在russh::client和russh::server模块中。
代码中主要用到的模块和结构如下
| 命名空间 | 类型 | 名称 | 描述 |
|---|---|---|---|
| russh:client | struct | Handle | Handle to a session, used to send messages to a client outside of the request/response cycle. |
| russh:client | struct | Session | Actual client session’s state. |
| russh::client | trait | Handler | A client handler. Note that messages can be received from the server at any time during a session. |
| russh::client | function | connect/connect_stream | Connect to a server at the address specified |
| russh::server | struct | Handle | Handle to a session, used to send messages to a client outside of the request/response cycle. |
| russh::server | struct | Session | A connected server session. This type is unique to a client. |
| russh::server | trait | Handler | Server handler. Each client will have their own handler. |
| russh::server | trait | Server | Trait used to create new handlers when clients connect. |
代码实现
服务端
use std::{io::Write, sync::Arc};
use anyhow::Result;
use clap::Parser;
use env_logger;
use log::info;
use rand_core::OsRng;
use russh::{
CryptoVec,
server::{Auth, Config, Handler, Server},
};
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
let start_args = CliStartArgs::parse();
let mut server = ServerImpl {};
let config = Config {
keys: vec![
russh::keys::PrivateKey::random(&mut OsRng, russh::keys::Algorithm::Ed25519).unwrap(),
],
..Default::default()
};
server
.run_on_address(Arc::new(config), ("0.0.0.0", start_args.port))
.await?;
Ok(())
}
#[derive(Debug, Clone)]
struct ServerImpl {}
//对每个请求进行处理
impl Handler for ServerImpl {
type Error = russh::Error;
async fn auth_password(
&mut self,
user: &str,
password: &str,
) -> std::result::Result<russh::server::Auth, Self::Error> {
//todo:待完善
if user.eq("sa") && password.eq("pass01!") {
Ok(Auth::Accept)
} else {
Ok(Auth::Reject {
proceed_with_methods: None, //todo:可以继续完善
partial_success: false,
})
}
}
async fn data(
&mut self,
channel: russh::ChannelId,
data: &[u8],
session: &mut russh::server::Session,
) -> std::result::Result<(), Self::Error> {
let recv_str = String::from_utf8(data.to_vec()).expect("parse to string failed!");
info!("received from client: {}", recv_str);
let mut response_content = Vec::new();
response_content.extend("received from client:".as_bytes());
response_content.extend_from_slice(data);
let mut response = CryptoVec::new();
response.write_all(&response_content)?;
session.data(channel, response)?;
Ok(())
}
async fn channel_open_session(
&mut self,
_channel: russh::Channel<russh::server::Msg>,
_session: &mut russh::server::Session,
) -> std::result::Result<bool, Self::Error> {
Ok(true)
}
}
impl russh::server::Server for ServerImpl {
type Handler = Self;
fn new_client(&mut self, _peer_addr: Option<std::net::SocketAddr>) -> Self::Handler {
self.clone()
}
}
#[derive(Parser)]
struct CliStartArgs {
#[arg(long, short = 'p')]
port: u16,
}
客户端
use anyhow::Result;
use clap::Parser;
use log::{error, info};
use russh::{
ChannelMsg, CryptoVec,
client::{self, Config},
};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
let start_args = CliStartArgs::parse();
let config = Arc::new(Config {
..Default::default()
});
let client = ClientHandler {};
let mut handler = client::connect(config, (start_args.server, start_args.port), client).await?;
let auth_res = handler.authenticate_password("sa", "pass01!").await?;
info!("auth success {}", auth_res.success());
if auth_res.success() {
let mut channel = handler.channel_open_session().await?;
let send_data = "abcdef";
let res = handler.data(channel.id(), CryptoVec::from(send_data)).await;
if let Err(err) = res {
error!("{:?}", err);
}
match channel.wait().await {
Some(msg) => {
if let ChannelMsg::Data { data } = msg {
match String::from_utf8(data.to_vec()) {
Ok(str) => {
info!("received from server: {}", str);
}
Err(_) => {
error!("responsed vec to string failed!");
}
}
}
}
_ => {}
}
} else {
error!("auth failed!");
}
Ok(())
}
struct ClientHandler {}
impl russh::client::Handler for ClientHandler {
type Error = russh::Error;
async fn check_server_key(
&mut self,
_server_public_key: &russh::keys::ssh_key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
#[derive(Parser)]
struct CliStartArgs {
#[arg(long, short = 's')]
server: String,
#[arg(long, short = 'p')]
port: u16,
//暂时只支持用户名和密码,且这两个是hard code的
}
项目依赖
[dependencies]
anyhow = "1.0.98"
clap = { version = "4.5.39", features = ["derive"] }
env_logger = "0.11.8"
log = "0.4.27"
rand = "0.9"
russh = "0.52.1"
tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread","io-std"] }
rand_core = { version = "0.6", features = [ "std"] }
shell-escape = "0.1.5"

浙公网安备 33010602011771号