Giter Club home page Giter Club logo

russh's Introduction

Russh

Rust All Contributors

Low-level Tokio SSH2 client and server implementation.

Examples: simple client, interactive PTY client, server, SFTP client, SFTP server.

This is a fork of Thrussh by Pierre-Étienne Meunier.

✨ = added in Russh

  • More panic safety
  • async_trait support ✨
  • direct-tcpip (local port forwarding)
  • forward-tcpip (remote port forwarding) ✨
  • direct-streamlocal (local UNIX socket forwarding, client only) ✨
  • Ciphers:
  • Key exchanges:
    • [email protected]
    • diffie-hellman-group1-sha1
    • diffie-hellman-group14-sha1
    • diffie-hellman-group14-sha256
    • diffie-hellman-group16-sha512
    • ecdh-sha2-nistp256
    • ecdh-sha2-nistp384
    • ecdh-sha2-nistp521
  • MACs:
  • Host keys and public key auth:
    • ssh-ed25519
    • rsa-sha2-256
    • rsa-sha2-512
    • ssh-rsa
    • ecdsa-sha2-nistp256
    • ecdsa-sha2-nistp384
    • ecdsa-sha2-nistp521
  • Authentication methods:
    • password
    • publickey
    • keyboard-interactive
    • none
    • OpenSSH certificates (client only ✨)
  • Dependency updates
  • OpenSSH keepalive request handling ✨
  • OpenSSH agent forwarding channels ✨
  • OpenSSH server-sig-algs extension ✨
  • openssl dependency is optional ✨

Safety

  • deny(clippy::unwrap_used)
  • deny(clippy::expect_used)
  • deny(clippy::indexing_slicing)
  • deny(clippy::panic)
  • Exceptions are checked manually

Panics

  • When the Rust allocator fails to allocate memory during a CryptoVec being resized.

Unsafe code

  • cryptovec uses unsafe for faster copying, initialization and binding to native API.

Ecosystem

  • russh-sftp - server-side and client-side SFTP subsystem support for russh - see russh/examples/sftp_server.rs or russh/examples/sftp_client.rs.
  • async-ssh2-tokio - simple high-level API for running commands over SSH.

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Mihir Samdarshi
Mihir Samdarshi

📖
Connor Peet
Connor Peet

💻
KVZN
KVZN

💻
Adrian Müller (DTT)
Adrian Müller (DTT)

💻
Simone Margaritelli
Simone Margaritelli

💻
Joe Grund
Joe Grund

💻
AspectUnk
AspectUnk

💻
Simão Mata
Simão Mata

💻
Mariotaku
Mariotaku

💻
yorkz1994
yorkz1994

💻
Ciprian Dorin Craciun
Ciprian Dorin Craciun

💻
Eric Milliken
Eric Milliken

💻
Swelio
Swelio

💻
Joshua Benz
Joshua Benz

💻
Jan Holthuis
Jan Holthuis

🛡️
mateuszkj
mateuszkj

💻
Saksham Mittal
Saksham Mittal

💻
Lucas Kent
Lucas Kent

💻
Raphael Druon
Raphael Druon

💻
Maya the bee
Maya the bee

💻
Milo Mirate
Milo Mirate

💻
George Hopkins
George Hopkins

💻
Åke Amcoff
Åke Amcoff

💻
Brendon Ho
Brendon Ho

💻
Samuel Ainsworth
Samuel Ainsworth

💻
Sherlock Holo
Sherlock Holo

💻
Alessandro Ricottone
Alessandro Ricottone

💻
T0b1-iOS
T0b1-iOS

💻
Shoaib Merchant
Shoaib Merchant

💻
Michael Gleason
Michael Gleason

💻
Ana Gelez
Ana Gelez

💻
Tom König
Tom König

💻
Pierre Barre
Pierre Barre

💻
Jean-Baptiste Skutnik
Jean-Baptiste Skutnik

💻
Adam Chappell
Adam Chappell

💻

This project follows the all-contributors specification. Contributions of any kind welcome!

russh's People

Contributors

eugeny avatar allcontributors[bot] avatar lowlevl avatar dependabot[bot] avatar connor4312 avatar mihirsamdarshi avatar mmirate avatar mllken avatar aspectunk avatar amtelekom avatar ricott1 avatar joshbenz avatar robertabcd avatar george-hopkins avatar samuela avatar bho01 avatar rukai avatar gotlougit avatar jgrund avatar akeamc avatar simao avatar elegaanz avatar swelio avatar barre avatar citorva avatar tomknig avatar evilsocket avatar shoaibmerchant avatar sherlock-holo avatar rdruon avatar

Stargazers

Jonas Krüger Svensson avatar Mathis Bramkamp avatar Evangelos Pappas avatar Cody avatar Malcolm Still avatar Vaz avatar Max Thomson avatar Kai Jellinghaus avatar nisch.codes avatar  avatar  avatar Lissan avatar Andrew Baxter avatar Shawn Pang avatar  avatar KAYUII avatar  avatar John Xu avatar Jérôme Tamba avatar Julius Rickert avatar  avatar Bruce Stringer avatar ItsNik avatar Thomas Lekanger avatar Mark Vainomaa avatar Timothy Cole avatar Paul avatar Markus Geiger avatar Frédéric Arroyo avatar Amit Nishry avatar Bruno Papait avatar Oleksandr Babak avatar Nithin Stephen Raphael avatar  avatar  avatar kanpov avatar cve avatar A'rpi avatar Abdelhak Bougouffa avatar Ame avatar Maxwell Koo avatar Christoph Grabo avatar Fredrik Magnusson avatar Tomislav O avatar Graham Moss avatar pandamoon21 avatar  avatar Kainoa Kanter avatar Miroslav Sobotka avatar SanLi avatar  avatar Bruce Du avatar  avatar Austin Lin avatar  avatar Saeed Almesalati avatar Johnson Masilla Vino avatar Shaeq Ahmed avatar William Hicks avatar Linhong avatar Colin Shaw avatar Lukas Stauersbøl avatar  avatar NightsWatch avatar Minho Ryang avatar  avatar Lyndon Stolton avatar Liam Perlaki avatar Juan avatar Sanmer avatar theProf avatar Janusz Dziurzyński avatar wangzc avatar Aklis avatar sgnay avatar Jeidnx avatar  avatar vk avatar Yongjoon Lee avatar Vaniot avatar Tyler avatar Sylvain LAFRASSE avatar Luis Capelo avatar Null avatar Lemuel Okoli avatar FelixKLG avatar Toby avatar Pavlo Chmykh avatar Jooy avatar AbyssalDrifter avatar dbr/Ben avatar  avatar Marco Cilloni avatar KK Cheung avatar Matteo Martellini avatar Mathys Lv avatar Stefano Probst avatar Arturo Lecuona avatar Tsuzuki Tsuchiya avatar Tetsuya Yamamoto avatar

Watchers

Lucian avatar Neustradamus avatar Andreas Gnau avatar  avatar Timo avatar Syber.Nomad avatar  avatar  avatar  avatar DravenLu avatar

russh's Issues

Corrupted MAC on input (with old ssh clinet OpenSSH_6.6.1p1)

Description

I ran into a problem with an old version of the ssh client.

error:

root@d127e6b24f29:/# ssh -p 2222 -l root 192.168.66.4 -vvvv
OpenSSH_6.6.1, OpenSSL 1.0.1f 6 Jan 2014
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: Applying options for *
debug2: ssh_connect: needpriv 0
debug1: Connecting to 192.168.66.4 [192.168.66.4] port 2222.
debug1: Connection established.
debug1: permanently_set_uid: 0/0
debug1: identity file /root/.ssh/id_rsa type -1
debug1: identity file /root/.ssh/id_rsa-cert type -1
debug1: identity file /root/.ssh/id_dsa type -1
debug1: identity file /root/.ssh/id_dsa-cert type -1
debug1: identity file /root/.ssh/id_ecdsa type -1
debug1: identity file /root/.ssh/id_ecdsa-cert type -1
debug1: identity file /root/.ssh/id_ed25519 type -1
debug1: identity file /root/.ssh/id_ed25519-cert type -1
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.13
debug1: Remote protocol version 2.0, remote software version russh_0.37.1
debug1: no match: russh_0.37.1
debug2: fd 3 setting O_NONBLOCK
debug3: put_host_port: [192.168.66.4]:2222
debug3: load_hostkeys: loading entries for host "[192.168.66.4]:2222" from file "/root/.ssh/known_hosts"
debug3: load_hostkeys: loaded 0 keys
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug2: kex_parse_kexinit: [email protected],ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1
debug2: kex_parse_kexinit: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-ed25519,ssh-rsa,ssh-dss
debug2: kex_parse_kexinit: aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,[email protected],[email protected],[email protected],aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour,[email protected]
debug2: kex_parse_kexinit: aes128-ctr,aes192-ctr,aes256-ctr,arcfour256,arcfour128,[email protected],[email protected],[email protected],aes128-cbc,3des-cbc,blowfish-cbc,cast128-cbc,aes192-cbc,aes256-cbc,arcfour,[email protected]
debug2: kex_parse_kexinit: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-md5,hmac-sha1,[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,[email protected],hmac-sha1-96,hmac-md5-96
debug2: kex_parse_kexinit: [email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],[email protected],hmac-md5,hmac-sha1,[email protected],[email protected],hmac-sha2-256,hmac-sha2-512,hmac-ripemd160,[email protected],hmac-sha1-96,hmac-md5-96
debug2: kex_parse_kexinit: none,[email protected],zlib
debug2: kex_parse_kexinit: none,[email protected],zlib
debug2: kex_parse_kexinit: 
debug2: kex_parse_kexinit: 
debug2: kex_parse_kexinit: first_kex_follows 0 
debug2: kex_parse_kexinit: reserved 0 
debug2: kex_parse_kexinit: [email protected],diffie-hellman-group14-sha256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,ext-info-s
debug2: kex_parse_kexinit: ssh-ed25519
debug2: kex_parse_kexinit: [email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr
debug2: kex_parse_kexinit: [email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr
debug2: kex_parse_kexinit: [email protected],[email protected],hmac-sha2-512,hmac-sha2-256,[email protected],hmac-sha1,none
debug2: kex_parse_kexinit: [email protected],[email protected],hmac-sha2-512,hmac-sha2-256,[email protected],hmac-sha1,none
debug2: kex_parse_kexinit: none,zlib,[email protected]
debug2: kex_parse_kexinit: none,zlib,[email protected]
debug2: kex_parse_kexinit: 
debug2: kex_parse_kexinit: 
debug2: kex_parse_kexinit: first_kex_follows 0 
debug2: kex_parse_kexinit: reserved 0 
debug2: mac_setup: setup [email protected]
debug1: kex: server->client aes128-ctr [email protected] none
debug2: mac_setup: setup [email protected]
debug1: kex: client->server aes128-ctr [email protected] none
debug1: sending SSH2_MSG_KEX_ECDH_INIT
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ED25519 57:1d:39:43:d1:56:22:b6:00:60:67:f0:5e:70:7e:0f
debug3: put_host_port: [192.168.66.4]:2222
debug3: put_host_port: [192.168.66.4]:2222
debug3: load_hostkeys: loading entries for host "[192.168.66.4]:2222" from file "/root/.ssh/known_hosts"
debug3: load_hostkeys: loaded 0 keys
debug3: load_hostkeys: loading entries for host "[192.168.66.4]:2222" from file "/root/.ssh/known_hosts"
debug3: load_hostkeys: loaded 0 keys
debug1: checking without port identifier
debug3: load_hostkeys: loading entries for host "192.168.66.4" from file "/root/.ssh/known_hosts"
debug3: load_hostkeys: loaded 0 keys
The authenticity of host '[192.168.66.4]:2222 ([192.168.66.4]:2222)' can't be established.
ED25519 key fingerprint is 57:1d:39:43:d1:56:22:b6:00:60:67:f0:5e:70:7e:0f.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[192.168.66.4]:2222' (ED25519) to the list of known hosts.
debug1: ssh_ed25519_verify: signature correct
debug2: kex_derive_keys
debug2: set_newkeys: mode 1
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug2: set_newkeys: mode 0
debug1: SSH2_MSG_SERVICE_REQUEST sent
Corrupted MAC on input.
Disconnecting: Packet corrupt

Steps to reproduce

Start ssh server from example:

cargo run --example echoserver

or using cli command

cargo install --git https://gitlab.com/mateuszkj/ssh-test-server.git ssh-test-server-cli
RUST_LOG=debug ssh-test-server-cli --bind-addr 0.0.0.0 --port 2222 --login user --password pass123

then download Ubuntu 14.04 in container and install openssh-client

podman run -t -i --rm docker.io/ubuntu:14.04 /bin/bash
apt update && apt install -y openssh-client

so you have ssh client version:

root@d127e6b24f29:/# ssh -V
OpenSSH_6.6.1p1 Ubuntu-2ubuntu2.13, OpenSSL 1.0.1f 6 Jan 2014

then try to login to your server from container

ssh -p 2222 -l root  <ip of your host pc> -vvv

Debugging

I found that the ssh server always sends an EXT_INFO message to the client, regardless of whether the client can handle it.

The ssh client I tested does not support RFC 8308, so it crashes on that message.

RFC 8308 says that client has to offer ext-info-s in filed kex_algorithms in message SSH_MSG_KEXINIT. If not then we can assume that client do not support it.

Track request from `auth_publickey` function

Sorry for yet another issue :p. There's just some more stuff I had a question about, and something I wasn't sure if the library was capable of solving or not.


In my application, users can pull Git repositories like you can on GitHub, and one of the available methods for doing such is via SSH. I'm wanting to identify users via their SSH keys, and I thus have a setup somewhat like this:

struct Server {}

impl server::Handler for Server {
    fn auth_publickey(self, _user: &str, _public_key: &russh_keys::key::PublicKey) -> Self::FutureAuth {
        self.finished_auth(Auth::Accept)
    }
    fn exec_request(self, channel: ChannelId, data: &[u8], mut session: Session) -> Self::FutureUnit {
        /* What here? */
    }
}

I'm needing some kind of way to track a fingerprint to the current session, so that I can map a fingerprint to a user inside of fn exec_request. Is there any way a session could be passed into fn auth_publickey so that I could just enter vectors of (Session::id(), PublicKey) pairs into my struct?

Accessing channel input during exec_request

I feel like I must be missing something, so I apologize if this is incredibly obvious. I've been trying everything I can think of for some time now.

I am trying to build a server that can be the target end of a git push. I originally thought it would be roughly enough to spawn a child process of git-receive-pack and connect the channel using copy_bidirectional. However it appears that I can never get any data from the channel.

To debug this, I started using cat so I could compare my application to OpenSSH. If I run ssh localhost cat (which goes to my OpenSSH daemon, not russh) I am able to type text and it is printed back to me, just like cat without ssh. When I try to replicate this inside exec_request though there is never any data read-- it just waits for data permanently. I've tried using Channel::wait as well as Channel::into_stream and using it as an AsyncRead but have had no luck with either.

My current russh::server::Handler is here https://github.com/justinrubek/thoenix/blob/aed91e27ddf80a05273b15ed7e59a9ef8c2fe1e9/crates/cli/src/main.rs#L284. Some of the code has gotten a bit messy as I've been messing with it a lot. I'd really appreciate some assistance, and would be willing to contribute another example if we get it working and that is desired.

Support custom server_id line terminators.

Hi, I opened this same issue on the original thrussh repo but it was never fixed/implemented, trying again here before I go creating my own fork just for this small change.

I’m using thrussh to create an SSH server honeypot. Among the configuration options, the user can specify the server_id (which is basically what nmap and most of the tools use to fingerprint the SSH server). The problem is that some banners are terminated by a single “\n”, while others are terminated by “\r\n”, but the library hardcodes the latter here https://github.com/warp-tech/russh/blob/master/russh/src/sshbuffer.rs#L40-L44

If someone passes a banner like “something\n”, that breaks the protocol because “\r\n” is added anyway. You can find more details about this issue here evilsocket/medusa#3

I suggest to add the server_id termination bytes as a configurable option (default to “\r\n”). Let me know if you prefer to do the changes yourself or you’d prefer me sending a PR ^_^

Channel doesn't expose adjust window size

I was wondering whether I could expose the mechanism to send an adjust window size method on a client Channel, since RFC 4254 does say that the client can also request a window size change?

I was wondering if you could help me out with the structure of the code, if this was something you would be ok with me implementing?

I see that client has an adjust_window method that is called internally when the window is adjusted. However, I have no clue what that does, since it just seems to return the parameter passed in un-touched.

In addition, the Encrypted struct has an adjust_window_size method which is used when the client receives data, but I am not sure whether this is the method that I should be calling, since it expects a slice of data (although I guess I could pass in an empty slice of u8).''

Using ssh-key and ssh-enconding in russh-keys

Hi,

There is a lot of custom code to encode/decode keys and ssh payloads in russh-keys. I think this code is error prone and might have some bugs, for example the following key, used in the russh-keys test suite:

-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDTR2N0uf
rcvaVVRBhwc6eaAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIAFQLoDASJm1+tu5
qs/5pwCJs6o4fbBjGcYZHCTImVikAAAAkGdkZqVbBNP5V5f4dnkogFK6B8recS9mRgbq6i
9coTdA7URjKPqbeUZ/Lvpp4XHtXvYKTQ2gGreU17yAzl5WSIYqQ5lK6NM8ifFkhjvfilFR
rW5M84np6Ku0LaIqlQjV/JHN5eE7aGtFGOA/WO3oO0W9eLqXdxIAm8Dntq7LLv/KeaY3EX
BlJ6/YOScK4e3qBA==
-----END OPENSSH PRIVATE KEY-----

Cannot be parsed by my version of openssh (9.2), but can be parsed with russh-key. Presumably it was generated/encoded with russh? Possibly this is an error on my side in this case, but my point is, these kind of problems are more likely to exist with a custom implementation, IMO.

There is a dependency on openssl, to use rsa keys and a few other things. To use openssl we need to use unsafe code. Moving to a rust only implementation would go towards at some point stop using openssl and maybe eventually stop using unsafe code (the cryptovec issue would also have to be addressed).

ssh keys encoding/decoding is already done by other rust only crates like https://docs.rs/ssh-key/latest/ssh_key/. That crate also supports encryption and signature verification, so we could potentially avoid using openssl and just use pure rust, even when verifying signatures and encrypting payloads. ssh-keys is maintained by the RustCrypto project which has more people working on it and there is more scrutiny over the implementation.

The api to load openssh keys using russh-key is a bit awkward to use, for example, to parse an openssh key from a string, you need to split the string with spaces and then call parse_public_key_base64. Using ssh-key would give us a better api.

We could potentially at some point reexport the ssh-key types, which would also provide better integration with other crates, that might also use it. For example, ssh-key has a serde feature that provides Serializer/Deserializer instances for keys.

To be clear, I think there is still functionality worth keeping in russh-keys, this is just about using ssh-key where it can be used and keep using russh-keys for the rest.

That all said, changing to use ssh-keys would be a lot of work, so I wanted to ask before I work on it, would you consider a PR that implement this change, or you have a strong reason to keep using russh-keys?

Initial window size

Is there a way to access the initial size of an opened SSH terminal size (aka rows and columns) from the russh::server::Handler?

There is a window_change_request method on the russh::server::Handler, but it is not invoked when a fresh connection is established. The arguments to window_change_request don't seem too clear either. Is column_width the width of a single column in pixels? Is the pix_width the width of the terminal in pixels, so the actual amount of columns is pix_width / column_width?

Allow client to use reverse port forwarding `-R`

Hi,

I am trying to allow a client to open a reverse port on my server. I have something like this:

async fn tcpip_forward(
        self,
        address: &str,
        port: &mut u32,
        session: Session,
    ) -> Result<(Self, bool, Session), Self::Error> {
        let port = *port;
        let session_ref = &mut session;

        tokio::spawn(async move {
            let h = format!("127.0.0.1:{}", port);

            let listener = TcpListener::bind(h).await.unwrap();

            let (stream, addr) = listener.accept().await.unwrap();

            let chid = session_ref.channel_open_forwarded_tcpip(&addr.ip().to_string(),
                                                            addr.port() as u32,
                                                            &addr.ip().to_string(),
                                                            addr.port() as u32).unwrap();

            // TODO: then somehow use session.data(chid, data) to copy data from stream to session.data?
        });

        Ok((self, true, session))
    } 

But obviously this does cannot compile. Session cannot really be moved into the spawned async block, since we need to return it from tcpip_forward.

So is there a way to listen to a port on the server and forward that to a client? Or is this impossible at the moment? Should I be using something other than Session to open this channel?

Thanks

Unable to connect to server with only public key authentication allowed

Whenever I try to create an SSH server with only public key authentication allowed, I always get a Permission denied (publickey) message from ssh. I was able to reproduce the situation with the following code:

use core::pin::Pin;
use futures::FutureExt;
use russh::{
    server::{self, Auth, Session},
    MethodSet,
};
use russh_keys::key::{KeyPair, PublicKey};
use std::{net::SocketAddr, str::FromStr, sync::Arc};

#[derive(Clone)]
struct Server {}

impl server::Server for Server {
    type Handler = Self;

    fn new_client(&mut self, socket_addr: Option<SocketAddr>) -> Self {
        self.to_owned()
    }
}

impl server::Handler for Server {
    type Error = russh::Error;
    type FutureAuth =
        Pin<Box<dyn core::future::Future<Output = Result<(Self, Auth), Self::Error>> + Send>>;
    type FutureUnit =
        Pin<Box<dyn core::future::Future<Output = Result<(Self, Session), Self::Error>> + Send>>;
    type FutureBool = Pin<
        Box<dyn core::future::Future<Output = Result<(Self, Session, bool), Self::Error>> + Send>,
    >;

    fn finished_auth(self, auth: Auth) -> Self::FutureAuth {
        async move { Ok((self, auth)) }.boxed()
    }

    fn finished_bool(self, b: bool, session: Session) -> Self::FutureBool {
        async move { Ok((self, session, b)) }.boxed()
    }

    fn finished(self, session: Session) -> Self::FutureUnit {
        async move { Ok((self, session)) }.boxed()
    }

    fn auth_publickey(self, user: &str, public_key: &PublicKey) -> Self::FutureAuth {
        async move { self.finished_auth(Auth::Accept).await }.boxed()
    }
}

#[tokio::main]
async fn main() {
    let mut config = server::Config::default();
    config.methods = MethodSet::PUBLICKEY;
    config.keys.push(KeyPair::generate_ed25519().unwrap());

    server::run(
        Arc::new(config),
        &SocketAddr::from_str("0.0.0.0:2222").unwrap(),
        Server {},
    )
    .await
    .unwrap();
}

If I add a println!("AUTH PUBKEY BEING CALLED"); line right below the function declaration at fn auth_publickey(self, user: &str, public_key: &PublicKey), my terminal doesn't show any output, leading me to think the function is never even being called, but I can't figure out why that might be the case.

Keepalive on client side

Hello,

Does russh support keepalive on the client side ?

Does it reply for us to the SSH server or do we have to do something ?

Thank you in advance

client channel extended data AsyncReader

Hi,

I'm really liking the new async trait handlers!

I can turn a channel into a AsyncRead + AsyncWrite stream using Channel's into_stream() method. Could you also provide a method to stream a Channel's stderr? Possibly, it could be a AsyncReader for extended data for the channel, special cased for where the extended_code == 1.

I'm using russh-0.35.0-beta.8
Thanks

Unclear how to override `server::Handler` fns that use `session.channels`

Basically what the title says. I'm just toying with Russh to learn its API but I realized copypasting the default server::Handler trait implementations is impossible since some of them use session.channels which is pub(crate) only.

E.g. the default server::Handler::channel_eof does this:

if let Some(chan) = session.channels.get(&channel) {
    chan.send(ChannelMsg::Eof).unwrap_or(())
}

It's also unclear why this is there at all. Maybe I missed something but as far as I can tell ChannelMsg::Eof is not used anywhere else and I cannot do chan.wait() in my server code since I cannot access the channel in the first place?

Not sure if this is my fault and I'm missing something, or maybe there's some clarification missing the API docs.

EDIT: Is this the same as doing session.handle().eof(channel_id)? If so, could the default trait method implementations use the public interface instead? Maybe it's me but I think it's very common to copy-paste default implementations when implementing traits.

EDIT2: Something similar happens in server::Handler::window_adjusted which accesses session.common, also pub(crate).

EDIT3: To clarify, this is while using v0.35.0-beta.8.

Question for `impl PartialEq for PublicKey`

Hello!

After spending a whole day debugging this, I've realized that the SignatureHash of PublicKey::RSA gets compared as part of PartialEq:

fn main() {
    let k = russh_keys::key::KeyPair::generate_rsa(2048, russh_keys::key::SignatureHash::SHA2_512).unwrap();

    let kpub = k.clone_public_key().unwrap();

    let mut k2 = k.clone();

    // change the SignatureHash of k2
    if let russh_keys::key::KeyPair::RSA { ref mut hash, .. } = k2 {
        *hash = russh_keys::key::SignatureHash::SHA2_256;
    }

    let kpub2 = k2.clone_public_key().unwrap();
    
    assert_eq!(kpub, kpub2); // panics: kpub != kpub2 because of the hash
}

Which struck me as counter-intuitive, so I was wondering, is this intended? And if it is, could we at least have a method like:

impl PublicKey {
    pub fn key_eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::RSA { key: a, .. }, Self::RSA { key: b, .. }) => a == b,
            (Self::Ed25519(a), Self::Ed25519(b)) => a == b,
            _ => false,
        }
    }
}

Channel.window_size can overflow on add

Consider the following field:

https://github.com/warp-tech/russh/blob/49bf9599637d492f0aa17377b9c66cf953cb0824/russh/src/channels.rs#L113

This field is meant to indicate current window_size on the Channel.

However, I've been able to hit a case where the u32 overflows on add, which doesn't seem correct. Here's the scenario:

  1. Push a large amount of data to an OpenSSH server
  2. Enter the send_data loop of the channel
  3. Eventually the window_size will become 0. When it does, we enter this loop to wait for an adjustment and reset the window_size.
  4. This continues until the data has all been pushed.
  5. After data returns, call Channel.wait in a loop waiting for ChannelMsg::Data to come through from the server.

The issue I am seeing is that OpenSSH has sent a large amount of ChannelMsg::WindowAdjusted messages during step 4.
send_data, does not attempt to process these, it only takes the first one it finds and leaves the rest in the channel.
While waiting for ChannelMsg::Data in step 5, the remaining ChannelMsg::WindowAdjusted are read out of the channel first.

Each CHANNEL_WINDOW_ADJUST coming from the server first goes through here:

https://github.com/warp-tech/russh/blob/49bf9599637d492f0aa17377b9c66cf953cb0824/russh/src/client/encrypted.rs#L489-L505

This code takes the existing Channel.recipient_window_size and adds the new adjustment amount to it. This resulting value is then passed into window_adjusted where it is used to build a new ChannelMsg::WindowAdjusted here:

https://github.com/warp-tech/russh/blob/49bf9599637d492f0aa17377b9c66cf953cb0824/russh/src/client/mod.rs#L1450-L1465

Finally, the wait method handles these messages by adding the entire Channel.recipient_window_size to the existing Channel.window_size here:

https://github.com/warp-tech/russh/blob/49bf9599637d492f0aa17377b9c66cf953cb0824/russh/src/channels.rs#L347-L356

Repeatedly adding the Channel.recipient_window_size to the Channel.window_size can overflow the u32 and cause a panic (in debug mode), or an two's complement wrapping (in release mode). I'm wondering if instead of self.window_size += new_size, we should do self.window_size = new_size since we are really passing in the value of Channel.recipient_window_size instead of some amount of byte adjustment from CHANNEL_WINDOW_ADJUST.

Help Wanted: Using Russh to execute multiple commands

For https://github.com/mTsBucy1/async-ssh2-tokio-fork I would like to be able to sequentially execute many commands on the same host (See also Miyoshi-Ryota/async-ssh2-tokio#12).

Currently we create one Handler Implementation and call connect to get a Session Handle and authenticate this.
We then call channel_open_session() and channel.exec for every command that needs execution.
This was done, because calling channel.exec multiple times has failed.

