mirror of
https://github.com/Noratrieb/cluelessh.git
synced 2026-01-14 16:35:06 +01:00
factor out auth
This commit is contained in:
parent
b0acf03502
commit
1c346659f6
7 changed files with 267 additions and 156 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{net::SocketAddr, sync::Arc};
|
use std::{net::SocketAddr, sync::Arc};
|
||||||
|
|
||||||
use cluelessh_tokio::Channel;
|
use cluelessh_tokio::{server::ServerAuthVerify, Channel};
|
||||||
use eyre::{Context, Result};
|
use eyre::{Context, Result};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
|
|
@ -37,7 +37,19 @@ async fn main() -> eyre::Result<()> {
|
||||||
|
|
||||||
let listener = TcpListener::bind(addr).await.wrap_err("binding listener")?;
|
let listener = TcpListener::bind(addr).await.wrap_err("binding listener")?;
|
||||||
|
|
||||||
let mut listener = cluelessh_tokio::server::ServerListener::new(listener);
|
let auth_verify = ServerAuthVerify {
|
||||||
|
verify_password: Some(Arc::new(|auth| {
|
||||||
|
Box::pin(async move {
|
||||||
|
info!(password = %auth.password, "Got password");
|
||||||
|
|
||||||
|
// Don't worry queen, your password is correct!
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
verify_pubkey: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut listener = cluelessh_tokio::server::ServerListener::new(listener, auth_verify);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let next = listener.accept().await?;
|
let next = listener.accept().await?;
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,10 @@ use std::{collections::HashSet, sync::Arc};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use eyre::{bail, Context, ContextCompat, OptionExt, Result};
|
use cluelessh_tokio::client::SignatureResult;
|
||||||
use cluelessh_tokio::client::{PendingChannel, SignatureResult};
|
use cluelessh_tokio::PendingChannel;
|
||||||
use cluelessh_transport::{key::PublicKey, numbers, parse::Writer};
|
use cluelessh_transport::{key::PublicKey, numbers, parse::Writer};
|
||||||
|
use eyre::{bail, Context, ContextCompat, OptionExt, Result};
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
|
|
@ -140,7 +141,7 @@ async fn main_channel(channel: PendingChannel) -> Result<()> {
|
||||||
};
|
};
|
||||||
|
|
||||||
channel
|
channel
|
||||||
.send_operation(ChannelOperationKind::Request(ChannelRequest::PtyReq {
|
.send(ChannelOperationKind::Request(ChannelRequest::PtyReq {
|
||||||
want_reply: true,
|
want_reply: true,
|
||||||
term: "xterm-256color".to_owned(),
|
term: "xterm-256color".to_owned(),
|
||||||
width_chars: 70,
|
width_chars: 70,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
use core::panic;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
|
||||||
|
use auth::AuthOption;
|
||||||
pub use cluelessh_connection as connection;
|
pub use cluelessh_connection as connection;
|
||||||
use cluelessh_connection::ChannelOperation;
|
use cluelessh_connection::ChannelOperation;
|
||||||
pub use cluelessh_connection::{ChannelUpdate, ChannelUpdateKind};
|
pub use cluelessh_connection::{ChannelUpdate, ChannelUpdateKind};
|
||||||
|
|
@ -21,33 +24,42 @@ pub struct ServerConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ServerConnectionState {
|
enum ServerConnectionState {
|
||||||
Auth(auth::BadAuth),
|
Setup(HashSet<AuthOption>),
|
||||||
|
Auth(auth::ServerAuth),
|
||||||
Open(cluelessh_connection::ChannelsState),
|
Open(cluelessh_connection::ChannelsState),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerConnection {
|
impl ServerConnection {
|
||||||
pub fn new(transport: cluelessh_transport::server::ServerConnection) -> Self {
|
pub fn new(
|
||||||
|
transport: cluelessh_transport::server::ServerConnection,
|
||||||
|
auth_options: HashSet<AuthOption>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
transport,
|
transport,
|
||||||
state: ServerConnectionState::Auth(auth::BadAuth::new()),
|
state: ServerConnectionState::Setup(auth_options),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recv_bytes(&mut self, bytes: &[u8]) -> Result<()> {
|
pub fn recv_bytes(&mut self, bytes: &[u8]) -> Result<()> {
|
||||||
self.transport.recv_bytes(bytes)?;
|
self.transport.recv_bytes(bytes)?;
|
||||||
|
|
||||||
|
if let ServerConnectionState::Setup(options) = &mut self.state {
|
||||||
|
if let Some(session_ident) = self.transport.is_open() {
|
||||||
|
self.state = ServerConnectionState::Auth(auth::ServerAuth::new(
|
||||||
|
mem::take(options),
|
||||||
|
session_ident,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while let Some(packet) = self.transport.next_plaintext_packet() {
|
while let Some(packet) = self.transport.next_plaintext_packet() {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
|
ServerConnectionState::Setup(_) => unreachable!(),
|
||||||
ServerConnectionState::Auth(auth) => {
|
ServerConnectionState::Auth(auth) => {
|
||||||
auth.recv_packet(packet)?;
|
auth.recv_packet(packet)?;
|
||||||
for to_send in auth.packets_to_send() {
|
for to_send in auth.packets_to_send() {
|
||||||
self.transport.send_plaintext_packet(to_send);
|
self.transport.send_plaintext_packet(to_send);
|
||||||
}
|
}
|
||||||
if auth.is_authenticated() {
|
|
||||||
self.state = ServerConnectionState::Open(
|
|
||||||
cluelessh_connection::ChannelsState::new(true),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
ServerConnectionState::Open(con) => {
|
ServerConnectionState::Open(con) => {
|
||||||
con.recv_packet(packet)?;
|
con.recv_packet(packet)?;
|
||||||
|
|
@ -66,14 +78,16 @@ impl ServerConnection {
|
||||||
|
|
||||||
pub fn next_channel_update(&mut self) -> Option<cluelessh_connection::ChannelUpdate> {
|
pub fn next_channel_update(&mut self) -> Option<cluelessh_connection::ChannelUpdate> {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
ServerConnectionState::Auth(_) => None,
|
ServerConnectionState::Setup(_) | ServerConnectionState::Auth(_) => None,
|
||||||
ServerConnectionState::Open(con) => con.next_channel_update(),
|
ServerConnectionState::Open(con) => con.next_channel_update(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn do_operation(&mut self, op: ChannelOperation) {
|
pub fn do_operation(&mut self, op: ChannelOperation) {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
ServerConnectionState::Auth(_) => panic!("tried to get connection during auth"),
|
ServerConnectionState::Setup(_) | ServerConnectionState::Auth(_) => {
|
||||||
|
panic!("tried to get connection before it is ready")
|
||||||
|
}
|
||||||
ServerConnectionState::Open(con) => {
|
ServerConnectionState::Open(con) => {
|
||||||
con.do_operation(op);
|
con.do_operation(op);
|
||||||
self.progress();
|
self.progress();
|
||||||
|
|
@ -83,10 +97,15 @@ impl ServerConnection {
|
||||||
|
|
||||||
pub fn progress(&mut self) {
|
pub fn progress(&mut self) {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
|
ServerConnectionState::Setup(_) => {}
|
||||||
ServerConnectionState::Auth(auth) => {
|
ServerConnectionState::Auth(auth) => {
|
||||||
for to_send in auth.packets_to_send() {
|
for to_send in auth.packets_to_send() {
|
||||||
self.transport.send_plaintext_packet(to_send);
|
self.transport.send_plaintext_packet(to_send);
|
||||||
}
|
}
|
||||||
|
if auth.is_authenticated() {
|
||||||
|
self.state =
|
||||||
|
ServerConnectionState::Open(cluelessh_connection::ChannelsState::new(true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ServerConnectionState::Open(con) => {
|
ServerConnectionState::Open(con) => {
|
||||||
for to_send in con.packets_to_send() {
|
for to_send in con.packets_to_send() {
|
||||||
|
|
@ -103,7 +122,7 @@ impl ServerConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn auth(&mut self) -> Option<&mut auth::BadAuth> {
|
pub fn auth(&mut self) -> Option<&mut auth::ServerAuth> {
|
||||||
match &mut self.state {
|
match &mut self.state {
|
||||||
ServerConnectionState::Auth(auth) => Some(auth),
|
ServerConnectionState::Auth(auth) => Some(auth),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -140,11 +159,10 @@ impl ClientConnection {
|
||||||
if let Some(session_ident) = self.transport.is_open() {
|
if let Some(session_ident) = self.transport.is_open() {
|
||||||
let mut auth = mem::take(auth).unwrap();
|
let mut auth = mem::take(auth).unwrap();
|
||||||
auth.set_session_identifier(session_ident);
|
auth.set_session_identifier(session_ident);
|
||||||
for to_send in auth.packets_to_send() {
|
|
||||||
self.transport.send_plaintext_packet(to_send);
|
|
||||||
}
|
|
||||||
debug!("Connection has been opened");
|
debug!("Connection has been opened");
|
||||||
self.state = ClientConnectionState::Auth(auth);
|
self.state = ClientConnectionState::Auth(auth);
|
||||||
|
self.progress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -235,35 +253,53 @@ impl ClientConnection {
|
||||||
|
|
||||||
/// <https://datatracker.ietf.org/doc/html/rfc4252>
|
/// <https://datatracker.ietf.org/doc/html/rfc4252>
|
||||||
pub mod auth {
|
pub mod auth {
|
||||||
use std::collections::VecDeque;
|
use std::collections::{HashSet, VecDeque};
|
||||||
|
|
||||||
use cluelessh_transport::{numbers, packet::Packet, parse::NameList, peer_error, Result};
|
use cluelessh_transport::{numbers, packet::Packet, parse::NameList, peer_error, Result};
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
pub struct BadAuth {
|
pub struct ServerAuth {
|
||||||
has_failed: bool,
|
has_failed: bool,
|
||||||
packets_to_send: VecDeque<Packet>,
|
packets_to_send: VecDeque<Packet>,
|
||||||
is_authenticated: bool,
|
is_authenticated: bool,
|
||||||
|
options: HashSet<AuthOption>,
|
||||||
|
|
||||||
|
server_requests: VecDeque<ServerRequest>,
|
||||||
|
session_ident: [u8; 32],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ServerRequest {
|
pub enum ServerRequest {
|
||||||
VerifyPassword {
|
VerifyPassword(VerifyPassword),
|
||||||
user: String,
|
VerifyPubkey(VerifyPubkey),
|
||||||
password: String,
|
|
||||||
},
|
|
||||||
VerifyPubkey {
|
|
||||||
session_identifier: [u8; 32],
|
|
||||||
user: String,
|
|
||||||
pubkey: Vec<u8>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BadAuth {
|
pub struct VerifyPassword {
|
||||||
pub fn new() -> Self {
|
pub user: String,
|
||||||
|
pub password: String,
|
||||||
|
}
|
||||||
|
pub struct VerifyPubkey {
|
||||||
|
pub user: String,
|
||||||
|
pub session_identifier: [u8; 32],
|
||||||
|
pub pubkey_alg_name: Vec<u8>,
|
||||||
|
pub pubkey: Vec<u8>,
|
||||||
|
pub signature: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum AuthOption {
|
||||||
|
Password,
|
||||||
|
PublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServerAuth {
|
||||||
|
pub fn new(options: HashSet<AuthOption>, session_ident: [u8; 32]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
has_failed: false,
|
has_failed: false,
|
||||||
packets_to_send: VecDeque::new(),
|
packets_to_send: VecDeque::new(),
|
||||||
|
options,
|
||||||
is_authenticated: false,
|
is_authenticated: false,
|
||||||
|
session_ident,
|
||||||
|
server_requests: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -274,14 +310,14 @@ pub mod auth {
|
||||||
// We ask for a public key, and always let that one pass.
|
// We ask for a public key, and always let that one pass.
|
||||||
// The reason for this is that this makes it a lot easier to test locally.
|
// The reason for this is that this makes it a lot easier to test locally.
|
||||||
// It's not very good, but it's good enough for now.
|
// It's not very good, but it's good enough for now.
|
||||||
let mut auth_req = packet.payload_parser();
|
let mut p = packet.payload_parser();
|
||||||
|
|
||||||
if auth_req.u8()? != numbers::SSH_MSG_USERAUTH_REQUEST {
|
if p.u8()? != numbers::SSH_MSG_USERAUTH_REQUEST {
|
||||||
return Err(peer_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
return Err(peer_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
||||||
}
|
}
|
||||||
let username = auth_req.utf8_string()?;
|
let username = p.utf8_string()?;
|
||||||
let service_name = auth_req.utf8_string()?;
|
let service_name = p.utf8_string()?;
|
||||||
let method_name = auth_req.utf8_string()?;
|
let method_name = p.utf8_string()?;
|
||||||
|
|
||||||
if method_name != "none" {
|
if method_name != "none" {
|
||||||
info!(
|
info!(
|
||||||
|
|
@ -300,22 +336,47 @@ pub mod auth {
|
||||||
|
|
||||||
match method_name {
|
match method_name {
|
||||||
"password" => {
|
"password" => {
|
||||||
let change_password = auth_req.bool()?;
|
if !self.options.contains(&AuthOption::Password) {
|
||||||
|
self.has_failed = true;
|
||||||
|
self.send_failure();
|
||||||
|
}
|
||||||
|
|
||||||
|
let change_password = p.bool()?;
|
||||||
if change_password {
|
if change_password {
|
||||||
return Err(peer_error!("client tried to change password unprompted"));
|
return Err(peer_error!("client tried to change password unprompted"));
|
||||||
}
|
}
|
||||||
let password = auth_req.utf8_string()?;
|
let password = p.utf8_string()?;
|
||||||
|
|
||||||
info!(%password, "Got password");
|
self.server_requests
|
||||||
// Don't worry queen, your password is correct!
|
.push_back(ServerRequest::VerifyPassword(VerifyPassword {
|
||||||
self.queue_packet(Packet::new_msg_userauth_success());
|
user: username.to_owned(),
|
||||||
self.is_authenticated = true;
|
password: password.to_owned(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
"publickey" => {
|
"publickey" => {
|
||||||
info!("Got public key");
|
if !self.options.contains(&AuthOption::PublicKey) {
|
||||||
// Don't worry queen, your key is correct!
|
self.has_failed = true;
|
||||||
self.queue_packet(Packet::new_msg_userauth_success());
|
self.send_failure();
|
||||||
self.is_authenticated = true;
|
}
|
||||||
|
|
||||||
|
// Whether the client is just checking whether the public key is allowed.
|
||||||
|
let is_check = p.bool()?;
|
||||||
|
if is_check {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
let pubkey_alg_name = p.string()?;
|
||||||
|
let public_key_blob = p.string()?;
|
||||||
|
let signature = p.string()?;
|
||||||
|
|
||||||
|
self.server_requests
|
||||||
|
.push_back(ServerRequest::VerifyPubkey(VerifyPubkey {
|
||||||
|
user: username.to_owned(),
|
||||||
|
session_identifier: self.session_ident,
|
||||||
|
pubkey_alg_name: pubkey_alg_name.to_vec(),
|
||||||
|
pubkey: public_key_blob.to_vec(),
|
||||||
|
signature: signature.to_vec(),
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
_ if self.has_failed => {
|
_ if self.has_failed => {
|
||||||
return Err(peer_error!(
|
return Err(peer_error!(
|
||||||
|
|
@ -323,8 +384,7 @@ pub mod auth {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Initial.
|
// Initial:
|
||||||
|
|
||||||
self.queue_packet(Packet::new_msg_userauth_banner(
|
self.queue_packet(Packet::new_msg_userauth_banner(
|
||||||
b"!! this system ONLY allows catgirls to enter !!\r\n\
|
b"!! this system ONLY allows catgirls to enter !!\r\n\
|
||||||
!! all other attempts WILL be prosecuted to the full extent of the rawr !!\r\n\
|
!! all other attempts WILL be prosecuted to the full extent of the rawr !!\r\n\
|
||||||
|
|
@ -333,16 +393,23 @@ pub mod auth {
|
||||||
b"",
|
b"",
|
||||||
));
|
));
|
||||||
|
|
||||||
self.queue_packet(Packet::new_msg_userauth_failure(
|
self.send_failure();
|
||||||
NameList::one("password"),
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
// Stay in the same state
|
// Stay in the same state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn verification_result(&mut self, is_ok: bool) {
|
||||||
|
if is_ok {
|
||||||
|
self.queue_packet(Packet::new_msg_userauth_success());
|
||||||
|
self.is_authenticated = true;
|
||||||
|
} else {
|
||||||
|
self.send_failure();
|
||||||
|
self.has_failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn packets_to_send(&mut self) -> impl Iterator<Item = Packet> + '_ {
|
pub fn packets_to_send(&mut self) -> impl Iterator<Item = Packet> + '_ {
|
||||||
self.packets_to_send.drain(..)
|
self.packets_to_send.drain(..)
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +419,25 @@ pub mod auth {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn server_requests(&mut self) -> impl Iterator<Item = ServerRequest> + '_ {
|
pub fn server_requests(&mut self) -> impl Iterator<Item = ServerRequest> + '_ {
|
||||||
[].into_iter()
|
self.server_requests.drain(..)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_failure(&mut self) {
|
||||||
|
self.queue_packet(Packet::new_msg_userauth_failure(
|
||||||
|
NameList(&self.option_list()),
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn option_list(&self) -> String {
|
||||||
|
self.options
|
||||||
|
.iter()
|
||||||
|
.map(|op| match op {
|
||||||
|
AuthOption::Password => "password",
|
||||||
|
AuthOption::PublicKey => "publickey",
|
||||||
|
})
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(",")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn queue_packet(&mut self, packet: Packet) {
|
fn queue_packet(&mut self, packet: Packet) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation, ChannelOperationKind};
|
use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation};
|
||||||
use std::{collections::HashMap, pin::Pin, sync::Arc};
|
use std::{collections::HashMap, pin::Pin, sync::Arc};
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ use futures::future::BoxFuture;
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
use crate::Channel;
|
use crate::{Channel, ChannelState, PendingChannel};
|
||||||
|
|
||||||
pub struct ClientConnection<S> {
|
pub struct ClientConnection<S> {
|
||||||
stream: Pin<Box<S>>,
|
stream: Pin<Box<S>>,
|
||||||
|
|
@ -27,14 +27,6 @@ pub struct ClientConnection<S> {
|
||||||
auth: ClientAuth,
|
auth: ClientAuth,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChannelState {
|
|
||||||
Pending {
|
|
||||||
ready_send: tokio::sync::oneshot::Sender<Result<(), String>>,
|
|
||||||
updates_send: tokio::sync::mpsc::Sender<ChannelUpdateKind>,
|
|
||||||
},
|
|
||||||
Ready(tokio::sync::mpsc::Sender<ChannelUpdateKind>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ClientAuth {
|
pub struct ClientAuth {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub prompt_password: Arc<dyn Fn() -> BoxFuture<'static, Result<String>> + Send + Sync>,
|
pub prompt_password: Arc<dyn Fn() -> BoxFuture<'static, Result<String>> + Send + Sync>,
|
||||||
|
|
@ -53,11 +45,6 @@ pub struct SignatureResult {
|
||||||
pub signature: Vec<u8>,
|
pub signature: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PendingChannel {
|
|
||||||
ready_recv: tokio::sync::oneshot::Receiver<Result<(), String>>,
|
|
||||||
channel: Channel,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S: AsyncRead + AsyncWrite> ClientConnection<S> {
|
impl<S: AsyncRead + AsyncWrite> ClientConnection<S> {
|
||||||
pub async fn connect(stream: S, auth: ClientAuth) -> Result<Self> {
|
pub async fn connect(stream: S, auth: ClientAuth) -> Result<Self> {
|
||||||
let (operations_send, operations_recv) = tokio::sync::mpsc::channel(15);
|
let (operations_send, operations_recv) = tokio::sync::mpsc::channel(15);
|
||||||
|
|
@ -272,22 +259,3 @@ impl<S: AsyncRead + AsyncWrite> ClientConnection<S> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingChannel {
|
|
||||||
pub async fn wait_ready(self) -> Result<Channel, Option<String>> {
|
|
||||||
match self.ready_recv.await {
|
|
||||||
Ok(Ok(())) => Ok(self.channel),
|
|
||||||
Ok(Err(err)) => Err(Some(err)),
|
|
||||||
Err(_) => Err(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Channel {
|
|
||||||
pub async fn send_operation(&mut self, op: ChannelOperationKind) -> Result<()> {
|
|
||||||
self.ops_send
|
|
||||||
.send(self.number.construct_op(op))
|
|
||||||
.await
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,25 @@ impl Channel {
|
||||||
&self.kind
|
&self.kind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ChannelState {
|
||||||
|
Pending {
|
||||||
|
ready_send: tokio::sync::oneshot::Sender<Result<(), String>>,
|
||||||
|
updates_send: tokio::sync::mpsc::Sender<ChannelUpdateKind>,
|
||||||
|
},
|
||||||
|
Ready(tokio::sync::mpsc::Sender<ChannelUpdateKind>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PendingChannel {
|
||||||
|
ready_recv: tokio::sync::oneshot::Receiver<Result<(), String>>,
|
||||||
|
channel: Channel,
|
||||||
|
}
|
||||||
|
impl PendingChannel {
|
||||||
|
pub async fn wait_ready(self) -> Result<Channel, Option<String>> {
|
||||||
|
match self.ready_recv.await {
|
||||||
|
Ok(Ok(())) => Ok(self.channel),
|
||||||
|
Ok(Err(err)) => Err(Some(err)),
|
||||||
|
Err(_) => Err(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,30 @@
|
||||||
use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation};
|
use cluelessh_connection::{ChannelKind, ChannelNumber, ChannelOperation};
|
||||||
|
use futures::future::BoxFuture;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
net::SocketAddr,
|
net::SocketAddr,
|
||||||
pin::Pin,
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{AsyncReadExt, AsyncWriteExt},
|
io::{AsyncReadExt, AsyncWriteExt},
|
||||||
net::{TcpListener, TcpStream},
|
net::{TcpListener, TcpStream},
|
||||||
};
|
};
|
||||||
|
|
||||||
use cluelessh_protocol::{ChannelUpdateKind, SshStatus};
|
use cluelessh_protocol::{
|
||||||
use eyre::{eyre, ContextCompat, Result, WrapErr};
|
auth::{AuthOption, VerifyPassword, VerifyPubkey},
|
||||||
|
ChannelUpdateKind, SshStatus,
|
||||||
|
};
|
||||||
|
use eyre::{eyre, ContextCompat, OptionExt, Result, WrapErr};
|
||||||
use tokio::io::{AsyncRead, AsyncWrite};
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
use crate::Channel;
|
use crate::{Channel, ChannelState, PendingChannel};
|
||||||
|
|
||||||
pub struct ServerListener {
|
pub struct ServerListener {
|
||||||
listener: TcpListener,
|
listener: TcpListener,
|
||||||
// todo ratelimits etc
|
auth_verify: ServerAuthVerify,
|
||||||
|
// TODO ratelimits etc
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ServerConnection<S> {
|
pub struct ServerConnection<S> {
|
||||||
|
|
@ -38,38 +44,27 @@ pub struct ServerConnection<S> {
|
||||||
|
|
||||||
/// New channels opened by the peer.
|
/// New channels opened by the peer.
|
||||||
new_channels: VecDeque<Channel>,
|
new_channels: VecDeque<Channel>,
|
||||||
}
|
|
||||||
|
|
||||||
enum ChannelState {
|
auth_verify: ServerAuthVerify,
|
||||||
Pending {
|
|
||||||
ready_send: tokio::sync::oneshot::Sender<Result<(), String>>,
|
|
||||||
updates_send: tokio::sync::mpsc::Sender<ChannelUpdateKind>,
|
|
||||||
},
|
|
||||||
Ready(tokio::sync::mpsc::Sender<ChannelUpdateKind>),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Operation {
|
enum Operation {
|
||||||
VerifyPassword {
|
VerifyPassword(Result<()>),
|
||||||
user: String,
|
VerifyPubkey(Result<()>),
|
||||||
password: String,
|
|
||||||
},
|
|
||||||
VerifyPubkey {
|
|
||||||
session_identifier: [u8; 32],
|
|
||||||
user: String,
|
|
||||||
pubkey: Vec<u8>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SignatureResult {
|
#[derive(Clone)]
|
||||||
pub key_alg_name: &'static str,
|
pub struct ServerAuthVerify {
|
||||||
pub public_key: Vec<u8>,
|
pub verify_password:
|
||||||
pub signature: Vec<u8>,
|
Option<Arc<dyn Fn(VerifyPassword) -> BoxFuture<'static, Result<()>> + Send + Sync>>,
|
||||||
|
pub verify_pubkey:
|
||||||
|
Option<Arc<dyn Fn(VerifyPubkey) -> BoxFuture<'static, Result<()>> + Send + Sync>>,
|
||||||
|
}
|
||||||
|
fn _assert_send_sync() {
|
||||||
|
fn send<T: Send + Sync>() {}
|
||||||
|
send::<ServerAuthVerify>();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PendingChannel {
|
|
||||||
ready_recv: tokio::sync::oneshot::Receiver<Result<(), String>>,
|
|
||||||
channel: Channel,
|
|
||||||
}
|
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
SshStatus(SshStatus),
|
SshStatus(SshStatus),
|
||||||
ServerError(eyre::Report),
|
ServerError(eyre::Report),
|
||||||
|
|
@ -81,22 +76,41 @@ impl From<eyre::Report> for Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerListener {
|
impl ServerListener {
|
||||||
pub fn new(listener: TcpListener) -> Self {
|
pub fn new(listener: TcpListener, auth_verify: ServerAuthVerify) -> Self {
|
||||||
Self { listener }
|
Self {
|
||||||
|
listener,
|
||||||
|
auth_verify,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn accept(&mut self) -> Result<ServerConnection<TcpStream>> {
|
pub async fn accept(&mut self) -> Result<ServerConnection<TcpStream>> {
|
||||||
let (conn, peer_addr) = self.listener.accept().await?;
|
let (conn, peer_addr) = self.listener.accept().await?;
|
||||||
|
|
||||||
Ok(ServerConnection::new(conn, peer_addr))
|
Ok(ServerConnection::new(
|
||||||
|
conn,
|
||||||
|
peer_addr,
|
||||||
|
self.auth_verify.clone(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
pub fn new(stream: S, peer_addr: SocketAddr) -> Self {
|
pub fn new(stream: S, peer_addr: SocketAddr, auth_verify: ServerAuthVerify) -> Self {
|
||||||
let (operations_send, operations_recv) = tokio::sync::mpsc::channel(15);
|
let (operations_send, operations_recv) = tokio::sync::mpsc::channel(15);
|
||||||
let (channel_ops_send, channel_ops_recv) = tokio::sync::mpsc::channel(15);
|
let (channel_ops_send, channel_ops_recv) = tokio::sync::mpsc::channel(15);
|
||||||
|
|
||||||
|
let mut options = HashSet::new();
|
||||||
|
if auth_verify.verify_password.is_some() {
|
||||||
|
options.insert(AuthOption::Password);
|
||||||
|
}
|
||||||
|
if auth_verify.verify_pubkey.is_some() {
|
||||||
|
options.insert(AuthOption::PublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.is_empty() {
|
||||||
|
panic!("no auth options provided");
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
stream: Box::pin(stream),
|
stream: Box::pin(stream),
|
||||||
peer_addr,
|
peer_addr,
|
||||||
|
|
@ -110,8 +124,10 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
cluelessh_transport::server::ServerConnection::new(
|
cluelessh_transport::server::ServerConnection::new(
|
||||||
cluelessh_protocol::ThreadRngRand,
|
cluelessh_protocol::ThreadRngRand,
|
||||||
),
|
),
|
||||||
|
options,
|
||||||
),
|
),
|
||||||
new_channels: VecDeque::new(),
|
new_channels: VecDeque::new(),
|
||||||
|
auth_verify,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,28 +141,28 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
if let Some(auth) = self.proto.auth() {
|
if let Some(auth) = self.proto.auth() {
|
||||||
for req in auth.server_requests() {
|
for req in auth.server_requests() {
|
||||||
match req {
|
match req {
|
||||||
cluelessh_protocol::auth::ServerRequest::VerifyPassword { user, password } => {
|
cluelessh_protocol::auth::ServerRequest::VerifyPassword(password_verify) => {
|
||||||
let send = self.operations_send.clone();
|
let send = self.operations_send.clone();
|
||||||
|
let verify = self
|
||||||
|
.auth_verify
|
||||||
|
.verify_password
|
||||||
|
.clone()
|
||||||
|
.ok_or_eyre("password auth not supported")?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _ = send
|
let result = verify(password_verify).await;
|
||||||
.send(Operation::VerifyPassword { user, password })
|
let _ = send.send(Operation::VerifyPassword(result)).await;
|
||||||
.await;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
cluelessh_protocol::auth::ServerRequest::VerifyPubkey {
|
cluelessh_protocol::auth::ServerRequest::VerifyPubkey(pubkey_verify) => {
|
||||||
session_identifier,
|
|
||||||
pubkey,
|
|
||||||
user,
|
|
||||||
} => {
|
|
||||||
let send = self.operations_send.clone();
|
let send = self.operations_send.clone();
|
||||||
|
let verify = self
|
||||||
|
.auth_verify
|
||||||
|
.verify_pubkey
|
||||||
|
.clone()
|
||||||
|
.ok_or_eyre("pubkey auth not supported")?;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let _ = send
|
let result = verify(pubkey_verify).await;
|
||||||
.send(Operation::VerifyPubkey {
|
let _ = send.send(Operation::VerifyPubkey(result)).await;
|
||||||
session_identifier,
|
|
||||||
user,
|
|
||||||
pubkey,
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +263,7 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
let read = read.wrap_err("reading from connection")?;
|
let read = read.wrap_err("reading from connection")?;
|
||||||
if read == 0 {
|
if read == 0 {
|
||||||
info!("Did not read any bytes from TCP stream, EOF");
|
info!("Did not read any bytes from TCP stream, EOF");
|
||||||
return Ok(());
|
return Err(Error::SshStatus(SshStatus::Disconnect));
|
||||||
}
|
}
|
||||||
if let Err(err) = self.proto.recv_bytes(&self.buf[..read]) {
|
if let Err(err) = self.proto.recv_bytes(&self.buf[..read]) {
|
||||||
return Err(Error::SshStatus(err));
|
return Err(Error::SshStatus(err));
|
||||||
|
|
@ -261,8 +277,12 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
}
|
}
|
||||||
op = self.operations_recv.recv() => {
|
op = self.operations_recv.recv() => {
|
||||||
match op {
|
match op {
|
||||||
Some(Operation::VerifyPubkey { .. }) => todo!(),
|
Some(Operation::VerifyPubkey(result)) => if let Some(auth) = self.proto.auth() {
|
||||||
Some(Operation::VerifyPassword { .. }) => todo!(),
|
auth.verification_result(result.is_ok());
|
||||||
|
},
|
||||||
|
Some(Operation::VerifyPassword(result)) => if let Some(auth) = self.proto.auth() {
|
||||||
|
auth.verification_result(result.is_ok());
|
||||||
|
},
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
self.send_off_data().await?;
|
self.send_off_data().await?;
|
||||||
|
|
@ -315,13 +335,3 @@ impl<S: AsyncRead + AsyncWrite> ServerConnection<S> {
|
||||||
self.new_channels.pop_front()
|
self.new_channels.pop_front()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingChannel {
|
|
||||||
pub async fn wait_ready(self) -> Result<Channel, Option<String>> {
|
|
||||||
match self.ready_recv.await {
|
|
||||||
Ok(Ok(())) => Ok(self.channel),
|
|
||||||
Ok(Err(err)) => Err(Some(err)),
|
|
||||||
Err(_) => Err(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,12 @@ enum ServerState {
|
||||||
encryption_client_to_server: EncryptionAlgorithm,
|
encryption_client_to_server: EncryptionAlgorithm,
|
||||||
encryption_server_to_client: EncryptionAlgorithm,
|
encryption_server_to_client: EncryptionAlgorithm,
|
||||||
},
|
},
|
||||||
ServiceRequest,
|
ServiceRequest {
|
||||||
Open,
|
session_ident: [u8; 32],
|
||||||
|
},
|
||||||
|
Open {
|
||||||
|
session_ident: [u8; 32],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerConnection {
|
impl ServerConnection {
|
||||||
|
|
@ -289,9 +293,9 @@ impl ServerConnection {
|
||||||
*encryption_server_to_client,
|
*encryption_server_to_client,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
self.state = ServerState::ServiceRequest {};
|
self.state = ServerState::ServiceRequest { session_ident: *h };
|
||||||
}
|
}
|
||||||
ServerState::ServiceRequest => {
|
ServerState::ServiceRequest { session_ident } => {
|
||||||
// TODO: this should probably move out of here? unsure.
|
// TODO: this should probably move out of here? unsure.
|
||||||
if packet.payload.first() != Some(&numbers::SSH_MSG_SERVICE_REQUEST) {
|
if packet.payload.first() != Some(&numbers::SSH_MSG_SERVICE_REQUEST) {
|
||||||
return Err(peer_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
return Err(peer_error!("did not send SSH_MSG_SERVICE_REQUEST"));
|
||||||
|
|
@ -312,9 +316,11 @@ impl ServerConnection {
|
||||||
writer.finish()
|
writer.finish()
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
self.state = ServerState::Open;
|
self.state = ServerState::Open {
|
||||||
|
session_ident: *session_ident,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
ServerState::Open => {
|
ServerState::Open { .. } => {
|
||||||
self.plaintext_packets.push_back(packet);
|
self.plaintext_packets.push_back(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -322,6 +328,13 @@ impl ServerConnection {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_open(&self) -> Option<[u8; 32]> {
|
||||||
|
match self.state {
|
||||||
|
ServerState::Open { session_ident } => Some(session_ident),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn next_msg_to_send(&mut self) -> Option<Msg> {
|
pub fn next_msg_to_send(&mut self) -> Option<Msg> {
|
||||||
self.packet_transport.next_msg_to_send()
|
self.packet_transport.next_msg_to_send()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue