Russh Documentation

repository·main·Indexed 23 days ago

https://github.com/eugeny/russh

A low-level, asynchronous SSH2 client and server implementation built on the Tokio runtime. Russh supports a wide range of SSH primitives, including port forwarding (direct-tcpip, forward-tcpip, streamlocal), various ciphers, key exchanges, MACs, and authentication methods such as public key and OpenSSH certificates. The ecosystem includes russh-config for parsing SSH configuration files, russh-sftp for SFTP subsystem support, and PageantStream for Windows agent communication via named pipes or WM_Message.

Tokens
11.4K
Snippets
11
Records
77
Agent score
82%

What's inside Russh

  1. Overview of Russh features and supported protocols

    main

    Russh is a low-level Tokio-based SSH2 client and server implementation. It supports a wide range of SSH primitives including:

    Port Forwarding

    • direct-tcpip (local port forwarding)
    • forward-tcpip (remote port forwarding)
    • direct-streamlocal (local UNIX socket forwarding, client only)
    • forward-streamlocal (remote UNIX socket forwarding)

    Supported Ciphers

    • chacha20-poly1305@openssh.com
    • aes128-gcm@openssh.com, aes256-gcm@openssh.com
    • aes128-ctr, aes192-ctr, aes256-ctr
    • aes128-cbc, aes192-cbc, aes256-cbc, 3des-cbc

    Key Exchanges

    • curve25519-sha256@libssh.org
    • diffie-hellman-group-sha1 (GEX)
    • diffie-hellman-group1-sha1, diffie-hellman-group14-sha1
    • diffie-hellman-group-sha256 (GEX), diffie-hellman-group14-sha256, diffie-hellman-group16-sha512
    • ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521

    MACs

    • hmac-sha1, hmac-sha2-256, hmac-sha2-512
    • hmac-sha1-etm@openssh.com, hmac-sha2-256-etm@openssh.com, hmac-sha2-512-etm@openssh.com

    Authentication

    • Host keys & Public key auth: ssh-ed25519, rsa-sha2-256, rsa-sha2-512, ssh-rsa, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521
    • Methods: password, publickey, keyboard-interactive, none, and OpenSSH certificates.
  2. Integrate SFTP support with Russh

    main
    For server-side and client-side SFTP subsystem support, use the russh-sftp crate. You can find implementation details in the sftp_server.rs and sftp_client.rs examples within the repository.
  3. Explore Russh examples

    main

    Russh provides several reference implementations to help you get started with client and server development:

    • Simple Client: russh/examples/client_exec_simple.rs
    • Interactive PTY Client: russh/examples/client_exec_interactive.rs
    • Server: russh/examples/echoserver.rs
    • SFTP Client: russh/examples/sftp_client.rs
    • SFTP Server: russh/examples/sftp_server.rs
  4. Configure crypto backends for Russh

    main
    Russh requires at least one crypto backend to be enabled during compilation. If both aws-lc-rs and ring features are disabled, the crate will fail to compile. You must enable at least one of these features in your Cargo.toml to use Russh.
  5. Handle channel-open requests with ChannelOpenHandle

    main

    When a client requests to open a channel, you are provided with a ChannelOpenHandle. You must use this handle to either accept or reject the request.

    Warning: If you drop the ChannelOpenHandle without calling either method, the server will automatically send an AdministrativelyProhibited rejection to the client.

  6. Implement the Server trait to accept connections

    main

    To build an SSH server with Russh, you must implement the Server trait. This trait defines how new client handlers are created and how session errors are managed. Once implemented, you can use run_on_socket or run_on_address to start the server loop.

    There are two primary patterns for accepting connections:

    1. Automatic Management: Implement Server and use run_on_socket (if you have a TcpListener) or run_on_address (to bind to an address). Russh will handle the listener loop and connection lifecycle.
    2. Manual Management: Accept connections yourself and pass them to run_stream to handle the SSH protocol for that specific stream.
    #[async_trait::async_trait]
    pub trait Server {
        type Handler: Handler + Send + 'static;
    
        /// Called when a new client connects.
        fn new_client(&mut self, peer_addr: Option<std::net::SocketAddr>) -> Self::Handler;
    
        /// Called when an active connection fails.
        fn handle_session_error(&mut self, _error: <Self::Handler as Handler>::Error) {}
    
        // ... run_on_socket and run_on_address methods
    }
  7. How to use Russh for clients and servers

    main

    Russh is an asynchronous SSH library based on tokio/futures. The primary way to use the library is by implementing custom handlers:

    • For SSH clients: Implement the russh::client::Handler trait.
    • For SSH servers: Implement the russh::server::Handler trait.

    Detailed guides for each are available in the russh::client and russh::server modules.

  8. Create and sign with SSH certificates

    main
    Russh supports SSH certificates. You can create a certificate using a Certificate Authority (CA) key to sign a user key. Once created, a certificate can be used as an identity in an AgentClient to perform signing requests. When signing with a certificate, the sign_request method returns a buffer where the signature is appended to the original data.
  9. How SSH channels work in Russh

    main

    The SSH protocol uses channels (represented by integers) to allow multiple parallel requests over a single connection.

    To perform a simple command execution (similar to running a single command in a shell), a client typically follows this lifecycle:

    1. Call client::Connection::channel_open_session to obtain a ChannelId.
    2. Call client::Connection::exec to request command execution.
    3. (Optional) Call client::Connection::data multiple times to send data to the command's standard input.
    4. Call client::Connection::channel_eof and client::Connection::channel_close to terminate the channel.
  10. Understand the Names struct in SSH negotiation

    main

    The Names struct represents the result of a successful SSH handshake negotiation. It contains the specific algorithms that were agreed upon by both parties.

    Key fields include:

    • kex: The negotiated key exchange algorithm.
    • key: The negotiated host/public key algorithm.
    • cipher: The negotiated symmetric cipher.
    • client_mac / server_mac: The negotiated MAC algorithms for each direction.
    • client_compression / server_compression: The negotiated compression algorithms.
    • strict_kex: A boolean indicating if strict key exchange was negotiated (relevant for OpenSSH extensions).
    • ignore_guessed: A boolean used to determine if the next packet should be ignored (occurs when a party optimistically guesses a KEX algorithm that was not actually selected).
  11. Implement the Handler trait to manage SSH events

    main

    The Handler trait is the core of your SSH server logic. Each connected client gets its own instance of a type implementing Handler. You must implement this trait to respond to various SSH protocol events, such as authentication requests, channel openings, and data transfers.

    Key event categories include:

    • Authentication: auth_none, auth_password, auth_publickey, auth_openssh_certificate, and auth_keyboard_interactive.
    • Channel Management: channel_open_session, channel_open_direct_tcpip, channel_close, and channel_eof.
    • Data Handling: data (for incoming packets) and extended_data (for stderr/other streams).
    • Requests: pty_request, shell_request, exec_request, and subsystem_request.

    Note: For many request types (like pty_request or shell_request), you must explicitly communicate success or failure to the client by calling session.channel_success(channel) or session.channel_failure(channel) within the handler method.

    #[cfg_attr(feature = "async-trait", async_trait::async_trait)]
    pub trait Handler: Sized {
        type Error: From<crate::Error> + Send;
    
        // Example authentication method
        fn auth_password(
            &mut self,
            user: &str,
            password: &str,
        ) -> impl Future<Output = Result<Auth, Self::Error>> + Send;
    
        // Example channel request
        fn shell_request(
            &mut self,
            channel: ChannelId,
            session: &mut Session,
        ) -> impl Future<Output = Result<(), Self::Error>> + Send;
    
        // ... other methods
    }
  12. How Channel splitting and concurrency works

    main

    The Channel type can be split into a ChannelReadHalf and a ChannelWriteHalf using the split() method. This allows you to move the reading and writing logic into different tasks or threads, enabling full-duplex communication without needing to borrow the original Channel object.

    • ChannelReadHalf provides wait() and make_reader().
    • ChannelWriteHalf provides data(), exec(), signal(), and other command methods.