However, after exactly 10 invocations, the session gets disconnected (presumably by the server). This was demonstrated on a Windows OpenSSH server and a Ubuntu ssh server.

I've tried looking into the russh code to understand the issue, but without success. The one thing I tried, was to override Handler::channel_close to also send a session.close(channel); packet back to the server. This was unsuccessful, but I also found no reference of a channel ever getting closed any other way.

Is this an inherit limitation of the session and do we need to reconnect and/or reauthenticate every 10 invocations? Or is there something we missed?
If someone with more SSH knowledge could point me in the right direction that would be amazing.
If you need a minimal reproducable example or further explanations from my side, please ask, but I maybe someone here knows of a approach to solve this.

"No common key algorithm" error when connect to ssh server

Hello,

I try the example code in async-ssh2-tokio (link), but failed with below error:

Error: Other Error Happen

Caused by:
No common key algorithm

The server is ok as I can ssh it by "ssh" command in linux, and if I try the same code to ssh a raspberry pi, it also works. I searched in google, and try to compare the difference :

When check the key_exchange_init message in wireshark:

for my unit:
server_host_key_algorithms string: rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp256

for raspberry pi:
server_host_key_algorithms string: rsa-sha2-512,rsa-sha2-256,ssh-rsa,ecdsa-sha2-nistp256,ssh-ed25519

So it seems there is something different, does anyone know what was wrong? How can I fix it in the client side?

Best Regards,
Hermes

Unclear difference between `Channel` and `Session`

Currently the comments on both are very autological and it seems deeply unobvious to me which one of them is for what. That resulted in the following questions arising:

  1. Which one can I use to uniquely identify a connected client?
  2. They both have IDs, are they unique?
  3. Why is ChannelID a vocabulary type and Session's id is not?
  4. Should all data from all connections of the same session be syphoned into a single Sender for app-level logic?

Perhaps they are in the documentation or require external knowledge of the SSH protocol in general, so I would be grateful to know where I can learn more. Maybe also make note of all mentioned above in the documentation too.

At which point is it possible to send a prompt to the client in a Server implementation?

Hi (again), I've tried to read the docs and the code but still failed in my intent. I'm implementing an honeypot server and I want to send a fake shell prompt (or any other string) to new clients as soon as they start the session, before they start sending commands.

I've tried here since it's one of the few points where the Session object is mutable, but the client never displays it https://github.com/evilsocket/medusa/blob/main/src/protocols/ssh/handler.rs#L214 while it works fine in the data handler and/or after an exect_request.

Thanks

`channel_open_session` not returning

I have the following code for a Client:

pub struct Client {
    handle: Handle<Handler>,
}

pub struct Channel {
    inner: russh::Channel<Msg>,
}

impl Client {
    pub async fn init(remote: &str, user: &str, key: impl AsRef<Path>) -> eyre::Result<Self> {
        let config = Arc::new(ClientConfig {
            connection_timeout: Some(Duration::from_secs(5)),
            ..Default::default()
        });
        let mut handle = client::connect(config, remote, Handler).await?;
        let key = Arc::new(load_secret_key(key, None)?);
        let res = handle.authenticate_publickey(user, key).await?;
        if !res {
            return Err(eyre::eyre!("Server rejected our SSH publickey"));
        }
        Ok(Self { handle })
    }

    pub async fn new_channel(&mut self) -> eyre::Result<Channel> {
        dbg!("a");
        let inner = self.handle.channel_open_session().await?;
        dbg!("b");
        Ok(Channel { inner })
    }
}

When running

    let mut ssh_client = ssh::Client::init(
        &env::var("REMOTE_ADDR")?,
        &env::var("SSH_USER")?,
        &env::var("SSH_KEY")?,
    )
    .await?;
    let mut channel = ssh_client.new_channel().await?;

the "a" gets printed, but the "b" does not.
I have implemented the method in the Handler for printing out the channel open confirmation, with the logs containing this:

[snip]
2022-12-19T11:02:19.625093Z DEBUG russh::client::encrypted: userauth_success    
[src/ssh.rs:75] "a" = "a"
2022-12-19T11:02:19.625233Z DEBUG russh::cipher: writing, seqn = 6    
2022-12-19T11:02:19.625251Z DEBUG russh::cipher: padding length 7    
2022-12-19T11:02:19.625259Z DEBUG russh::cipher: packet_length 32    
2022-12-19T11:02:19.649032Z DEBUG russh::cipher: reading, len = [191, 235, 165, 118]    
2022-12-19T11:02:19.649065Z DEBUG russh::cipher: reading, seqn = 7    
2022-12-19T11:02:19.649152Z DEBUG russh::cipher: reading, clear len = 624    
2022-12-19T11:02:19.649164Z DEBUG russh::cipher: read_exact 628    
2022-12-19T11:02:19.649178Z DEBUG russh::cipher: read_exact done    
2022-12-19T11:02:19.649533Z DEBUG russh::cipher: reading, padding_length 4    
2022-12-19T11:02:19.649616Z  WARN russh::client::encrypted: Unhandled global request: Ok("[email protected]") 0    
2022-12-19T11:02:19.649634Z DEBUG russh::cipher: writing, seqn = 7    
2022-12-19T11:02:19.649642Z DEBUG russh::cipher: padding length 14    
2022-12-19T11:02:19.649649Z DEBUG russh::cipher: packet_length 16    
2022-12-19T11:02:19.649841Z DEBUG russh::cipher: reading, len = [131, 217, 96, 50]    
2022-12-19T11:02:19.649864Z DEBUG russh::cipher: reading, seqn = 8    
2022-12-19T11:02:19.649921Z DEBUG russh::cipher: reading, clear len = 136    
2022-12-19T11:02:19.649929Z DEBUG russh::cipher: read_exact 140    
2022-12-19T11:02:19.649938Z DEBUG russh::cipher: read_exact done    
2022-12-19T11:02:19.650096Z DEBUG russh::cipher: reading, padding_length 7    
2022-12-19T11:02:19.650120Z DEBUG russh::cipher: reading, len = [219, 50, 139, 30]    
2022-12-19T11:02:19.650129Z DEBUG russh::cipher: reading, seqn = 9    
2022-12-19T11:02:19.650200Z DEBUG russh::cipher: reading, clear len = 136    
2022-12-19T11:02:19.650211Z DEBUG russh::cipher: read_exact 140    
2022-12-19T11:02:19.650225Z DEBUG russh::cipher: read_exact done    
2022-12-19T11:02:19.650451Z DEBUG russh::cipher: reading, padding_length 7    
2022-12-19T11:02:19.672871Z DEBUG russh::cipher: reading, len = [89, 113, 103, 91]    
2022-12-19T11:02:19.672897Z DEBUG russh::cipher: reading, seqn = 10    
2022-12-19T11:02:19.672984Z DEBUG russh::cipher: reading, clear len = 40    
2022-12-19T11:02:19.672995Z DEBUG russh::cipher: read_exact 44    
2022-12-19T11:02:19.673010Z DEBUG russh::cipher: read_exact done    
2022-12-19T11:02:19.673173Z DEBUG russh::cipher: reading, padding_length 6    
2022-12-19T11:02:19.673207Z DEBUG russh::client::encrypted: channel_open_confirmation    
2022-12-19T11:02:19.673251Z DEBUG ssh_worker::ssh: Got channel open confirmation for channel ChannelId(2)

After this the program seems to freeze.
I tried connecting to both a remote Debian server as well as a local linuxserver/openssh-server docker container.
Anyone know what might be going wrong here?

Ability for server to support concurrent exec?

Using the russh client (async_ssh2_tokio) I'm able to connect to an OpenSSH server and run multiple exec at the same time. For example, I can exec sleep 20 first, then in a different tokio task, exec again on the same connection to run uname -a and it works just fine.

I'm trying to get the same thing working with the russh server. I've narrowed it down to server trait method exec_request, and honestly, a lot of the methods exhibit the same problem.

async fn exec_request(
        self,
        channel: ChannelId,
        data: &[u8],
        mut session: Session,
    ) -> Result<(Self, Session), Self::Error> {

The method has to return self and session, so session can't be moved or cloned to another task. If I run the shell command, get the output, send it back, send the exit code, and close the channel before I return, it works but it holds up all other execs from running.
I need the session (and ChannelId) so the shell output can be sent back after I spawn a task for it, but I can't figure out a way to do that.

I imagine you'd have the same problem for almost any of the other channel functions as well. For example, you start up an interactive shell, the request_shell would need to spawn a process and map the I/O, there is no possible way for it to finish before returning.

I apologize if I'm missing some Rust way of doing this, but I've been struggling with this for days and can't find any similar examples.

Server: Handle::channel_open_forwarded_tcpip stuck when client fails to connect to remote host

Description

Hi, first of all thank you for this great project.
While implementing remote port forwarding (ssh -R), I got this issue: if client fails to connect to the designated service, it replies to server with CHANNEL_OPEN_FAILURE (92u8).

My issue is that I'm waiting for this reply to continue the process. But it seems that the server drops it.

In the server logs, I found this: src/server/encrypted.rs:897: unknown message received: Some(92).
I assume this is the packet I'm waiting for with Handle::channel_open_forwarded_tcpip.

Environment

  • russh: "0.36.1" (from crates.io)
  • logs gathered using tracing-log

Please tell me if more details are required. Thanks by advance for your help.

Potential issue with handling multiple channels/premature EOF

I've had an issue I've been trying to debug this for a while now and hopefully try to upstream whatever fix it takes, but I think I am at my wit's end trying to debug at this point.

I have been using Russh to try to create a local port-forwarding utility that will end up being embedded into my application. However I am running into an issue. For this, I use the client module's session.channel_open_direct_tcpip method. For reference and reproduction, the utility (that works via CLI) can be found here.

The main issue is that when sending a large amount of requests via an ClientSession (and therefore via multiple open channels) there is a massive delay between sending the request and getting any sort of response. (this is not a server issue, it works perfectly fine when running over OpenSSH).

You won't see this behavior being exhibited (in my utility) when you send a single curl request over the session (therefore opening and closing one channel), but it will be noticeable if you try to open a webpage over the connection that sends >5 or 6 XHR requests over the connection.

I have narrowed down the source of this slow down to be somewhat related to the select! macro in the Session run function, and suspect this has to do with many channels being opened but not being closed when a full TCP stream is sent back.

I figured out that OpenSSH actually just closes the channel when it cannot read from the input or output buffer any more, so I thought the fix for this would have been sending a channel EOF immediately after forwarding the local TCP stream to the remote (at around line 148 in the utility), but it seems the OpenSSH (or Russh?) takes this as a sign I want to close the channel and just drops it immediately.

Any help on even where to dig deeper would be much appreciated

Explain the reasons of the fork better

As the original author of the project, I would have appreciated a little email to explain why you weren't able to contribute your changes back.

Also, if you don't explain what "extra safety guarantees" you added, please remove that line in the readme: either you found safety problems in my code and you should have reported them, or you didn't, and you can't claim my code needed extra safety.

Can't establish SSH connection

I can't establish an SSH connection using version v0.34.0

I have checked the examples, but they use key::PublicKey on the check_server_key and it is private, so I couldn't find a way to approve my remote key.

Any plan to support WebAssembly?

Hi,

Out of curiosity, is there any plan or desire to make russh more WebAssembly-friendly?

Currently I see a few dependencies that can’t be used in WebAssembly: tokio, libc and winapi.
Changing this might be significant work.

Is it something on your radar yet? Would you accept somewhat significant contributions towards this goal?

The problem with PuTTY Client

I run example:

cargo run -p russh --example echoserver

It works as expected using OpenSSH's Client.
However with PuTTY, no data is sent.

Client versions:

[...DEBUG russh::ssh_read] Ok("SSH-2.0-PuTTY_Release_0.78\r\n") 
[...DEBUG russh::ssh_read] Ok("SSH-2.0-OpenSSH_for_Windows_8.1\r\n")

Unable to connect to the "example server" from Ubuntu 18.04 and 20.04 SSH.

I'm using the example server from: https://docs.rs/russh/latest/russh/server/index.html but with a 60 second timeout. (Otherwise it exits after just one second.)

When I try: ssh localhost -p 2222 -v I see:

17:02 fadedbee@box ~ $ ssh localhost -p 2222 -v
OpenSSH_7.6p1 Ubuntu-4ubuntu0.7, OpenSSL 1.0.2n  7 Dec 2017
debug1: Reading configuration data /home/fadedbee/.ssh/config
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: Applying options for *
debug1: Connecting to localhost [127.0.0.1] port 2222.
debug1: Connection established.
debug1: identity file /home/fadedbee/.ssh/id_rsa type 0
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_rsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_dsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_dsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_ecdsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_ecdsa-cert type -1
debug1: identity file /home/fadedbee/.ssh/id_ed25519 type 3
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_ed25519-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.7
debug1: Remote protocol version 2.0, remote software version russh_0.36.1
debug1: no match: russh_0.36.1
debug1: Authenticating to localhost:2222 as 'fadedbee'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: [email protected]
debug1: kex: host key algorithm: ssh-ed25519
debug1: kex: server->client cipher: [email protected] MAC: <implicit> compression: none
debug1: kex: client->server cipher: [email protected] MAC: <implicit> compression: none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ssh-ed25519 SHA256:M2gsNaJCJk+mYTqsas4NrjYHYsbd5OwnFN2MlQ1RRwY
debug1: checking without port identifier
The authenticity of host '[localhost]:2222 ([127.0.0.1]:2222)' can't be established.
ED25519 key fingerprint is SHA256:M2gsNaJCJk+mYTqsas4NrjYHYsbd5OwnFN2MlQ1RRwY.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[localhost]:2222' (ED25519) to the list of known hosts.
debug1: rekey after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: rekey after 134217728 blocks
debug1: Skipping ssh-dss key fadedbee@box - not in PubkeyAcceptedKeyTypes
Connection closed by 127.0.0.1 port 2222

17:02 fadedbee@box ~ $ 

What do I need to add to the example server to allow SSH clients to connect?

How to get terminal type, size and WINCH signals?

Apologies if these are out of scope or already documented and I've missed it.

I'm successfully using example/echoserver.rs.

  • How do I see the client's terminal type?
  • How do I see the initial terminal width and height?
  • How do I get WINCH signals, or if that isn't the right thing for SSH, how do I get notifications of changes to the client terminal.

(It may be that all of these happen over SSH, rather than being part of SSH, and I need to look elsewhere.)

Add an example

Hi, could you add a working example directory for a client usage?

I'm a bit new to Rust and I got stuck with can't find crate for 'russh_keys'. A minimal repo or a directory which shows how use to this library, would be awesome, thanks

SSH-RSA key support

hello, I am using thrussh as an ssh client recently, but I encountered an older dropbear (v2014.63) that can only use diffie-hellman-group1-sha1 to login. Since thrussh is not implemented, I switched to russh, but I still encountered questions like:

russh = { version = "0.34.0-beta.8", features = ["openssl"]}
russh-keys = { version = "0.22.0-beta.4", features = ["openssl"]}
[2022-08-10T03:55:18Z DEBUG russh::ssh_read] read_ssh_id: reading
[2022-08-10T03:55:18Z DEBUG russh::ssh_read] read 256
[2022-08-10T03:55:18Z DEBUG russh::ssh_read] Err(Utf8Error { valid_up_to: 32, error_len: Some(1) })
[2022-08-10T03:55:18Z DEBUG russh::cipher] writing, seqn = 0
[2022-08-10T03:55:18Z DEBUG russh::cipher] padding length 9
[2022-08-10T03:55:18Z DEBUG russh::cipher] packet_length 700
[2022-08-10T03:55:18Z DEBUG russh::client] writing 704 bytes
[2022-08-10T03:55:18Z DEBUG russh::ssh_read] id 256 26
[2022-08-10T03:55:18Z DEBUG russh::ssh_read] read () bytes from id.buf
[2022-08-10T03:55:18Z DEBUG russh::cipher] reading, len = [0, 0, 1, 76]
[2022-08-10T03:55:18Z DEBUG russh::cipher] reading, seqn = 0
[2022-08-10T03:55:18Z DEBUG russh::cipher] reading, clear len = 332
[2022-08-10T03:55:18Z DEBUG russh::cipher] read_exact 336
[2022-08-10T03:55:18Z DEBUG russh::ssh_read] id 256 30
[2022-08-10T03:55:18Z DEBUG russh::ssh_read] read () bytes from id.buf
[2022-08-10T03:55:18Z DEBUG russh::ssh_read] id 256 256
[2022-08-10T03:55:18Z DEBUG russh::cipher] read_exact done
[2022-08-10T03:55:18Z DEBUG russh::cipher] reading, padding_length 8
[2022-08-10T03:55:18Z DEBUG russh::client::kex] client parse 323 [20, 164, 237, 215, 135, 217, 249, 251, 29, 131, 20, 194, 124, 170, 117, 190, 149, 0, 0, 0, 80, 100, 105, 102, 102, 105, 101, 45, 104, 101, 108, 108, 109, 97, 110, 45, 103, 114, 111, 117, 112, 49, 45, 115, 104, 97, 49, 44, 100, 105, 102, 102, 105, 101, 45, 104, 101, 108, 108, 109, 97, 110, 45, 103, 114, 111, 117, 112, 49, 52, 45, 115, 104, 97, 49, 44, 107, 101, 120, 103, 117, 101, 115, 115, 50, 64, 109, 97, 116, 116, 46, 117, 99, 99, 46, 97, 115, 110, 46, 97, 117, 0, 0, 0, 15, 115, 115, 104, 45, 114, 115, 97, 44, 115, 115, 104, 45, 100, 115, 115, 0, 0, 0, 61, 97, 101, 115, 49, 50, 56, 45, 99, 116, 114, 44, 51, 100, 101, 115, 45, 99, 116, 114, 44, 97, 101, 115, 50, 53, 54, 45, 99, 116, 114, 44, 97, 101, 115, 49, 50, 56, 45, 99, 98, 99, 44, 51, 100, 101, 115, 45, 99, 98, 99, 44, 97, 101, 115, 50, 53, 54, 45, 99, 98, 99, 0, 0, 0, 61, 97, 101, 115, 49, 50, 56, 45, 99, 116, 114, 44, 51, 100, 101, 115, 45, 99, 116, 114, 44, 97, 101, 115, 50, 53, 54, 45, 99, 116, 114, 44, 97, 101, 115, 49, 50, 56, 45, 99, 98, 99, 44, 51, 100, 101, 115, 45, 99, 98, 99, 44, 97, 101, 115, 50, 53, 54, 45, 99, 98, 99, 0, 0, 0, 18, 104, 109, 97, 99, 45, 115, 104, 97, 49, 44, 104, 109, 97, 99, 45, 109, 100, 53, 0, 0, 0, 18, 104, 109, 97, 99, 45, 115, 104, 97, 49, 44, 104, 109, 97, 99, 45, 109, 100, 53, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 4, 110, 111, 110, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[2022-08-10T03:55:18Z DEBUG russh::client::kex] extending []
[2022-08-10T03:55:18Z DEBUG russh::negotiation] Could not find common key algorithm, other side only supports Ok("ssh-rsa,ssh-dss"), we only support [Name("ssh-ed25519"), Name("rsa-sha2-256"), Name("rsa-sha2-512")]
[2022-08-10T03:55:18Z DEBUG russh::client] drop session

Can anyone help me with this question?

No disconnect handler when using exec_request

Behavior is different when using exec_request over a "normal" connection

$ ssh localhost -p2222 test

vs

$ ssh localhost -p2222

When adding the test parameter (which will be put into an exec_request) data isn't received per-keystroke anymore, but only once per new-line, and a ctrl+c on the client side doesn't trigger disconnects or channel close or anything.

How or when do I know on the server when a client closed / disconnected the session when they supply an argument using exec_request?

Connection timeout in client doesn't take effect

 let config = client::Config {
        connection_timeout: Some(Duration::from_secs(5)),
        ..<_>::default()
 };

When I connect to a ssh server that is not existed, the time elapsed during connection with server is longer than connection_timeout.
Is it a useless field in client::Config?

Unable to connect with aes256gcm cipher

Hi,

I have an OpenSSH_8.0p1 server where I've disabled the [email protected] cipher so that russh will use the aes256gcm cipher instead.

Upon doing this, I get the following error on the openssh server when trying to open a session using a russh client:

ssh_dispatch_run_fatal: Connection from X.X.X.X port 65175: message authentication code incorrect [preauth]

I am able to connect without issue if the [email protected] cipher is used instead.

The following client code can reproduce the issue:

extern crate env_logger;
extern crate futures;
extern crate russh;
extern crate russh_keys;
extern crate tokio;
use anyhow::Context;
use russh::*;
use russh_keys::*;
use std::net::SocketAddr;
use std::str::FromStr;
use std::sync::Arc;

struct Client {}

impl client::Handler for Client {
    type Error = russh::Error;
    type FutureUnit = futures::future::Ready<Result<(Self, client::Session), Self::Error>>;
    type FutureBool = futures::future::Ready<Result<(Self, bool), Self::Error>>;

    fn finished_bool(self, b: bool) -> Self::FutureBool {
        futures::future::ready(Ok((self, b)))
    }
    fn finished(self, session: client::Session) -> Self::FutureUnit {
        futures::future::ready(Ok((self, session)))
    }
    fn check_server_key(self, server_public_key: &key::PublicKey) -> Self::FutureBool {
        println!("check_server_key: {:?}", server_public_key);
        self.finished_bool(true)
    }
}

#[tokio::main]
async fn main() {
    env_logger::init();
    let config = russh::client::Config::default();

    let config = Arc::new(config);
    let sh = Client {};

    let mut session =
        russh::client::connect(config, SocketAddr::from_str("X.X.X.X:22").unwrap(), sh)
            .await
            .unwrap();

    let x = session
        .authenticate_password("XX", "XX")
        .await
        .unwrap();

    dbg!(x);

    let mut ch = session.channel_open_session().await.unwrap();
}

Server Graceful Shutdown

Currently, russh doesn't provide a graceful shutdown mechanism. Graceful shutdowns are especially important in cloud environments to allow instances to be shut down without impacting users.

The following should happen on shutdown:

  1. Stop listening accepting new connections
  2. Notify application code that a shutdown has been triggered
  3. Allow application code to notify that shutdown has been complete
  4. Wait for the existing connections to close
  5. If application code misbehaves and doesn't close the connection within a certain period of time, close the connection

Client Keyboard Interactive

Do you have any suggestions for logging via keyboard interactive? I have a usecase for logging in via keyboard-interactive with the password. Does russh currently support something like that or is there changes that would need to be made to russh to facilitate such a usecase?

For example, Python's Paramiko attempts keyboard-interactive with the password if the normal password authentication fails. I imagine we don't want to do that, but it would be nice to have the methods to facilitate such a workflow when users implement the client. Or perhaps do what paramiko does

Unable to load keys with a passphrase

I have been unable to load any ed25519 key generated with ssh-keygen that has a passphrase.

I am not using russh generated keys because openssh has trouble with them.

I have tried the default, PEM, PKCS8 and RFC4716. Stepping through the code, it seems like its trying to match "ssh-ed25519" and not finding it before it even makes use of the passphrase.

russh-keys version 0.24.1

NOTE: i also tried keys generated by the rust create ssh-key and had the same problem

#!/bin/bash

for KFORMAT in RFC4716 PKCS8 PEM  ; do
    for KTYPE in ed25519 ; do        
        rm -f "test.$KTYPE.$KFORMAT"*
        ssh-keygen -t $KTYPE -m $KFORMAT -q -f "test.$KTYPE.$KFORMAT" -N ""
        ssh-keygen -t $KTYPE -m $KFORMAT -q -f "test.$KTYPE.$KFORMAT.prot" -N "test"
    done
done

for KTYPE in ed25519 ; do    
    rm -f "test.$KTYPE.default"*    
    ssh-keygen -t $KTYPE  -q -f "test.$KTYPE.default" -N ""
    ssh-keygen -t $KTYPE  -q -f "test.$KTYPE.default.prot" -N "test"
done
    let mut unprot_key_files: [&str; 4] = [
        "test.ed25519.PEM",
        "test.ed25519.PKCS8",
        "test.ed25519.RFC4716",
        "test.ed25519.default"
    ];

    let mut prot_key_files: [&str; 4] = [
        "test.ed25519.RFC4716.prot",
        "test.ed25519.PEM.prot",
        "test.ed25519.PKCS8.prot",
        "test.ed25519.default.prot"
    ];

    for key_file in &unprot_key_files {
        let r = russh_keys::load_secret_key(key_file, None);
        if r.is_ok() {
            println!("{}: OK", key_file);
        } else {
            println!("{}: {:?}", key_file, r);
        }
    }

    for key_file in &prot_key_files {
        let r = russh_keys::load_secret_key(key_file, Some("test"));
        if r.is_ok() {
            println!("{}: OK", key_file);
        } else {
            println!("{}: {:?}", key_file, r);
        }
    }
test.ed25519.PEM: OK
test.ed25519.PKCS8: OK
test.ed25519.RFC4716: OK
test.ed25519.default: OK
test.ed25519.RFC4716.prot: Err(UnsupportedKeyType([115, 115, 104, 45, 214, 124, 73, 235, 44, 74, 208]))
test.ed25519.PEM.prot: Err(UnsupportedKeyType([115, 115, 104, 45, 86, 111, 219, 130, 73, 11, 89]))
test.ed25519.PKCS8.prot: Err(UnsupportedKeyType([115, 115, 104, 45, 211, 69, 184, 138, 143, 192, 161]))
test.ed25519.default.prot: Err(UnsupportedKeyType([115, 115, 104, 45, 81, 109, 54, 171, 75, 57, 218]))

Weird issue when sending a message from a server on an SSH connection

I'm using this library's server portion, and the following code for the Handler trait is producing some weird output:

fn shell_request(self, channel: ChannelId, mut session: Session) -> Self::FutureUnit {
    session.data(channel, "Interactive shell is disabled.\n".to_string().into());
    session.close(channel);
    self.finished(session)
}

For some reason this is showing the following output in my terminal when connecting to the server:

Interactive shell is disabled.
                              Connection to dev.hunterwittenborn.com closed.

I'm not sure how related to the library this is, but any idea what might be causing it?

FIPS "OpenSSL only" mode

One cloud on the horizon for our usage of this library is the likelihood of needing to be FIPS certifiable. Long story short, this essentially means having all cryptography done by a dynamically linked OpenSSL library--which in turn is FIPS certified. In this case, we would use OpenSSL for the cryptography currently done by crates like aes and pbkdf2.

I will (attempt to) implement this in our fork of the library, but I was wondering if it's something there'd be interest in merging upstream as a feature flag. Understanding that dependency on OpenSSL is not greatly desirable and that this would increase the complexity of the library for something a minority of consumers will ever need 😛

Unable to use 0 as port in tcpip_forward on server

A client is able to request a port assignment from the server using

$ ssh -R 0:localhost:3000 localhost -p2222

but because this requires the server to reply with the allocated port, this currently fails with a

ssh_confirm_remote_forward: parse packet: incomplete message

because the return type of tcpip_forward(..) is FutureBool, it doesn't include said port number.

SSH RFC specifying port 0

Is there a way that allows me to send the correct response?

Native support for subsystems

Would you like to add native support for subsystems such as SFTP and etc.? At the moment, the lib does not provide simple "out-of-the-box" implementations of server-side and client-side subsytems. I could take the time to integrate my ready implementation under russh, since I have already taken the time to implement the SFTP protocol on the server side. Are you interested in PR?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.