chore: checkpoint before Python removal

This commit is contained in:
2026-03-26 22:33:59 +00:00
parent 683cec9307
commit e568ddf82a
29972 changed files with 11269302 additions and 2 deletions

1
vendor/russh/.cargo-checksum.json vendored Normal file

File diff suppressed because one or more lines are too long

7
vendor/russh/.cargo_vcs_info.json vendored Normal file
View File

@@ -0,0 +1,7 @@
{
"git": {
"sha1": "a3ae9836e27111b5e1ad15ac5c84491907d21e86",
"dirty": true
},
"path_in_vcs": "russh"
}

2704
vendor/russh/Cargo.lock generated vendored Normal file

File diff suppressed because it is too large Load Diff

259
vendor/russh/Cargo.toml vendored Normal file
View File

@@ -0,0 +1,259 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2018"
rust-version = "1.65"
name = "russh"
version = "0.46.0"
authors = ["Pierre-Étienne Meunier <pe@pijul.org>"]
build = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A client and server SSH library."
homepage = "https://github.com/warp-tech/russh"
documentation = "https://docs.rs/russh"
readme = "README.md"
keywords = ["ssh"]
license = "Apache-2.0"
repository = "https://github.com/warp-tech/russh"
resolver = "2"
[package.metadata.docs.rs]
features = ["openssl"]
[lib]
name = "russh"
path = "src/lib.rs"
[[example]]
name = "client_exec_interactive"
path = "examples/client_exec_interactive.rs"
[[example]]
name = "client_exec_simple"
path = "examples/client_exec_simple.rs"
[[example]]
name = "echoserver"
path = "examples/echoserver.rs"
[[example]]
name = "ratatui_app"
path = "examples/ratatui_app.rs"
[[example]]
name = "ratatui_shared_app"
path = "examples/ratatui_shared_app.rs"
[[example]]
name = "sftp_client"
path = "examples/sftp_client.rs"
[[example]]
name = "sftp_server"
path = "examples/sftp_server.rs"
[[example]]
name = "test"
path = "examples/test.rs"
[[test]]
name = "test_data_stream"
path = "tests/test_data_stream.rs"
[dependencies.aes]
version = "0.8"
[dependencies.aes-gcm]
version = "0.10"
[dependencies.async-trait]
version = "0.1"
[dependencies.bitflags]
version = "2.0"
[dependencies.byteorder]
version = "1.4"
[dependencies.cbc]
version = "0.1"
[dependencies.chacha20]
version = "0.9"
[dependencies.ctr]
version = "0.9"
[dependencies.curve25519-dalek]
version = "4.1.3"
[dependencies.des]
version = "0.8.1"
[dependencies.digest]
version = "0.10"
[dependencies.elliptic-curve]
version = "0.13"
features = ["ecdh"]
[dependencies.flate2]
version = "1.0"
optional = true
[dependencies.futures]
version = "0.3"
[dependencies.generic-array]
version = "0.14"
[dependencies.hex-literal]
version = "0.4"
[dependencies.hmac]
version = "0.12"
[dependencies.log]
version = "0.4"
[dependencies.num-bigint]
version = "0.4"
features = ["rand"]
[dependencies.once_cell]
version = "1.13"
[dependencies.p256]
version = "0.13"
features = ["ecdh"]
[dependencies.p384]
version = "0.13"
features = ["ecdh"]
[dependencies.p521]
version = "0.13"
features = ["ecdh"]
[dependencies.poly1305]
version = "0.8"
[dependencies.rand]
version = "0.8"
[dependencies.rand_core]
version = "0.6.4"
features = ["getrandom"]
[dependencies.russh-cryptovec]
version = "0.7.0"
[dependencies.russh-keys]
version = "0.46.0"
[dependencies.russh-util]
version = "0.46.0"
[dependencies.sha1]
version = "0.10"
features = ["oid"]
[dependencies.sha2]
version = "0.10"
features = ["oid"]
[dependencies.ssh-encoding]
version = "0.2"
[dependencies.ssh-key]
version = "0.6"
features = [
"ed25519",
"rsa",
"encryption",
]
[dependencies.subtle]
version = "2.4"
[dependencies.thiserror]
version = "1.0"
[dependencies.tokio]
version = "1.17.0"
features = [
"io-util",
"sync",
"time",
]
[dev-dependencies.anyhow]
version = "1.0"
[dev-dependencies.clap]
version = "3.2"
features = ["derive"]
[dev-dependencies.env_logger]
version = "0.11"
[dev-dependencies.rand]
version = "0.8.5"
[dev-dependencies.ratatui]
version = "0.26.0"
[dev-dependencies.shell-escape]
version = "0.1"
[dev-dependencies.termion]
version = "2"
[dev-dependencies.tokio]
version = "1.17.0"
features = [
"io-std",
"io-util",
"rt-multi-thread",
"time",
"net",
"sync",
"macros",
]
[dev-dependencies.tokio-fd]
version = "0.3"
[features]
default = ["flate2"]
legacy-ed25519-pkcs8-parser = ["russh-keys/legacy-ed25519-pkcs8-parser"]
openssl = [
"russh-keys/openssl",
"dep:openssl",
]
vendored-openssl = [
"openssl/vendored",
"russh-keys/vendored-openssl",
]
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.openssl]
version = "0.10"
optional = true
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.russh-sftp]
version = "2.0.5"
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
version = "1.17.0"

182
vendor/russh/README.md vendored Normal file
View File

@@ -0,0 +1,182 @@
# Russh
[![Rust](https://github.com/warp-tech/russh/actions/workflows/rust.yml/badge.svg)](https://github.com/warp-tech/russh/actions/workflows/rust.yml) <!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-43-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Low-level Tokio SSH2 client and server implementation.
Examples: [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).
This is a fork of [Thrussh](https://nest.pijul.com/pijul/thrussh) by Pierre-Étienne Meunier.
> ✨ = added in Russh
* [More panic safety](https://github.com/warp-tech/russh#safety) ✨
* `async_trait` support ✨
* `direct-tcpip` (local port forwarding)
* `forward-tcpip` (remote port forwarding) ✨
* `direct-streamlocal` (local UNIX socket forwarding, client only) ✨
* `forward-streamlocal` (remote UNIX socket forwarding) ✨
* Ciphers:
* `chacha20-poly1305@openssh.com`
* `aes256-gcm@openssh.com`
* `aes256-ctr`
* `aes192-ctr`
* `aes128-ctr`
* `aes256-cbc`
* `aes192-cbc`
* `aes128-cbc`
* `3des-cbc`
* Key exchanges:
* `curve25519-sha256@libssh.org`
* `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:
* `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`
* 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.
* When `mlock`/`munlock` fails to protect sensitive data in memory.
### Unsafe code
* `cryptovec` uses `unsafe` for faster copying, initialization and binding to native API.
## Ecosystem
* [russh-sftp](https://crates.io/crates/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](https://crates.io/crates/async-ssh2-tokio) - simple high-level API for running commands over SSH.
## Adopters
* [HexPatch](https://github.com/Etto48/HexPatch) - A binary patcher and editor written in Rust with terminal user interface (TUI).
* Uses `russh::client` and `russh_sftp::client` to allow remote editing of files.
* [kartoffels](https://github.com/Patryk27/kartoffels) - A game where you're given a potato and your job is to implement a firmware for it
* Uses `russh:server` to deliver the game, using `ratatui` as the rendering engine.
* [kty](https://github.com/grampelberg/kty) - The terminal for Kubernetes.
* Uses `russh::server` to deliver the `ratatui` based TUI and `russh_sftp::server` to provide `scp` based file management.
* [lapdev](https://github.com/lapce/lapdev) - Self-Hosted Remote Dev Environment
* Uses `russh::server` to construct a proxy into your development environment.
* [medusa](https://github.com/evilsocket/medusa) - A fast and secure multi protocol honeypot.
* Uses `russh::server` to be the basis of the honyepot.
* [rebels-in-the-sky](https://github.com/ricott1/rebels-in-the-sky) - P2P terminal game about spacepirates playing basketball across the galaxy
* Uses `russh::server` to deliver the game, using `ratatui` as the rendering engine.
* [warpgate](https://github.com/warp-tech/warpgate) - Smart SSH, HTTPS and MySQL bastion that requires no additional client-side software
* Uses `russh::server` in addition to `russh::client` as part of the smart SSH functionality.
* [Devolutions Gateway](https://github.com/Devolutions/devolutions-gateway/) - Establish a secure entry point for internal or external segmented networks that require authorized just-in-time (JIT) access.
* Uses `russh::client` for the web-based SSH client of the standalone web application.
## Contributors ✨
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tbody>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mihirsamdarshi"><img src="https://avatars.githubusercontent.com/u/5462077?v=4?s=100" width="100px;" alt="Mihir Samdarshi"/><br /><sub><b>Mihir Samdarshi</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=mihirsamdarshi" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://peet.io/"><img src="https://avatars.githubusercontent.com/u/2230985?v=4?s=100" width="100px;" alt="Connor Peet"/><br /><sub><b>Connor Peet</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=connor4312" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kvzn"><img src="https://avatars.githubusercontent.com/u/313271?v=4?s=100" width="100px;" alt="KVZN"/><br /><sub><b>KVZN</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=kvzn" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.telekom.de"><img src="https://avatars.githubusercontent.com/u/21334898?v=4?s=100" width="100px;" alt="Adrian Müller (DTT)"/><br /><sub><b>Adrian Müller (DTT)</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=amtelekom" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.evilsocket.net"><img src="https://avatars.githubusercontent.com/u/86922?v=4?s=100" width="100px;" alt="Simone Margaritelli"/><br /><sub><b>Simone Margaritelli</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=evilsocket" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://joegrund.com"><img src="https://avatars.githubusercontent.com/u/458717?v=4?s=100" width="100px;" alt="Joe Grund"/><br /><sub><b>Joe Grund</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=jgrund" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/AspectUnk"><img src="https://avatars.githubusercontent.com/u/59799956?v=4?s=100" width="100px;" alt="AspectUnk"/><br /><sub><b>AspectUnk</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=AspectUnk" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://0io.eu"><img src="https://avatars.githubusercontent.com/u/203575?v=4?s=100" width="100px;" alt="Simão Mata"/><br /><sub><b>Simão Mata</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=simao" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://mariotaku.org"><img src="https://avatars.githubusercontent.com/u/830358?v=4?s=100" width="100px;" alt="Mariotaku"/><br /><sub><b>Mariotaku</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=mariotaku" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/yorkz1994"><img src="https://avatars.githubusercontent.com/u/16678950?v=4?s=100" width="100px;" alt="yorkz1994"/><br /><sub><b>yorkz1994</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=yorkz1994" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://volution.ro/"><img src="https://avatars.githubusercontent.com/u/29785?v=4?s=100" width="100px;" alt="Ciprian Dorin Craciun"/><br /><sub><b>Ciprian Dorin Craciun</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=cipriancraciun" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mllken"><img src="https://avatars.githubusercontent.com/u/11590808?v=4?s=100" width="100px;" alt="Eric Milliken"/><br /><sub><b>Eric Milliken</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=mllken" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Swelio"><img src="https://avatars.githubusercontent.com/u/24651896?v=4?s=100" width="100px;" alt="Swelio"/><br /><sub><b>Swelio</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=Swelio" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/joshbenz"><img src="https://avatars.githubusercontent.com/u/94999261?v=4?s=100" width="100px;" alt="Joshua Benz"/><br /><sub><b>Joshua Benz</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=joshbenz" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="http://homepage.ruhr-uni-bochum.de/Jan.Holthuis/"><img src="https://avatars.githubusercontent.com/u/1834516?v=4?s=100" width="100px;" alt="Jan Holthuis"/><br /><sub><b>Jan Holthuis</b></sub></a><br /><a href="#security-Holzhaus" title="Security">🛡️</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mateuszkj"><img src="https://avatars.githubusercontent.com/u/2494082?v=4?s=100" width="100px;" alt="mateuszkj"/><br /><sub><b>mateuszkj</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=mateuszkj" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://gotlou.srht.site"><img src="https://avatars.githubusercontent.com/u/23006870?v=4?s=100" width="100px;" alt="Saksham Mittal"/><br /><sub><b>Saksham Mittal</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=gotlougit" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://canoncollision.com"><img src="https://avatars.githubusercontent.com/u/5120858?v=4?s=100" width="100px;" alt="Lucas Kent"/><br /><sub><b>Lucas Kent</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=rukai" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/RDruon"><img src="https://avatars.githubusercontent.com/u/64585623?v=4?s=100" width="100px;" alt="Raphael Druon"/><br /><sub><b>Raphael Druon</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=RDruon" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nurrl"><img src="https://avatars.githubusercontent.com/u/15341887?v=4?s=100" width="100px;" alt="Maya the bee"/><br /><sub><b>Maya the bee</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=Nurrl" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/mmirate"><img src="https://avatars.githubusercontent.com/u/992859?v=4?s=100" width="100px;" alt="Milo Mirate"/><br /><sub><b>Milo Mirate</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=mmirate" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/george-hopkins"><img src="https://avatars.githubusercontent.com/u/552590?v=4?s=100" width="100px;" alt="George Hopkins"/><br /><sub><b>George Hopkins</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=george-hopkins" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://amcoff.net/"><img src="https://avatars.githubusercontent.com/u/17624114?v=4?s=100" width="100px;" alt="Åke Amcoff"/><br /><sub><b>Åke Amcoff</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=akeamc" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://brendonho.com"><img src="https://avatars.githubusercontent.com/u/12106620?v=4?s=100" width="100px;" alt="Brendon Ho"/><br /><sub><b>Brendon Ho</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=bho01" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://samlikes.pizza/"><img src="https://avatars.githubusercontent.com/u/226872?v=4?s=100" width="100px;" alt="Samuel Ainsworth"/><br /><sub><b>Samuel Ainsworth</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=samuela" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Sherlock-Holo"><img src="https://avatars.githubusercontent.com/u/10096425?v=4?s=100" width="100px;" alt="Sherlock Holo"/><br /><sub><b>Sherlock Holo</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=sherlock-holo" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/ricott1"><img src="https://avatars.githubusercontent.com/u/16502243?v=4?s=100" width="100px;" alt="Alessandro Ricottone"/><br /><sub><b>Alessandro Ricottone</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=ricott1" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/T0b1-iOS"><img src="https://avatars.githubusercontent.com/u/15174814?v=4?s=100" width="100px;" alt="T0b1-iOS"/><br /><sub><b>T0b1-iOS</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=T0b1-iOS" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://mecha.so"><img src="https://avatars.githubusercontent.com/u/4598631?v=4?s=100" width="100px;" alt="Shoaib Merchant"/><br /><sub><b>Shoaib Merchant</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=shoaibmerchant" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/gleason-m"><img src="https://avatars.githubusercontent.com/u/86493344?v=4?s=100" width="100px;" alt="Michael Gleason"/><br /><sub><b>Michael Gleason</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=gleason-m" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://ana.gelez.xyz"><img src="https://avatars.githubusercontent.com/u/16254623?v=4?s=100" width="100px;" alt="Ana Gelez"/><br /><sub><b>Ana Gelez</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=elegaanz" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/tomknig"><img src="https://avatars.githubusercontent.com/u/3586316?v=4?s=100" width="100px;" alt="Tom König"/><br /><sub><b>Tom König</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=tomknig" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://www.legaltile.com/"><img src="https://avatars.githubusercontent.com/u/45085843?v=4?s=100" width="100px;" alt="Pierre Barre"/><br /><sub><b>Pierre Barre</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=Barre" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://skutnik.page"><img src="https://avatars.githubusercontent.com/u/22240065?v=4?s=100" width="100px;" alt="Jean-Baptiste Skutnik"/><br /><sub><b>Jean-Baptiste Skutnik</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=spoutn1k" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://blog.packetsource.net/"><img src="https://avatars.githubusercontent.com/u/6276475?v=4?s=100" width="100px;" alt="Adam Chappell"/><br /><sub><b>Adam Chappell</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=packetsource" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/CertainLach"><img src="https://avatars.githubusercontent.com/u/6235312?v=4?s=100" width="100px;" alt="Yaroslav Bolyukin"/><br /><sub><b>Yaroslav Bolyukin</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=CertainLach" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://www.systemscape.de"><img src="https://avatars.githubusercontent.com/u/20155974?v=4?s=100" width="100px;" alt="Julian"/><br /><sub><b>Julian</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=JuliDi" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://saunter.org"><img src="https://avatars.githubusercontent.com/u/47992?v=4?s=100" width="100px;" alt="Thomas Rampelberg"/><br /><sub><b>Thomas Rampelberg</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=grampelberg" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://belak.io"><img src="https://avatars.githubusercontent.com/u/107097?v=4?s=100" width="100px;" alt="Kaleb Elwert"/><br /><sub><b>Kaleb Elwert</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=belak" title="Documentation">📖</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://garyguo.net"><img src="https://avatars.githubusercontent.com/u/4065244?v=4?s=100" width="100px;" alt="Gary Guo"/><br /><sub><b>Gary Guo</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=nbdd0121" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/irvingoujAtDevolution"><img src="https://avatars.githubusercontent.com/u/139169536?v=4?s=100" width="100px;" alt="irvingouj @ Devolutions"/><br /><sub><b>irvingouj @ Devolutions</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=irvingoujAtDevolution" title="Code">💻</a></td>
<td align="center" valign="top" width="14.28%"><a href="http://tonipeter.de"><img src="https://avatars.githubusercontent.com/u/4614215?v=4?s=100" width="100px;" alt="Toni Peter"/><br /><sub><b>Toni Peter</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=Tehforsch" title="Code">💻</a></td>
</tr>
<tr>
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Nathy-bajo"><img src="https://avatars.githubusercontent.com/u/73991674?v=4?s=100" width="100px;" alt="Nathaniel Bajo"/><br /><sub><b>Nathaniel Bajo</b></sub></a><br /><a href="https://github.com/Eugeny/russh/commits?author=Nathy-bajo" title="Code">💻</a></td>
</tr>
</tbody>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!

View File

@@ -0,0 +1,227 @@
///
/// Run this example with:
/// cargo run --example client_exec_interactive -- -k <private key path> <host> <command>
///
use std::convert::TryFrom;
use std::env;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use async_trait::async_trait;
use clap::Parser;
use log::info;
use russh::keys::*;
use russh::*;
use termion::raw::IntoRawMode;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::ToSocketAddrs;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();
// CLI options are defined later in this file
let cli = Cli::parse();
info!("Connecting to {}:{}", cli.host, cli.port);
info!("Key path: {:?}", cli.private_key);
info!("OpenSSH Certificate path: {:?}", cli.openssh_certificate);
// Session is a wrapper around a russh client, defined down below
let mut ssh = Session::connect(
cli.private_key,
cli.username.unwrap_or("root".to_string()),
cli.openssh_certificate,
(cli.host, cli.port),
)
.await?;
info!("Connected");
let code = {
// We're using `termion` to put the terminal into raw mode, so that we can
// display the output of interactive applications correctly
let _raw_term = std::io::stdout().into_raw_mode()?;
ssh.call(
&cli.command
.into_iter()
.map(|x| shell_escape::escape(x.into())) // arguments are escaped manually since the SSH protocol doesn't support quoting
.collect::<Vec<_>>()
.join(" "),
)
.await?
};
println!("Exitcode: {:?}", code);
ssh.close().await?;
Ok(())
}
struct Client {}
// More SSH event handlers
// can be defined in this trait
// In this example, we're only using Channel, so these aren't needed.
#[async_trait]
impl client::Handler for Client {
type Error = russh::Error;
async fn check_server_key(
&mut self,
_server_public_key: &key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
/// This struct is a convenience wrapper
/// around a russh client
/// that handles the input/output event loop
pub struct Session {
session: client::Handle<Client>,
}
impl Session {
async fn connect<P: AsRef<Path>, A: ToSocketAddrs>(
key_path: P,
user: impl Into<String>,
openssh_cert_path: Option<P>,
addrs: A,
) -> Result<Self> {
let key_pair = load_secret_key(key_path, None)?;
// load ssh certificate
let mut openssh_cert = None;
if openssh_cert_path.is_some() {
openssh_cert = Some(load_openssh_certificate(openssh_cert_path.unwrap())?);
}
let config = client::Config {
inactivity_timeout: Some(Duration::from_secs(5)),
..<_>::default()
};
let config = Arc::new(config);
let sh = Client {};
let mut session = client::connect(config, addrs, sh).await?;
// use publickey authentication, with or without certificate
if openssh_cert.is_none() {
let auth_res = session
.authenticate_publickey(user, Arc::new(key_pair))
.await?;
if !auth_res {
anyhow::bail!("Authentication (with publickey) failed");
}
} else {
let auth_res = session
.authenticate_openssh_cert(user, Arc::new(key_pair), openssh_cert.unwrap())
.await?;
if !auth_res {
anyhow::bail!("Authentication (with publickey+cert) failed");
}
}
Ok(Self { session })
}
async fn call(&mut self, command: &str) -> Result<u32> {
let mut channel = self.session.channel_open_session().await?;
// This example doesn't terminal resizing after the connection is established
let (w, h) = termion::terminal_size()?;
// Request an interactive PTY from the server
channel
.request_pty(
false,
&env::var("TERM").unwrap_or("xterm".into()),
w as u32,
h as u32,
0,
0,
&[], // ideally you want to pass the actual terminal modes here
)
.await?;
channel.exec(true, command).await?;
let code;
let mut stdin = tokio_fd::AsyncFd::try_from(0)?;
let mut stdout = tokio_fd::AsyncFd::try_from(1)?;
let mut buf = vec![0; 1024];
let mut stdin_closed = false;
loop {
// Handle one of the possible events:
tokio::select! {
// There's terminal input available from the user
r = stdin.read(&mut buf), if !stdin_closed => {
match r {
Ok(0) => {
stdin_closed = true;
channel.eof().await?;
},
// Send it to the server
Ok(n) => channel.data(&buf[..n]).await?,
Err(e) => return Err(e.into()),
};
},
// There's an event available on the session channel
Some(msg) = channel.wait() => {
match msg {
// Write data to the terminal
ChannelMsg::Data { ref data } => {
stdout.write_all(data).await?;
stdout.flush().await?;
}
// The command has returned an exit code
ChannelMsg::ExitStatus { exit_status } => {
code = exit_status;
if !stdin_closed {
channel.eof().await?;
}
break;
}
_ => {}
}
},
}
}
Ok(code)
}
async fn close(&mut self) -> Result<()> {
self.session
.disconnect(Disconnect::ByApplication, "", "English")
.await?;
Ok(())
}
}
#[derive(clap::Parser)]
#[clap(trailing_var_arg = true)]
pub struct Cli {
#[clap(index = 1)]
host: String,
#[clap(long, short, default_value_t = 22)]
port: u16,
#[clap(long, short)]
username: Option<String>,
#[clap(long, short = 'k')]
private_key: PathBuf,
#[clap(long, short = 'o')]
openssh_certificate: Option<PathBuf>,
#[clap(multiple = true, index = 2, required = true)]
command: Vec<String>,
}

View File

@@ -0,0 +1,158 @@
///
/// Run this example with:
/// cargo run --example client_exec_simple -- -k <private key path> <host> <command>
///
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
use anyhow::Result;
use async_trait::async_trait;
use clap::Parser;
use log::info;
use russh::keys::*;
use russh::*;
use tokio::io::AsyncWriteExt;
use tokio::net::ToSocketAddrs;
#[tokio::main]
async fn main() -> Result<()> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.init();
// CLI options are defined later in this file
let cli = Cli::parse();
info!("Connecting to {}:{}", cli.host, cli.port);
info!("Key path: {:?}", cli.private_key);
// Session is a wrapper around a russh client, defined down below
let mut ssh = Session::connect(
cli.private_key,
cli.username.unwrap_or("root".to_string()),
(cli.host, cli.port),
)
.await?;
info!("Connected");
let code = ssh
.call(
&cli.command
.into_iter()
.map(|x| shell_escape::escape(x.into())) // arguments are escaped manually since the SSH protocol doesn't support quoting
.collect::<Vec<_>>()
.join(" "),
)
.await?;
println!("Exitcode: {:?}", code);
ssh.close().await?;
Ok(())
}
struct Client {}
// More SSH event handlers
// can be defined in this trait
// In this example, we're only using Channel, so these aren't needed.
#[async_trait]
impl client::Handler for Client {
type Error = russh::Error;
async fn check_server_key(
&mut self,
_server_public_key: &key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
/// This struct is a convenience wrapper
/// around a russh client
pub struct Session {
session: client::Handle<Client>,
}
impl Session {
async fn connect<P: AsRef<Path>, A: ToSocketAddrs>(
key_path: P,
user: impl Into<String>,
addrs: A,
) -> Result<Self> {
let key_pair = load_secret_key(key_path, None)?;
let config = client::Config {
inactivity_timeout: Some(Duration::from_secs(5)),
..<_>::default()
};
let config = Arc::new(config);
let sh = Client {};
let mut session = client::connect(config, addrs, sh).await?;
let auth_res = session
.authenticate_publickey(user, Arc::new(key_pair))
.await?;
if !auth_res {
anyhow::bail!("Authentication failed");
}
Ok(Self { session })
}
async fn call(&mut self, command: &str) -> Result<u32> {
let mut channel = self.session.channel_open_session().await?;
channel.exec(true, command).await?;
let mut code = None;
let mut stdout = tokio::io::stdout();
loop {
// There's an event available on the session channel
let Some(msg) = channel.wait().await else {
break;
};
match msg {
// Write data to the terminal
ChannelMsg::Data { ref data } => {
stdout.write_all(data).await?;
stdout.flush().await?;
}
// The command has returned an exit code
ChannelMsg::ExitStatus { exit_status } => {
code = Some(exit_status);
// cannot leave the loop immediately, there might still be more data to receive
}
_ => {}
}
}
Ok(code.expect("program did not exit cleanly"))
}
async fn close(&mut self) -> Result<()> {
self.session
.disconnect(Disconnect::ByApplication, "", "English")
.await?;
Ok(())
}
}
#[derive(clap::Parser)]
#[clap(trailing_var_arg = true)]
pub struct Cli {
#[clap(index = 1)]
host: String,
#[clap(long, short, default_value_t = 22)]
port: u16,
#[clap(long, short)]
username: Option<String>,
#[clap(long, short = 'k')]
private_key: PathBuf,
#[clap(multiple = true, index = 2, required = true)]
command: Vec<String>,
}

121
vendor/russh/examples/echoserver.rs vendored Normal file
View File

@@ -0,0 +1,121 @@
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use russh::keys::*;
use russh::server::{Msg, Server as _, Session};
use russh::*;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(log::LevelFilter::Debug)
.init();
let config = russh::server::Config {
inactivity_timeout: Some(std::time::Duration::from_secs(3600)),
auth_rejection_time: std::time::Duration::from_secs(3),
auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)),
keys: vec![russh_keys::key::KeyPair::generate_ed25519()],
..Default::default()
};
let config = Arc::new(config);
let mut sh = Server {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
};
sh.run_on_address(config, ("0.0.0.0", 2222)).await.unwrap();
}
#[derive(Clone)]
struct Server {
clients: Arc<Mutex<HashMap<(usize, ChannelId), russh::server::Handle>>>,
id: usize,
}
impl Server {
async fn post(&mut self, data: CryptoVec) {
let mut clients = self.clients.lock().await;
for ((id, channel), ref mut s) in clients.iter_mut() {
if *id != self.id {
let _ = s.data(*channel, data.clone()).await;
}
}
}
}
impl server::Server for Server {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
self.id += 1;
s
}
fn handle_session_error(&mut self, _error: <Self::Handler as russh::server::Handler>::Error) {
eprintln!("Session error: {:#?}", _error);
}
}
#[async_trait]
impl server::Handler for Server {
type Error = russh::Error;
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().await;
clients.insert((self.id, channel.id()), session.handle());
}
Ok(true)
}
async fn auth_publickey(
&mut self,
_: &str,
key: &key::PublicKey,
) -> Result<server::Auth, Self::Error> {
dbg!(key);
Ok(server::Auth::Accept)
}
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
// Sending Ctrl+C ends the session and disconnects the client
if data == [3] {
return Err(russh::Error::Disconnect);
}
let data = CryptoVec::from(format!("Got data: {}\r\n", String::from_utf8_lossy(data)));
self.post(data.clone()).await;
session.data(channel, data);
Ok(())
}
async fn tcpip_forward(
&mut self,
address: &str,
port: &mut u32,
session: &mut Session,
) -> Result<bool, Self::Error> {
let handle = session.handle();
let address = address.to_string();
let port = *port;
tokio::spawn(async move {
let channel = handle
.channel_open_forwarded_tcpip(address, port, "1.2.3.4", 1234)
.await
.unwrap();
let _ = channel.data(&b"Hello from a forwarded port"[..]).await;
let _ = channel.eof().await;
});
Ok(true)
}
}

212
vendor/russh/examples/ratatui_app.rs vendored Normal file
View File

@@ -0,0 +1,212 @@
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use ratatui::Terminal;
use russh::keys::key::PublicKey;
use russh::server::*;
use russh::{Channel, ChannelId};
use tokio::sync::Mutex;
type SshTerminal = Terminal<CrosstermBackend<TerminalHandle>>;
struct App {
pub counter: usize,
}
impl App {
pub fn new() -> App {
Self { counter: 0 }
}
}
#[derive(Clone)]
struct TerminalHandle {
handle: Handle,
// The sink collects the data which is finally flushed to the handle.
sink: Vec<u8>,
channel_id: ChannelId,
}
// The crossterm backend writes to the terminal handle.
impl std::io::Write for TerminalHandle {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.sink.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
let handle = self.handle.clone();
let channel_id = self.channel_id;
let data = self.sink.clone().into();
futures::executor::block_on(async move {
let result = handle.data(channel_id, data).await;
if result.is_err() {
eprintln!("Failed to send data: {:?}", result);
}
});
self.sink.clear();
Ok(())
}
}
#[derive(Clone)]
struct AppServer {
clients: Arc<Mutex<HashMap<usize, (SshTerminal, App)>>>,
id: usize,
}
impl AppServer {
pub fn new() -> Self {
Self {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
}
}
pub async fn run(&mut self) -> Result<(), anyhow::Error> {
let clients = self.clients.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
for (_, (terminal, app)) in clients.lock().await.iter_mut() {
app.counter += 1;
terminal
.draw(|f| {
let size = f.size();
f.render_widget(Clear, size);
let style = match app.counter % 3 {
0 => Style::default().fg(Color::Red),
1 => Style::default().fg(Color::Green),
_ => Style::default().fg(Color::Blue),
};
let paragraph = Paragraph::new(format!("Counter: {}", app.counter))
.alignment(ratatui::layout::Alignment::Center)
.style(style);
let block = Block::default()
.title("Press 'c' to reset the counter!")
.borders(Borders::ALL);
f.render_widget(paragraph.block(block), size);
})
.unwrap();
}
}
});
let config = Config {
inactivity_timeout: Some(std::time::Duration::from_secs(3600)),
auth_rejection_time: std::time::Duration::from_secs(3),
auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)),
keys: vec![russh_keys::key::KeyPair::generate_ed25519()],
..Default::default()
};
self.run_on_address(Arc::new(config), ("0.0.0.0", 2222))
.await?;
Ok(())
}
}
impl Server for AppServer {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
self.id += 1;
s
}
}
#[async_trait]
impl Handler for AppServer {
type Error = anyhow::Error;
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().await;
let terminal_handle = TerminalHandle {
handle: session.handle(),
sink: Vec::new(),
channel_id: channel.id(),
};
let backend = CrosstermBackend::new(terminal_handle.clone());
let terminal = Terminal::new(backend)?;
let app = App::new();
clients.insert(self.id, (terminal, app));
}
Ok(true)
}
async fn auth_publickey(&mut self, _: &str, _: &PublicKey) -> Result<Auth, Self::Error> {
Ok(Auth::Accept)
}
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
match data {
// Pressing 'q' closes the connection.
b"q" => {
self.clients.lock().await.remove(&self.id);
session.close(channel);
}
// Pressing 'c' resets the counter for the app.
// Only the client with the id sees the counter reset.
b"c" => {
let mut clients = self.clients.lock().await;
let (_, app) = clients.get_mut(&self.id).unwrap();
app.counter = 0;
}
_ => {}
}
Ok(())
}
/// The client's window size has changed.
async fn window_change_request(
&mut self,
_: ChannelId,
col_width: u32,
row_height: u32,
_: u32,
_: u32,
_: &mut Session,
) -> Result<(), Self::Error> {
{
let mut clients = self.clients.lock().await;
let (terminal, _) = clients.get_mut(&self.id).unwrap();
let rect = Rect {
x: 0,
y: 0,
width: col_width as u16,
height: row_height as u16,
};
terminal.resize(rect)?;
}
Ok(())
}
}
#[tokio::main]
async fn main() {
let mut server = AppServer::new();
server.run().await.expect("Failed running server");
}

View File

@@ -0,0 +1,211 @@
use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::Rect;
use ratatui::style::{Color, Style};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use ratatui::Terminal;
use russh::keys::key::PublicKey;
use russh::server::*;
use russh::{Channel, ChannelId};
use tokio::sync::Mutex;
type SshTerminal = Terminal<CrosstermBackend<TerminalHandle>>;
struct App {
pub counter: usize,
}
impl App {
pub fn new() -> App {
Self { counter: 0 }
}
}
#[derive(Clone)]
struct TerminalHandle {
handle: Handle,
// The sink collects the data which is finally flushed to the handle.
sink: Vec<u8>,
channel_id: ChannelId,
}
// The crossterm backend writes to the terminal handle.
impl std::io::Write for TerminalHandle {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.sink.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
let handle = self.handle.clone();
let channel_id = self.channel_id;
let data = self.sink.clone().into();
futures::executor::block_on(async move {
let result = handle.data(channel_id, data).await;
if result.is_err() {
eprintln!("Failed to send data: {:?}", result);
}
});
self.sink.clear();
Ok(())
}
}
#[derive(Clone)]
struct AppServer {
clients: Arc<Mutex<HashMap<usize, SshTerminal>>>,
id: usize,
app: Arc<Mutex<App>>,
}
impl AppServer {
pub fn new() -> Self {
Self {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
app: Arc::new(Mutex::new(App::new())),
}
}
pub async fn run(&mut self) -> Result<(), anyhow::Error> {
let app = self.app.clone();
let clients = self.clients.clone();
tokio::spawn(async move {
loop {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
app.lock().await.counter += 1;
let counter = app.lock().await.counter;
for (_, terminal) in clients.lock().await.iter_mut() {
terminal
.draw(|f| {
let size = f.size();
f.render_widget(Clear, size);
let style = match counter % 3 {
0 => Style::default().fg(Color::Red),
1 => Style::default().fg(Color::Green),
_ => Style::default().fg(Color::Blue),
};
let paragraph = Paragraph::new(format!("Counter: {counter}"))
.alignment(ratatui::layout::Alignment::Center)
.style(style);
let block = Block::default()
.title("Press 'c' to reset the counter!")
.borders(Borders::ALL);
f.render_widget(paragraph.block(block), size);
})
.unwrap();
}
}
});
let config = Config {
inactivity_timeout: Some(std::time::Duration::from_secs(3600)),
auth_rejection_time: std::time::Duration::from_secs(3),
auth_rejection_time_initial: Some(std::time::Duration::from_secs(0)),
keys: vec![russh_keys::key::KeyPair::generate_ed25519()],
..Default::default()
};
self.run_on_address(Arc::new(config), ("0.0.0.0", 2222))
.await?;
Ok(())
}
}
impl Server for AppServer {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
self.id += 1;
s
}
}
#[async_trait]
impl Handler for AppServer {
type Error = anyhow::Error;
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().await;
let terminal_handle = TerminalHandle {
handle: session.handle(),
sink: Vec::new(),
channel_id: channel.id(),
};
let backend = CrosstermBackend::new(terminal_handle.clone());
let terminal = Terminal::new(backend)?;
clients.insert(self.id, terminal);
}
Ok(true)
}
async fn auth_publickey(&mut self, _: &str, _: &PublicKey) -> Result<Auth, Self::Error> {
Ok(Auth::Accept)
}
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
let app = self.app.clone();
match data {
// Pressing 'q' closes the connection.
b"q" => {
self.clients.lock().await.remove(&self.id);
session.close(channel);
}
// Pressing 'c' resets the counter for the app.
// Every client sees the counter reset.
b"c" => {
app.lock().await.counter = 0;
}
_ => {}
}
Ok(())
}
/// The client's pseudo-terminal window size has changed.
async fn window_change_request(
&mut self,
_: ChannelId,
col_width: u32,
row_height: u32,
_: u32,
_: u32,
_: &mut Session,
) -> Result<(), Self::Error> {
let mut terminal = {
let clients = self.clients.lock().await;
clients.get(&self.id).unwrap().clone()
};
let rect = Rect {
x: 0,
y: 0,
width: col_width as u16,
height: row_height as u16,
};
terminal.resize(rect)?;
Ok(())
}
}
#[tokio::main]
async fn main() {
let mut server = AppServer::new();
server.run().await.expect("Failed running server");
}

112
vendor/russh/examples/sftp_client.rs vendored Normal file
View File

@@ -0,0 +1,112 @@
use async_trait::async_trait;
use log::{error, info, LevelFilter};
use russh::*;
use russh_keys::*;
use russh_sftp::{client::SftpSession, protocol::OpenFlags};
use std::sync::Arc;
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWriteExt};
struct Client;
#[async_trait]
impl client::Handler for Client {
type Error = anyhow::Error;
async fn check_server_key(
&mut self,
server_public_key: &key::PublicKey,
) -> Result<bool, Self::Error> {
info!("check_server_key: {:?}", server_public_key);
Ok(true)
}
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
_session: &mut client::Session,
) -> Result<(), Self::Error> {
info!("data on channel {:?}: {}", channel, data.len());
Ok(())
}
}
#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(LevelFilter::Debug)
.init();
let config = russh::client::Config::default();
let sh = Client {};
let mut session = russh::client::connect(Arc::new(config), ("localhost", 22), sh)
.await
.unwrap();
if session
.authenticate_password("root", "password")
.await
.unwrap()
{
let channel = session.channel_open_session().await.unwrap();
channel.request_subsystem(true, "sftp").await.unwrap();
let sftp = SftpSession::new(channel.into_stream()).await.unwrap();
info!("current path: {:?}", sftp.canonicalize(".").await.unwrap());
// create dir and symlink
let path = "./some_kind_of_dir";
let symlink = "./symlink";
sftp.create_dir(path).await.unwrap();
sftp.symlink(path, symlink).await.unwrap();
info!("dir info: {:?}", sftp.metadata(path).await.unwrap());
info!(
"symlink info: {:?}",
sftp.symlink_metadata(path).await.unwrap()
);
// scanning directory
for entry in sftp.read_dir(".").await.unwrap() {
info!("file in directory: {:?}", entry.file_name());
}
sftp.remove_file(symlink).await.unwrap();
sftp.remove_dir(path).await.unwrap();
// interaction with i/o
let filename = "test_new.txt";
let mut file = sftp
.open_with_flags(
filename,
OpenFlags::CREATE | OpenFlags::TRUNCATE | OpenFlags::WRITE | OpenFlags::READ,
)
.await
.unwrap();
info!("metadata by handle: {:?}", file.metadata().await.unwrap());
file.write_all(b"magic text").await.unwrap();
info!("flush: {:?}", file.flush().await); // or file.sync_all()
info!(
"current cursor position: {:?}",
file.stream_position().await
);
let mut str = String::new();
file.rewind().await.unwrap();
file.read_to_string(&mut str).await.unwrap();
file.rewind().await.unwrap();
info!(
"our magical contents: {}, after rewind: {:?}",
str,
file.stream_position().await
);
file.shutdown().await.unwrap();
sftp.remove_file(filename).await.unwrap();
// should fail because handle was closed
error!("should fail: {:?}", file.read_u8().await);
}
}

201
vendor/russh/examples/sftp_server.rs vendored Normal file
View File

@@ -0,0 +1,201 @@
use async_trait::async_trait;
use log::{error, info, LevelFilter};
use russh::{
server::{Auth, Msg, Server as _, Session},
Channel, ChannelId,
};
use russh_keys::key::KeyPair;
use russh_sftp::protocol::{File, FileAttributes, Handle, Name, Status, StatusCode, Version};
use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration};
use tokio::sync::Mutex;
#[derive(Clone)]
struct Server;
impl russh::server::Server for Server {
type Handler = SshSession;
fn new_client(&mut self, _: Option<SocketAddr>) -> Self::Handler {
SshSession::default()
}
}
struct SshSession {
clients: Arc<Mutex<HashMap<ChannelId, Channel<Msg>>>>,
}
impl Default for SshSession {
fn default() -> Self {
Self {
clients: Arc::new(Mutex::new(HashMap::new())),
}
}
}
impl SshSession {
pub async fn get_channel(&mut self, channel_id: ChannelId) -> Channel<Msg> {
let mut clients = self.clients.lock().await;
clients.remove(&channel_id).unwrap()
}
}
#[async_trait]
impl russh::server::Handler for SshSession {
type Error = anyhow::Error;
async fn auth_password(&mut self, user: &str, password: &str) -> Result<Auth, Self::Error> {
info!("credentials: {}, {}", user, password);
Ok(Auth::Accept)
}
async fn auth_publickey(
&mut self,
user: &str,
public_key: &russh_keys::key::PublicKey,
) -> Result<Auth, Self::Error> {
info!("credentials: {}, {:?}", user, public_key);
Ok(Auth::Accept)
}
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
_session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().await;
clients.insert(channel.id(), channel);
}
Ok(true)
}
async fn channel_eof(
&mut self,
channel: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
// After a client has sent an EOF, indicating that they don't want
// to send more data in this session, the channel can be closed.
session.close(channel);
Ok(())
}
async fn subsystem_request(
&mut self,
channel_id: ChannelId,
name: &str,
session: &mut Session,
) -> Result<(), Self::Error> {
info!("subsystem: {}", name);
if name == "sftp" {
let channel = self.get_channel(channel_id).await;
let sftp = SftpSession::default();
session.channel_success(channel_id);
russh_sftp::server::run(channel.into_stream(), sftp).await;
} else {
session.channel_failure(channel_id);
}
Ok(())
}
}
#[derive(Default)]
struct SftpSession {
version: Option<u32>,
root_dir_read_done: bool,
}
#[async_trait]
impl russh_sftp::server::Handler for SftpSession {
type Error = StatusCode;
fn unimplemented(&self) -> Self::Error {
StatusCode::OpUnsupported
}
async fn init(
&mut self,
version: u32,
extensions: HashMap<String, String>,
) -> Result<Version, Self::Error> {
if self.version.is_some() {
error!("duplicate SSH_FXP_VERSION packet");
return Err(StatusCode::ConnectionLost);
}
self.version = Some(version);
info!("version: {:?}, extensions: {:?}", self.version, extensions);
Ok(Version::new())
}
async fn close(&mut self, id: u32, _handle: String) -> Result<Status, Self::Error> {
Ok(Status {
id,
status_code: StatusCode::Ok,
error_message: "Ok".to_string(),
language_tag: "en-US".to_string(),
})
}
async fn opendir(&mut self, id: u32, path: String) -> Result<Handle, Self::Error> {
info!("opendir: {}", path);
self.root_dir_read_done = false;
Ok(Handle { id, handle: path })
}
async fn readdir(&mut self, id: u32, handle: String) -> Result<Name, Self::Error> {
info!("readdir handle: {}", handle);
if handle == "/" && !self.root_dir_read_done {
self.root_dir_read_done = true;
return Ok(Name {
id,
files: vec![
File::new("foo", FileAttributes::default()),
File::new("bar", FileAttributes::default()),
],
});
}
// If all files have been sent to the client, respond with an EOF
Err(StatusCode::Eof)
}
async fn realpath(&mut self, id: u32, path: String) -> Result<Name, Self::Error> {
info!("realpath: {}", path);
Ok(Name {
id,
files: vec![File::dummy("/")],
})
}
}
#[tokio::main]
async fn main() {
env_logger::builder()
.filter_level(LevelFilter::Debug)
.init();
let config = russh::server::Config {
auth_rejection_time: Duration::from_secs(3),
auth_rejection_time_initial: Some(Duration::from_secs(0)),
keys: vec![KeyPair::generate_ed25519()],
..Default::default()
};
let mut server = Server;
server
.run_on_address(
Arc::new(config),
(
"0.0.0.0",
std::env::var("PORT")
.unwrap_or("22".to_string())
.parse()
.unwrap(),
),
)
.await
.unwrap();
}

95
vendor/russh/examples/test.rs vendored Normal file
View File

@@ -0,0 +1,95 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use log::debug;
use russh::keys::*;
use russh::server::{Auth, Msg, Server as _, Session};
use russh::*;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
let mut config = russh::server::Config::default();
config.auth_rejection_time = std::time::Duration::from_secs(3);
config
.keys
.push(russh_keys::key::KeyPair::generate_ed25519());
let config = Arc::new(config);
let mut sh = Server {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
};
tokio::time::timeout(
std::time::Duration::from_secs(60),
sh.run_on_address(config, ("0.0.0.0", 2222)),
)
.await
.unwrap_or(Ok(()))?;
Ok(())
}
#[derive(Clone)]
struct Server {
clients: Arc<Mutex<HashMap<(usize, ChannelId), Channel<Msg>>>>,
id: usize,
}
impl server::Server for Server {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
debug!("new client");
let s = self.clone();
self.id += 1;
s
}
}
#[async_trait]
impl server::Handler for Server {
type Error = anyhow::Error;
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
_session: &mut Session,
) -> Result<bool, Self::Error> {
{
debug!("channel open session");
let mut clients = self.clients.lock().unwrap();
clients.insert((self.id, channel.id()), channel);
}
Ok(true)
}
/// The client requests a shell.
#[allow(unused_variables)]
async fn shell_request(
&mut self,
channel: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
session.request_success();
Ok(())
}
async fn auth_publickey(&mut self, _: &str, _: &key::PublicKey) -> Result<Auth, Self::Error> {
Ok(server::Auth::Accept)
}
async fn data(
&mut self,
_channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
debug!("data: {data:?}");
{
let mut clients = self.clients.lock().unwrap();
for ((_, _channel_id), ref mut channel) in clients.iter_mut() {
session.data(channel.id(), CryptoVec::from(data.to_vec()));
}
}
Ok(())
}
}

153
vendor/russh/src/auth.rs vendored Normal file
View File

@@ -0,0 +1,153 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use std::sync::Arc;
use bitflags::bitflags;
use ssh_key::Certificate;
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use crate::keys::{encoding, key};
use crate::CryptoVec;
bitflags! {
/// Set of authentication methods, represented by bit flags.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MethodSet: u32 {
/// The SSH `none` method (no authentication).
const NONE = 1;
/// The SSH `password` method (plaintext passwords).
const PASSWORD = 2;
/// The SSH `publickey` method (sign a challenge sent by the
/// server).
const PUBLICKEY = 4;
/// The SSH `hostbased` method (certain hostnames are allowed
/// by the server).
const HOSTBASED = 8;
/// The SSH `keyboard-interactive` method (answer to a
/// challenge, where the "challenge" can be a password prompt,
/// a bytestring to sign with a smartcard, or something else).
const KEYBOARD_INTERACTIVE = 16;
}
}
pub trait Signer: Sized {
type Error: From<crate::SendError>;
type Future: futures::Future<Output = (Self, Result<CryptoVec, Self::Error>)> + Send;
fn auth_publickey_sign(self, key: &key::PublicKey, to_sign: CryptoVec) -> Self::Future;
}
#[derive(Debug, Error)]
pub enum AgentAuthError {
#[error(transparent)]
Send(#[from] crate::SendError),
#[error(transparent)]
Key(#[from] russh_keys::Error),
}
impl<R: AsyncRead + AsyncWrite + Unpin + Send + 'static> Signer
for russh_keys::agent::client::AgentClient<R>
{
type Error = AgentAuthError;
#[allow(clippy::type_complexity)]
type Future = std::pin::Pin<
Box<dyn futures::Future<Output = (Self, Result<CryptoVec, Self::Error>)> + Send>,
>;
fn auth_publickey_sign(self, key: &key::PublicKey, to_sign: CryptoVec) -> Self::Future {
let fut = self.sign_request(key, to_sign);
futures::FutureExt::boxed(async move {
let (a, b) = fut.await;
(a, b.map_err(AgentAuthError::Key))
})
}
}
#[derive(Debug)]
pub enum Method {
None,
Password {
password: String,
},
PublicKey {
key: Arc<key::KeyPair>,
},
OpenSSHCertificate {
key: Arc<key::KeyPair>,
cert: Certificate,
},
FuturePublicKey {
key: key::PublicKey,
},
KeyboardInteractive {
submethods: String,
},
// Hostbased,
}
impl encoding::Bytes for MethodSet {
fn bytes(&self) -> &'static [u8] {
match *self {
MethodSet::NONE => b"none",
MethodSet::PASSWORD => b"password",
MethodSet::PUBLICKEY => b"publickey",
MethodSet::HOSTBASED => b"hostbased",
MethodSet::KEYBOARD_INTERACTIVE => b"keyboard-interactive",
_ => b"",
}
}
}
impl MethodSet {
pub(crate) fn from_bytes(b: &[u8]) -> Option<MethodSet> {
match b {
b"none" => Some(MethodSet::NONE),
b"password" => Some(MethodSet::PASSWORD),
b"publickey" => Some(MethodSet::PUBLICKEY),
b"hostbased" => Some(MethodSet::HOSTBASED),
b"keyboard-interactive" => Some(MethodSet::KEYBOARD_INTERACTIVE),
_ => None,
}
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct AuthRequest {
pub methods: MethodSet,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub partial_success: bool,
pub current: Option<CurrentRequest>,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub rejection_count: usize,
}
#[doc(hidden)]
#[derive(Debug)]
pub enum CurrentRequest {
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
PublicKey {
#[allow(dead_code)]
key: CryptoVec,
#[allow(dead_code)]
algo: CryptoVec,
sent_pk_ok: bool,
},
KeyboardInteractive {
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
submethods: String,
},
}

62
vendor/russh/src/cert.rs vendored Normal file
View File

@@ -0,0 +1,62 @@
use ssh_encoding::Encode;
use ssh_key::{Algorithm, Certificate, EcdsaCurve};
use crate::key::PubKey;
use crate::keys::encoding::Encoding;
use crate::negotiation::Named;
use crate::CryptoVec;
/// OpenSSH certificate for DSA public key
const CERT_DSA: &str = "ssh-dss-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-256) public key
const CERT_ECDSA_SHA2_P256: &str = "ecdsa-sha2-nistp256-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-384) public key
const CERT_ECDSA_SHA2_P384: &str = "ecdsa-sha2-nistp384-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-521) public key
const CERT_ECDSA_SHA2_P521: &str = "ecdsa-sha2-nistp521-cert-v01@openssh.com";
/// OpenSSH certificate for Ed25519 public key
const CERT_ED25519: &str = "ssh-ed25519-cert-v01@openssh.com";
/// OpenSSH certificate with RSA public key
const CERT_RSA: &str = "ssh-rsa-cert-v01@openssh.com";
/// OpenSSH certificate for ECDSA (NIST P-256) U2F/FIDO security key
const CERT_SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com";
/// OpenSSH certificate for Ed25519 U2F/FIDO security key
const CERT_SK_SSH_ED25519: &str = "sk-ssh-ed25519-cert-v01@openssh.com";
/// None
const NONE: &str = "none";
impl PubKey for Certificate {
fn push_to(&self, buffer: &mut CryptoVec) {
let mut cert_encoded = Vec::new();
let _ = self.encode(&mut cert_encoded);
buffer.extend_ssh_string(&cert_encoded);
}
}
impl Named for Certificate {
fn name(&self) -> &'static str {
match self.algorithm() {
Algorithm::Dsa => CERT_DSA,
Algorithm::Ecdsa { curve } => match curve {
EcdsaCurve::NistP256 => CERT_ECDSA_SHA2_P256,
EcdsaCurve::NistP384 => CERT_ECDSA_SHA2_P384,
EcdsaCurve::NistP521 => CERT_ECDSA_SHA2_P521,
},
Algorithm::Ed25519 => CERT_ED25519,
Algorithm::Rsa { .. } => CERT_RSA,
Algorithm::SkEcdsaSha2NistP256 => CERT_SK_ECDSA_SHA2_P256,
Algorithm::SkEd25519 => CERT_SK_SSH_ED25519,
Algorithm::Other(_) => NONE,
_ => NONE,
}
}
}

View File

@@ -0,0 +1,35 @@
use std::sync::Arc;
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::Mutex;
use crate::ChannelMsg;
/// A handle to the [`super::Channel`]'s to be able to transmit messages
/// to it and update it's `window_size`.
#[derive(Debug)]
pub struct ChannelRef {
pub(super) sender: UnboundedSender<ChannelMsg>,
pub(super) window_size: Arc<Mutex<u32>>,
}
impl ChannelRef {
pub fn new(sender: UnboundedSender<ChannelMsg>) -> Self {
Self {
sender,
window_size: Default::default(),
}
}
pub fn window_size(&self) -> &Arc<Mutex<u32>> {
&self.window_size
}
}
impl std::ops::Deref for ChannelRef {
type Target = UnboundedSender<ChannelMsg>;
fn deref(&self) -> &Self::Target {
&self.sender
}
}

View File

@@ -0,0 +1,63 @@
use std::io;
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite};
use super::io::{ChannelRx, ChannelTx};
use super::{ChannelId, ChannelMsg};
/// AsyncRead/AsyncWrite wrapper for SSH Channels
pub struct ChannelStream<S>
where
S: From<(ChannelId, ChannelMsg)> + 'static,
{
tx: ChannelTx<S>,
rx: ChannelRx<'static, S>,
}
impl<S> ChannelStream<S>
where
S: From<(ChannelId, ChannelMsg)>,
{
pub(super) fn new(tx: ChannelTx<S>, rx: ChannelRx<'static, S>) -> Self {
Self { tx, rx }
}
}
impl<S> AsyncRead for ChannelStream<S>
where
S: From<(ChannelId, ChannelMsg)>,
{
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<io::Result<()>> {
Pin::new(&mut self.rx).poll_read(cx, buf)
}
}
impl<S> AsyncWrite for ChannelStream<S>
where
S: From<(ChannelId, ChannelMsg)> + 'static + Send + Sync,
{
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, io::Error>> {
Pin::new(&mut self.tx).poll_write(cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
Pin::new(&mut self.tx).poll_flush(cx)
}
fn poll_shutdown(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), io::Error>> {
Pin::new(&mut self.tx).poll_shutdown(cx)
}
}

48
vendor/russh/src/channels/io/mod.rs vendored Normal file
View File

@@ -0,0 +1,48 @@
use super::{Channel, ChannelId, ChannelMsg};
mod rx;
pub use rx::ChannelRx;
mod tx;
pub use tx::ChannelTx;
/// An enum with the ability to hold either an owned [`Channel`]
/// or a `&mut` ref to it.
#[derive(Debug)]
pub enum ChannelAsMut<'i, S>
where
S: From<(ChannelId, ChannelMsg)>,
{
Owned(Channel<S>),
RefMut(&'i mut Channel<S>),
}
impl<'i, S> AsMut<Channel<S>> for ChannelAsMut<'i, S>
where
S: From<(ChannelId, ChannelMsg)>,
{
fn as_mut(&mut self) -> &mut Channel<S> {
match self {
Self::Owned(channel) => channel,
Self::RefMut(ref_mut) => ref_mut,
}
}
}
impl<S> From<Channel<S>> for ChannelAsMut<'static, S>
where
S: From<(ChannelId, ChannelMsg)>,
{
fn from(value: Channel<S>) -> Self {
Self::Owned(value)
}
}
impl<'i, S> From<&'i mut Channel<S>> for ChannelAsMut<'i, S>
where
S: From<(ChannelId, ChannelMsg)>,
{
fn from(value: &'i mut Channel<S>) -> Self {
Self::RefMut(value)
}
}

91
vendor/russh/src/channels/io/rx.rs vendored Normal file
View File

@@ -0,0 +1,91 @@
use std::io;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
use tokio::io::AsyncRead;
use super::{ChannelAsMut, ChannelMsg};
use crate::ChannelId;
#[derive(Debug)]
pub struct ChannelRx<'i, S>
where
S: From<(ChannelId, ChannelMsg)>,
{
channel: ChannelAsMut<'i, S>,
buffer: Option<(ChannelMsg, usize)>,
ext: Option<u32>,
}
impl<'i, S> ChannelRx<'i, S>
where
S: From<(ChannelId, ChannelMsg)>,
{
pub fn new(channel: impl Into<ChannelAsMut<'i, S>>, ext: Option<u32>) -> Self {
Self {
channel: channel.into(),
buffer: None,
ext,
}
}
}
impl<'i, S> AsyncRead for ChannelRx<'i, S>
where
S: From<(ChannelId, ChannelMsg)>,
{
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let (msg, mut idx) = match self.buffer.take() {
Some(msg) => msg,
None => match ready!(self.channel.as_mut().receiver.poll_recv(cx)) {
Some(msg) => (msg, 0),
None => return Poll::Ready(Ok(())),
},
};
match (&msg, self.ext) {
(ChannelMsg::Data { data }, None) => {
let readable = buf.remaining().min(data.len() - idx);
// Clamped to maximum `buf.remaining()` and `data.len() - idx` with `.min`
#[allow(clippy::indexing_slicing)]
buf.put_slice(&data[idx..idx + readable]);
idx += readable;
if idx != data.len() {
self.buffer = Some((msg, idx));
}
Poll::Ready(Ok(()))
}
(ChannelMsg::ExtendedData { data, ext }, Some(target)) if *ext == target => {
let readable = buf.remaining().min(data.len() - idx);
// Clamped to maximum `buf.remaining()` and `data.len() - idx` with `.min`
#[allow(clippy::indexing_slicing)]
buf.put_slice(&data[idx..idx + readable]);
idx += readable;
if idx != data.len() {
self.buffer = Some((msg, idx));
}
Poll::Ready(Ok(()))
}
(ChannelMsg::Eof, _) => {
self.channel.as_mut().receiver.close();
Poll::Ready(Ok(()))
}
_ => {
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
}

145
vendor/russh/src/channels/io/tx.rs vendored Normal file
View File

@@ -0,0 +1,145 @@
use std::io;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{ready, Context, Poll};
use futures::FutureExt;
use tokio::io::AsyncWrite;
use tokio::sync::mpsc::error::SendError;
use tokio::sync::mpsc::{self, OwnedPermit};
use tokio::sync::{Mutex, OwnedMutexGuard};
use super::ChannelMsg;
use crate::{ChannelId, CryptoVec};
type BoxedThreadsafeFuture<T> = Pin<Box<dyn Sync + Send + std::future::Future<Output = T>>>;
type OwnedPermitFuture<S> =
BoxedThreadsafeFuture<Result<(OwnedPermit<S>, ChannelMsg, usize), SendError<()>>>;
pub struct ChannelTx<S> {
sender: mpsc::Sender<S>,
send_fut: Option<OwnedPermitFuture<S>>,
id: ChannelId,
window_size_fut: Option<BoxedThreadsafeFuture<OwnedMutexGuard<u32>>>,
window_size: Arc<Mutex<u32>>,
max_packet_size: u32,
ext: Option<u32>,
}
impl<S> ChannelTx<S>
where
S: From<(ChannelId, ChannelMsg)> + 'static + Send,
{
pub fn new(
sender: mpsc::Sender<S>,
id: ChannelId,
window_size: Arc<Mutex<u32>>,
max_packet_size: u32,
ext: Option<u32>,
) -> Self {
Self {
sender,
send_fut: None,
id,
window_size,
window_size_fut: None,
max_packet_size,
ext,
}
}
fn poll_mk_msg(&mut self, cx: &mut Context<'_>, buf: &[u8]) -> Poll<(ChannelMsg, usize)> {
let window_size = self.window_size.clone();
let window_size_fut = self
.window_size_fut
.get_or_insert_with(|| Box::pin(window_size.lock_owned()));
let mut window_size = ready!(window_size_fut.poll_unpin(cx));
self.window_size_fut.take();
let writable = (self.max_packet_size)
.min(*window_size)
.min(buf.len() as u32) as usize;
if writable == 0 {
// TODO fix this busywait
cx.waker().wake_by_ref();
return Poll::Pending;
}
let mut data = CryptoVec::new_zeroed(writable);
#[allow(clippy::indexing_slicing)] // Clamped to maximum `buf.len()` with `.min`
data.copy_from_slice(&buf[..writable]);
data.resize(writable);
*window_size -= writable as u32;
drop(window_size);
let msg = match self.ext {
None => ChannelMsg::Data { data },
Some(ext) => ChannelMsg::ExtendedData { data, ext },
};
Poll::Ready((msg, writable))
}
fn activate(&mut self, msg: ChannelMsg, writable: usize) -> &mut OwnedPermitFuture<S> {
use futures::TryFutureExt;
self.send_fut.insert(Box::pin(
self.sender
.clone()
.reserve_owned()
.map_ok(move |p| (p, msg, writable)),
))
}
fn handle_write_result(
&mut self,
r: Result<(OwnedPermit<S>, ChannelMsg, usize), SendError<()>>,
) -> Result<usize, io::Error> {
self.send_fut = None;
match r {
Ok((permit, msg, writable)) => {
permit.send((self.id, msg).into());
Ok(writable)
}
Err(SendError(())) => Err(io::Error::new(io::ErrorKind::BrokenPipe, "channel closed")),
}
}
}
impl<S> AsyncWrite for ChannelTx<S>
where
S: From<(ChannelId, ChannelMsg)> + 'static + Send,
{
#[allow(clippy::too_many_lines)]
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, io::Error>> {
let send_fut = if let Some(x) = self.send_fut.as_mut() {
x
} else {
let (msg, writable) = ready!(self.poll_mk_msg(cx, buf));
self.activate(msg, writable)
};
let r = ready!(send_fut.as_mut().poll_unpin(cx));
Poll::Ready(self.handle_write_result(r))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
Poll::Ready(Ok(()))
}
fn poll_shutdown(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), io::Error>> {
let send_fut = if let Some(x) = self.send_fut.as_mut() {
x
} else {
self.activate(ChannelMsg::Eof, 0)
};
let r = ready!(send_fut.as_mut().poll_unpin(cx)).map(|(p, _, _)| (p, ChannelMsg::Eof, 0));
Poll::Ready(self.handle_write_result(r).map(drop))
}
}

377
vendor/russh/src/channels/mod.rs vendored Normal file
View File

@@ -0,0 +1,377 @@
use std::sync::Arc;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::sync::mpsc::{Sender, UnboundedReceiver};
use tokio::sync::Mutex;
use crate::{ChannelId, ChannelOpenFailure, CryptoVec, Error, Pty, Sig};
pub mod io;
mod channel_ref;
pub use channel_ref::ChannelRef;
mod channel_stream;
pub use channel_stream::ChannelStream;
#[derive(Debug)]
#[non_exhaustive]
/// Possible messages that [Channel::wait] can receive.
pub enum ChannelMsg {
Open {
id: ChannelId,
max_packet_size: u32,
window_size: u32,
},
Data {
data: CryptoVec,
},
ExtendedData {
data: CryptoVec,
ext: u32,
},
Eof,
Close,
/// (client only)
RequestPty {
want_reply: bool,
term: String,
col_width: u32,
row_height: u32,
pix_width: u32,
pix_height: u32,
terminal_modes: Vec<(Pty, u32)>,
},
/// (client only)
RequestShell {
want_reply: bool,
},
/// (client only)
Exec {
want_reply: bool,
command: Vec<u8>,
},
/// (client only)
Signal {
signal: Sig,
},
/// (client only)
RequestSubsystem {
want_reply: bool,
name: String,
},
/// (client only)
RequestX11 {
want_reply: bool,
single_connection: bool,
x11_authentication_protocol: String,
x11_authentication_cookie: String,
x11_screen_number: u32,
},
/// (client only)
SetEnv {
want_reply: bool,
variable_name: String,
variable_value: String,
},
/// (client only)
WindowChange {
col_width: u32,
row_height: u32,
pix_width: u32,
pix_height: u32,
},
/// (client only)
AgentForward {
want_reply: bool,
},
/// (server only)
XonXoff {
client_can_do: bool,
},
/// (server only)
ExitStatus {
exit_status: u32,
},
/// (server only)
ExitSignal {
signal_name: Sig,
core_dumped: bool,
error_message: String,
lang_tag: String,
},
/// (server only)
WindowAdjusted {
new_size: u32,
},
/// (server only)
Success,
/// (server only)
Failure,
OpenFailure(ChannelOpenFailure),
}
/// A handle to a session channel.
///
/// Allows you to read and write from a channel without borrowing the session
pub struct Channel<Send: From<(ChannelId, ChannelMsg)>> {
pub(crate) id: ChannelId,
pub(crate) sender: Sender<Send>,
pub(crate) receiver: UnboundedReceiver<ChannelMsg>,
pub(crate) max_packet_size: u32,
pub(crate) window_size: Arc<Mutex<u32>>,
}
impl<T: From<(ChannelId, ChannelMsg)>> std::fmt::Debug for Channel<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Channel").field("id", &self.id).finish()
}
}
impl<S: From<(ChannelId, ChannelMsg)> + Send + Sync + 'static> Channel<S> {
pub(crate) fn new(
id: ChannelId,
sender: Sender<S>,
max_packet_size: u32,
window_size: u32,
) -> (Self, ChannelRef) {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let window_size = Arc::new(Mutex::new(window_size));
(
Self {
id,
sender,
receiver: rx,
max_packet_size,
window_size: window_size.clone(),
},
ChannelRef {
sender: tx,
window_size,
},
)
}
/// Returns the min between the maximum packet size and the
/// remaining window size in the channel.
pub async fn writable_packet_size(&self) -> usize {
self.max_packet_size.min(*self.window_size.lock().await) as usize
}
pub fn id(&self) -> ChannelId {
self.id
}
/// Request a pseudo-terminal with the given characteristics.
#[allow(clippy::too_many_arguments)] // length checked
pub async fn request_pty(
&self,
want_reply: bool,
term: &str,
col_width: u32,
row_height: u32,
pix_width: u32,
pix_height: u32,
terminal_modes: &[(Pty, u32)],
) -> Result<(), Error> {
self.send_msg(ChannelMsg::RequestPty {
want_reply,
term: term.to_string(),
col_width,
row_height,
pix_width,
pix_height,
terminal_modes: terminal_modes.to_vec(),
})
.await
}
/// Request a remote shell.
pub async fn request_shell(&self, want_reply: bool) -> Result<(), Error> {
self.send_msg(ChannelMsg::RequestShell { want_reply }).await
}
/// Execute a remote program (will be passed to a shell). This can
/// be used to implement scp (by calling a remote scp and
/// tunneling to its standard input).
pub async fn exec<A: Into<Vec<u8>>>(&self, want_reply: bool, command: A) -> Result<(), Error> {
self.send_msg(ChannelMsg::Exec {
want_reply,
command: command.into(),
})
.await
}
/// Signal a remote process.
pub async fn signal(&self, signal: Sig) -> Result<(), Error> {
self.send_msg(ChannelMsg::Signal { signal }).await
}
/// Request the start of a subsystem with the given name.
pub async fn request_subsystem<A: Into<String>>(
&self,
want_reply: bool,
name: A,
) -> Result<(), Error> {
self.send_msg(ChannelMsg::RequestSubsystem {
want_reply,
name: name.into(),
})
.await
}
/// Request X11 forwarding through an already opened X11
/// channel. See
/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-6.3.1)
/// for security issues related to cookies.
pub async fn request_x11<A: Into<String>, B: Into<String>>(
&self,
want_reply: bool,
single_connection: bool,
x11_authentication_protocol: A,
x11_authentication_cookie: B,
x11_screen_number: u32,
) -> Result<(), Error> {
self.send_msg(ChannelMsg::RequestX11 {
want_reply,
single_connection,
x11_authentication_protocol: x11_authentication_protocol.into(),
x11_authentication_cookie: x11_authentication_cookie.into(),
x11_screen_number,
})
.await
}
/// Set a remote environment variable.
pub async fn set_env<A: Into<String>, B: Into<String>>(
&self,
want_reply: bool,
variable_name: A,
variable_value: B,
) -> Result<(), Error> {
self.send_msg(ChannelMsg::SetEnv {
want_reply,
variable_name: variable_name.into(),
variable_value: variable_value.into(),
})
.await
}
/// Inform the server that our window size has changed.
pub async fn window_change(
&self,
col_width: u32,
row_height: u32,
pix_width: u32,
pix_height: u32,
) -> Result<(), Error> {
self.send_msg(ChannelMsg::WindowChange {
col_width,
row_height,
pix_width,
pix_height,
})
.await
}
/// Inform the server that we will accept agent forwarding channels
pub async fn agent_forward(&self, want_reply: bool) -> Result<(), Error> {
self.send_msg(ChannelMsg::AgentForward { want_reply }).await
}
/// Send data to a channel.
pub async fn data<R: tokio::io::AsyncRead + Unpin>(&self, data: R) -> Result<(), Error> {
self.send_data(None, data).await
}
/// Send data to a channel. The number of bytes added to the
/// "sending pipeline" (to be processed by the event loop) is
/// returned.
pub async fn extended_data<R: tokio::io::AsyncRead + Unpin>(
&self,
ext: u32,
data: R,
) -> Result<(), Error> {
self.send_data(Some(ext), data).await
}
async fn send_data<R: tokio::io::AsyncRead + Unpin>(
&self,
ext: Option<u32>,
mut data: R,
) -> Result<(), Error> {
let mut tx = self.make_writer_ext(ext);
tokio::io::copy(&mut data, &mut tx).await?;
Ok(())
}
pub async fn eof(&self) -> Result<(), Error> {
self.send_msg(ChannelMsg::Eof).await
}
/// Request that the channel be closed.
pub async fn close(&self) -> Result<(), Error> {
self.send_msg(ChannelMsg::Close).await
}
async fn send_msg(&self, msg: ChannelMsg) -> Result<(), Error> {
self.sender
.send((self.id, msg).into())
.await
.map_err(|_| Error::SendError)
}
/// Awaits an incoming [`ChannelMsg`], this method returns [`None`] if the channel has been closed.
pub async fn wait(&mut self) -> Option<ChannelMsg> {
self.receiver.recv().await
}
/// Consume the [`Channel`] to produce a bidirectionnal stream,
/// sending and receiving [`ChannelMsg::Data`] as `AsyncRead` + `AsyncWrite`.
pub fn into_stream(self) -> ChannelStream<S> {
ChannelStream::new(
io::ChannelTx::new(
self.sender.clone(),
self.id,
self.window_size.clone(),
self.max_packet_size,
None,
),
io::ChannelRx::new(self, None),
)
}
/// Make a reader for the [`Channel`] to receive [`ChannelMsg::Data`]
/// through the `AsyncRead` trait.
pub fn make_reader(&mut self) -> impl AsyncRead + '_ {
self.make_reader_ext(None)
}
/// Make a reader for the [`Channel`] to receive [`ChannelMsg::Data`] or [`ChannelMsg::ExtendedData`]
/// depending on the `ext` parameter, through the `AsyncRead` trait.
pub fn make_reader_ext(&mut self, ext: Option<u32>) -> impl AsyncRead + '_ {
io::ChannelRx::new(self, ext)
}
/// Make a writer for the [`Channel`] to send [`ChannelMsg::Data`]
/// through the `AsyncWrite` trait.
pub fn make_writer(&self) -> impl AsyncWrite {
self.make_writer_ext(None)
}
/// Make a writer for the [`Channel`] to send [`ChannelMsg::Data`] or [`ChannelMsg::ExtendedData`]
/// depending on the `ext` parameter, through the `AsyncWrite` trait.
pub fn make_writer_ext(&self, ext: Option<u32>) -> impl AsyncWrite {
io::ChannelTx::new(
self.sender.clone(),
self.id,
self.window_size.clone(),
self.max_packet_size,
ext,
)
}
}

219
vendor/russh/src/cipher/block.rs vendored Normal file
View File

@@ -0,0 +1,219 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use std::convert::TryInto;
use std::marker::PhantomData;
use aes::cipher::{IvSizeUser, KeyIvInit, KeySizeUser, StreamCipher};
use generic_array::GenericArray;
use rand::RngCore;
use super::super::Error;
use super::PACKET_LENGTH_LEN;
use crate::mac::{Mac, MacAlgorithm};
pub struct SshBlockCipher<C: BlockStreamCipher + KeySizeUser + IvSizeUser>(pub PhantomData<C>);
impl<C: BlockStreamCipher + KeySizeUser + IvSizeUser + KeyIvInit + Send + 'static> super::Cipher
for SshBlockCipher<C>
{
fn key_len(&self) -> usize {
C::key_size()
}
fn nonce_len(&self) -> usize {
C::iv_size()
}
fn needs_mac(&self) -> bool {
true
}
fn make_opening_key(
&self,
k: &[u8],
n: &[u8],
m: &[u8],
mac: &dyn MacAlgorithm,
) -> Box<dyn super::OpeningKey + Send> {
let mut key = GenericArray::<u8, C::KeySize>::default();
let mut nonce = GenericArray::<u8, C::IvSize>::default();
key.clone_from_slice(k);
nonce.clone_from_slice(n);
Box::new(OpeningKey {
cipher: C::new(&key, &nonce),
mac: mac.make_mac(m),
})
}
fn make_sealing_key(
&self,
k: &[u8],
n: &[u8],
m: &[u8],
mac: &dyn MacAlgorithm,
) -> Box<dyn super::SealingKey + Send> {
let mut key = GenericArray::<u8, C::KeySize>::default();
let mut nonce = GenericArray::<u8, C::IvSize>::default();
key.clone_from_slice(k);
nonce.clone_from_slice(n);
Box::new(SealingKey {
cipher: C::new(&key, &nonce),
mac: mac.make_mac(m),
})
}
}
pub struct OpeningKey<C: BlockStreamCipher> {
pub(crate) cipher: C,
pub(crate) mac: Box<dyn Mac + Send>,
}
pub struct SealingKey<C: BlockStreamCipher> {
pub(crate) cipher: C,
pub(crate) mac: Box<dyn Mac + Send>,
}
impl<C: BlockStreamCipher + KeySizeUser + IvSizeUser> super::OpeningKey for OpeningKey<C> {
fn packet_length_to_read_for_block_length(&self) -> usize {
16
}
fn decrypt_packet_length(
&self,
_sequence_number: u32,
encrypted_packet_length: &[u8],
) -> [u8; 4] {
let mut first_block = [0u8; 16];
// Fine because of self.packet_length_to_read_for_block_length()
#[allow(clippy::indexing_slicing)]
first_block.copy_from_slice(&encrypted_packet_length[..16]);
if self.mac.is_etm() {
// Fine because of self.packet_length_to_read_for_block_length()
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
encrypted_packet_length[..4].try_into().unwrap()
} else {
// Work around uncloneable Aes<>
let mut cipher: C = unsafe { std::ptr::read(&self.cipher as *const C) };
cipher.decrypt_data(&mut first_block);
// Fine because of self.packet_length_to_read_for_block_length()
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
first_block[..4].try_into().unwrap()
}
}
fn tag_len(&self) -> usize {
self.mac.mac_len()
}
fn open<'a>(
&mut self,
sequence_number: u32,
ciphertext_in_plaintext_out: &'a mut [u8],
tag: &[u8],
) -> Result<&'a [u8], Error> {
if self.mac.is_etm() {
if !self
.mac
.verify(sequence_number, ciphertext_in_plaintext_out, tag)
{
return Err(Error::PacketAuth);
}
#[allow(clippy::indexing_slicing)]
self.cipher
.decrypt_data(&mut ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..]);
} else {
self.cipher.decrypt_data(ciphertext_in_plaintext_out);
if !self
.mac
.verify(sequence_number, ciphertext_in_plaintext_out, tag)
{
return Err(Error::PacketAuth);
}
}
#[allow(clippy::indexing_slicing)]
Ok(&ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..])
}
}
impl<C: BlockStreamCipher + KeySizeUser + IvSizeUser> super::SealingKey for SealingKey<C> {
fn padding_length(&self, payload: &[u8]) -> usize {
let block_size = 16;
let pll = if self.mac.is_etm() {
0
} else {
PACKET_LENGTH_LEN
};
let extra_len = PACKET_LENGTH_LEN + super::PADDING_LENGTH_LEN + self.mac.mac_len();
let padding_len = if payload.len() + extra_len <= super::MINIMUM_PACKET_LEN {
super::MINIMUM_PACKET_LEN - payload.len() - super::PADDING_LENGTH_LEN - pll
} else {
block_size - ((pll + super::PADDING_LENGTH_LEN + payload.len()) % block_size)
};
if padding_len < PACKET_LENGTH_LEN {
padding_len + block_size
} else {
padding_len
}
}
fn fill_padding(&self, padding_out: &mut [u8]) {
rand::thread_rng().fill_bytes(padding_out);
}
fn tag_len(&self) -> usize {
self.mac.mac_len()
}
fn seal(
&mut self,
sequence_number: u32,
plaintext_in_ciphertext_out: &mut [u8],
tag_out: &mut [u8],
) {
if self.mac.is_etm() {
#[allow(clippy::indexing_slicing)]
self.cipher
.encrypt_data(&mut plaintext_in_ciphertext_out[PACKET_LENGTH_LEN..]);
self.mac
.compute(sequence_number, plaintext_in_ciphertext_out, tag_out);
} else {
self.mac
.compute(sequence_number, plaintext_in_ciphertext_out, tag_out);
self.cipher.encrypt_data(plaintext_in_ciphertext_out);
}
}
}
pub trait BlockStreamCipher {
fn encrypt_data(&mut self, data: &mut [u8]);
fn decrypt_data(&mut self, data: &mut [u8]);
}
impl<T: StreamCipher> BlockStreamCipher for T {
fn encrypt_data(&mut self, data: &mut [u8]) {
self.apply_keystream(data);
}
fn decrypt_data(&mut self, data: &mut [u8]) {
self.apply_keystream(data);
}
}

53
vendor/russh/src/cipher/cbc.rs vendored Normal file
View File

@@ -0,0 +1,53 @@
use aes::cipher::{
BlockCipher, BlockDecrypt, BlockDecryptMut, BlockEncrypt, BlockEncryptMut, InnerIvInit, Iv,
IvSizeUser,
};
use cbc::{Decryptor, Encryptor};
use digest::crypto_common::InnerUser;
use generic_array::GenericArray;
use super::block::BlockStreamCipher;
pub struct CbcWrapper<C: BlockEncrypt + BlockCipher + BlockDecrypt> {
encryptor: Encryptor<C>,
decryptor: Decryptor<C>,
}
impl<C: BlockEncrypt + BlockCipher + BlockDecrypt> InnerUser for CbcWrapper<C> {
type Inner = C;
}
impl<C: BlockEncrypt + BlockCipher + BlockDecrypt> IvSizeUser for CbcWrapper<C> {
type IvSize = C::BlockSize;
}
impl<C: BlockEncrypt + BlockCipher + BlockDecrypt> BlockStreamCipher for CbcWrapper<C> {
fn encrypt_data(&mut self, data: &mut [u8]) {
for chunk in data.chunks_exact_mut(C::block_size()) {
let mut block: GenericArray<u8, _> = GenericArray::clone_from_slice(chunk);
self.encryptor.encrypt_block_mut(&mut block);
chunk.clone_from_slice(&block);
}
}
fn decrypt_data(&mut self, data: &mut [u8]) {
for chunk in data.chunks_exact_mut(C::block_size()) {
let mut block = GenericArray::clone_from_slice(chunk);
self.decryptor.decrypt_block_mut(&mut block);
chunk.clone_from_slice(&block);
}
}
}
impl<C: BlockEncrypt + BlockCipher + BlockDecrypt + Clone> InnerIvInit for CbcWrapper<C>
where
C: BlockEncryptMut + BlockCipher,
{
#[inline]
fn inner_iv_init(cipher: C, iv: &Iv<Self>) -> Self {
Self {
encryptor: Encryptor::inner_iv_init(cipher.clone(), iv),
decryptor: Decryptor::inner_iv_init(cipher, iv),
}
}
}

View File

@@ -0,0 +1,202 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD
use std::convert::TryInto;
use aes::cipher::{BlockSizeUser, StreamCipherSeek};
use byteorder::{BigEndian, ByteOrder};
use chacha20::cipher::{KeyInit, KeyIvInit, StreamCipher};
use chacha20::{ChaCha20Legacy, ChaCha20LegacyCore};
use generic_array::typenum::{Unsigned, U16, U32, U8};
use generic_array::GenericArray;
use poly1305::Poly1305;
use subtle::ConstantTimeEq;
use super::super::Error;
use crate::cipher::PACKET_LENGTH_LEN;
use crate::mac::MacAlgorithm;
pub struct SshChacha20Poly1305Cipher {}
type KeyLength = U32;
type NonceLength = U8;
type TagLength = U16;
type Key = GenericArray<u8, KeyLength>;
type Nonce = GenericArray<u8, NonceLength>;
impl super::Cipher for SshChacha20Poly1305Cipher {
fn key_len(&self) -> usize {
KeyLength::to_usize() * 2
}
#[allow(clippy::indexing_slicing)] // length checked
fn make_opening_key(
&self,
k: &[u8],
_: &[u8],
_: &[u8],
_: &dyn MacAlgorithm,
) -> Box<dyn super::OpeningKey + Send> {
let mut k1 = Key::default();
let mut k2 = Key::default();
k1.clone_from_slice(&k[KeyLength::to_usize()..]);
k2.clone_from_slice(&k[..KeyLength::to_usize()]);
Box::new(OpeningKey { k1, k2 })
}
#[allow(clippy::indexing_slicing)] // length checked
fn make_sealing_key(
&self,
k: &[u8],
_: &[u8],
_: &[u8],
_: &dyn MacAlgorithm,
) -> Box<dyn super::SealingKey + Send> {
let mut k1 = Key::default();
let mut k2 = Key::default();
k1.clone_from_slice(&k[KeyLength::to_usize()..]);
k2.clone_from_slice(&k[..KeyLength::to_usize()]);
Box::new(SealingKey { k1, k2 })
}
}
pub struct OpeningKey {
k1: Key,
k2: Key,
}
pub struct SealingKey {
k1: Key,
k2: Key,
}
#[allow(clippy::indexing_slicing)] // length checked
fn make_counter(sequence_number: u32) -> Nonce {
let mut nonce = Nonce::default();
let i0 = NonceLength::to_usize() - 4;
BigEndian::write_u32(&mut nonce[i0..], sequence_number);
nonce
}
impl super::OpeningKey for OpeningKey {
fn decrypt_packet_length(
&self,
sequence_number: u32,
encrypted_packet_length: &[u8],
) -> [u8; 4] {
// Fine because of self.packet_length_to_read_for_block_length()
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
let mut encrypted_packet_length: [u8; 4] = encrypted_packet_length.try_into().unwrap();
let nonce = make_counter(sequence_number);
let mut cipher = ChaCha20Legacy::new(&self.k1, &nonce);
cipher.apply_keystream(&mut encrypted_packet_length);
encrypted_packet_length
}
fn tag_len(&self) -> usize {
TagLength::to_usize()
}
#[allow(clippy::indexing_slicing)] // lengths checked
fn open<'a>(
&mut self,
sequence_number: u32,
ciphertext_in_plaintext_out: &'a mut [u8],
tag: &[u8],
) -> Result<&'a [u8], Error> {
let nonce = make_counter(sequence_number);
let expected_tag = compute_poly1305(&nonce, &self.k2, ciphertext_in_plaintext_out);
if !bool::from(expected_tag.ct_eq(tag)) {
return Err(Error::DecryptionError);
}
let mut cipher = ChaCha20Legacy::new(&self.k2, &nonce);
cipher.seek(<ChaCha20LegacyCore as BlockSizeUser>::BlockSize::to_usize());
cipher.apply_keystream(&mut ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..]);
Ok(&ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..])
}
}
impl super::SealingKey for SealingKey {
fn padding_length(&self, payload: &[u8]) -> usize {
let block_size = 8;
let extra_len = super::PACKET_LENGTH_LEN + super::PADDING_LENGTH_LEN;
let padding_len = if payload.len() + extra_len <= super::MINIMUM_PACKET_LEN {
super::MINIMUM_PACKET_LEN - payload.len() - super::PADDING_LENGTH_LEN
} else {
block_size - ((super::PADDING_LENGTH_LEN + payload.len()) % block_size)
};
if padding_len < super::PACKET_LENGTH_LEN {
padding_len + block_size
} else {
padding_len
}
}
// As explained in "SSH via CTR mode with stateful decryption" in
// https://openvpn.net/papers/ssh-security.pdf, the padding doesn't need to
// be random because we're doing stateful counter-mode encryption. Use
// fixed padding to avoid PRNG overhead.
fn fill_padding(&self, padding_out: &mut [u8]) {
for padding_byte in padding_out {
*padding_byte = 0;
}
}
fn tag_len(&self) -> usize {
TagLength::to_usize()
}
fn seal(
&mut self,
sequence_number: u32,
plaintext_in_ciphertext_out: &mut [u8],
tag: &mut [u8],
) {
let nonce = make_counter(sequence_number);
let mut cipher = ChaCha20Legacy::new(&self.k1, &nonce);
#[allow(clippy::indexing_slicing)] // length checked
cipher.apply_keystream(&mut plaintext_in_ciphertext_out[..PACKET_LENGTH_LEN]);
// --
let mut cipher = ChaCha20Legacy::new(&self.k2, &nonce);
cipher.seek(<ChaCha20LegacyCore as BlockSizeUser>::BlockSize::to_usize());
#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
cipher.apply_keystream(&mut plaintext_in_ciphertext_out[PACKET_LENGTH_LEN..]);
// --
tag.copy_from_slice(
compute_poly1305(&nonce, &self.k2, plaintext_in_ciphertext_out).as_slice(),
);
}
}
fn compute_poly1305(nonce: &Nonce, key: &Key, data: &[u8]) -> poly1305::Tag {
let mut cipher = ChaCha20Legacy::new(key, nonce);
let mut poly_key = GenericArray::<u8, U32>::default();
cipher.apply_keystream(&mut poly_key);
Poly1305::new(&poly_key).compute_unpadded(data)
}

104
vendor/russh/src/cipher/clear.rs vendored Normal file
View File

@@ -0,0 +1,104 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use std::convert::TryInto;
use crate::mac::MacAlgorithm;
use crate::Error;
#[derive(Debug)]
pub struct Key;
pub struct Clear {}
impl super::Cipher for Clear {
fn key_len(&self) -> usize {
0
}
fn make_opening_key(
&self,
_: &[u8],
_: &[u8],
_: &[u8],
_: &dyn MacAlgorithm,
) -> Box<dyn super::OpeningKey + Send> {
Box::new(Key {})
}
fn make_sealing_key(
&self,
_: &[u8],
_: &[u8],
_: &[u8],
_: &dyn MacAlgorithm,
) -> Box<dyn super::SealingKey + Send> {
Box::new(Key {})
}
}
impl super::OpeningKey for Key {
fn decrypt_packet_length(&self, _seqn: u32, packet_length: &[u8]) -> [u8; 4] {
// Fine because of self.packet_length_to_read_for_block_length()
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
packet_length.try_into().unwrap()
}
fn tag_len(&self) -> usize {
0
}
fn open<'a>(
&mut self,
_seqn: u32,
ciphertext_in_plaintext_out: &'a mut [u8],
tag: &[u8],
) -> Result<&'a [u8], Error> {
debug_assert_eq!(tag.len(), 0); // self.tag_len());
#[allow(clippy::indexing_slicing)] // length known
Ok(&ciphertext_in_plaintext_out[4..])
}
}
impl super::SealingKey for Key {
// Cleartext packets (including lengths) must be multiple of 8 in
// length.
fn padding_length(&self, payload: &[u8]) -> usize {
let block_size = 8;
let padding_len = block_size - ((5 + payload.len()) % block_size);
if padding_len < 4 {
padding_len + block_size
} else {
padding_len
}
}
fn fill_padding(&self, padding_out: &mut [u8]) {
// Since the packet is unencrypted anyway, there's no advantage to
// randomizing the padding, so avoid possibly leaking extra RNG state
// by padding with zeros.
for padding_byte in padding_out {
*padding_byte = 0;
}
}
fn tag_len(&self) -> usize {
0
}
fn seal(&mut self, _seqn: u32, _plaintext_in_ciphertext_out: &mut [u8], tag_out: &mut [u8]) {
debug_assert_eq!(tag_out.len(), self.tag_len());
}
}

198
vendor/russh/src/cipher/gcm.rs vendored Normal file
View File

@@ -0,0 +1,198 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD
use std::convert::TryInto;
use aes_gcm::{AeadCore, AeadInPlace, Aes256Gcm, KeyInit, KeySizeUser};
use digest::typenum::Unsigned;
use generic_array::GenericArray;
use rand::RngCore;
use super::super::Error;
use crate::mac::MacAlgorithm;
pub struct GcmCipher {}
type KeySize = <Aes256Gcm as KeySizeUser>::KeySize;
type NonceSize = <Aes256Gcm as AeadCore>::NonceSize;
type TagSize = <Aes256Gcm as AeadCore>::TagSize;
impl super::Cipher for GcmCipher {
fn key_len(&self) -> usize {
Aes256Gcm::key_size()
}
fn nonce_len(&self) -> usize {
GenericArray::<u8, NonceSize>::default().len()
}
fn make_opening_key(
&self,
k: &[u8],
n: &[u8],
_: &[u8],
_: &dyn MacAlgorithm,
) -> Box<dyn super::OpeningKey + Send> {
let mut key = GenericArray::<u8, KeySize>::default();
key.clone_from_slice(k);
let mut nonce = GenericArray::<u8, NonceSize>::default();
nonce.clone_from_slice(n);
Box::new(OpeningKey {
nonce,
cipher: Aes256Gcm::new(&key),
})
}
fn make_sealing_key(
&self,
k: &[u8],
n: &[u8],
_: &[u8],
_: &dyn MacAlgorithm,
) -> Box<dyn super::SealingKey + Send> {
let mut key = GenericArray::<u8, KeySize>::default();
key.clone_from_slice(k);
let mut nonce = GenericArray::<u8, NonceSize>::default();
nonce.clone_from_slice(n);
Box::new(SealingKey {
nonce,
cipher: Aes256Gcm::new(&key),
})
}
}
pub struct OpeningKey {
nonce: GenericArray<u8, NonceSize>,
cipher: Aes256Gcm,
}
pub struct SealingKey {
nonce: GenericArray<u8, NonceSize>,
cipher: Aes256Gcm,
}
fn inc_nonce(nonce: &mut GenericArray<u8, NonceSize>) {
let mut carry = 1;
#[allow(clippy::indexing_slicing)] // length checked
for i in (0..nonce.len()).rev() {
let n = nonce[i] as u16 + carry;
nonce[i] = n as u8;
carry = n >> 8;
}
}
impl super::OpeningKey for OpeningKey {
fn decrypt_packet_length(
&self,
_sequence_number: u32,
encrypted_packet_length: &[u8],
) -> [u8; 4] {
// Fine because of self.packet_length_to_read_for_block_length()
#[allow(clippy::unwrap_used, clippy::indexing_slicing)]
encrypted_packet_length.try_into().unwrap()
}
fn tag_len(&self) -> usize {
TagSize::to_usize()
}
fn open<'a>(
&mut self,
_sequence_number: u32,
ciphertext_in_plaintext_out: &'a mut [u8],
tag: &[u8],
) -> Result<&'a [u8], Error> {
// Packet length is sent unencrypted
let mut packet_length = [0; super::PACKET_LENGTH_LEN];
#[allow(clippy::indexing_slicing)] // length checked
packet_length.clone_from_slice(&ciphertext_in_plaintext_out[..super::PACKET_LENGTH_LEN]);
let mut buffer = vec![0; ciphertext_in_plaintext_out.len() - super::PACKET_LENGTH_LEN];
#[allow(clippy::indexing_slicing)] // length checked
buffer.copy_from_slice(&ciphertext_in_plaintext_out[super::PACKET_LENGTH_LEN..]);
let mut tag_buf = GenericArray::<u8, TagSize>::default();
tag_buf.clone_from_slice(tag);
#[allow(clippy::indexing_slicing)]
self.cipher
.decrypt_in_place_detached(
&self.nonce,
&packet_length,
&mut ciphertext_in_plaintext_out[super::PACKET_LENGTH_LEN..],
&tag_buf,
)
.map_err(|_| Error::DecryptionError)?;
inc_nonce(&mut self.nonce);
#[allow(clippy::indexing_slicing)]
Ok(&ciphertext_in_plaintext_out[super::PACKET_LENGTH_LEN..])
}
}
impl super::SealingKey for SealingKey {
fn padding_length(&self, payload: &[u8]) -> usize {
let block_size = 16;
let extra_len = super::PACKET_LENGTH_LEN + super::PADDING_LENGTH_LEN;
let padding_len = if payload.len() + extra_len <= super::MINIMUM_PACKET_LEN {
super::MINIMUM_PACKET_LEN - payload.len() - super::PADDING_LENGTH_LEN
} else {
block_size - ((super::PADDING_LENGTH_LEN + payload.len()) % block_size)
};
if padding_len < super::PACKET_LENGTH_LEN {
padding_len + block_size
} else {
padding_len
}
}
fn fill_padding(&self, padding_out: &mut [u8]) {
rand::thread_rng().fill_bytes(padding_out);
}
fn tag_len(&self) -> usize {
TagSize::to_usize()
}
fn seal(
&mut self,
_sequence_number: u32,
plaintext_in_ciphertext_out: &mut [u8],
tag: &mut [u8],
) {
// Packet length is received unencrypted
let mut packet_length = [0; super::PACKET_LENGTH_LEN];
#[allow(clippy::indexing_slicing)] // length checked
packet_length.clone_from_slice(&plaintext_in_ciphertext_out[..super::PACKET_LENGTH_LEN]);
#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
let tag_out = self
.cipher
.encrypt_in_place_detached(
&self.nonce,
&packet_length,
&mut plaintext_in_ciphertext_out[super::PACKET_LENGTH_LEN..],
)
.unwrap();
inc_nonce(&mut self.nonce);
tag.clone_from_slice(&tag_out)
}
}

295
vendor/russh/src/cipher/mod.rs vendored Normal file
View File

@@ -0,0 +1,295 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//!
//! This module exports cipher names for use with [Preferred].
use std::borrow::Borrow;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Debug;
use std::marker::PhantomData;
use std::num::Wrapping;
use aes::{Aes128, Aes192, Aes256};
use byteorder::{BigEndian, ByteOrder};
use cbc::CbcWrapper;
use ctr::Ctr128BE;
use des::TdesEde3;
use log::debug;
use once_cell::sync::Lazy;
use tokio::io::{AsyncRead, AsyncReadExt};
use crate::mac::MacAlgorithm;
use crate::sshbuffer::SSHBuffer;
use crate::Error;
pub(crate) mod block;
pub(crate) mod cbc;
pub(crate) mod chacha20poly1305;
pub(crate) mod clear;
pub(crate) mod gcm;
use block::SshBlockCipher;
use chacha20poly1305::SshChacha20Poly1305Cipher;
use clear::Clear;
use gcm::GcmCipher;
pub(crate) trait Cipher {
fn needs_mac(&self) -> bool {
false
}
fn key_len(&self) -> usize;
fn nonce_len(&self) -> usize {
0
}
fn make_opening_key(
&self,
key: &[u8],
nonce: &[u8],
mac_key: &[u8],
mac: &dyn MacAlgorithm,
) -> Box<dyn OpeningKey + Send>;
fn make_sealing_key(
&self,
key: &[u8],
nonce: &[u8],
mac_key: &[u8],
mac: &dyn MacAlgorithm,
) -> Box<dyn SealingKey + Send>;
}
/// `clear`
pub const CLEAR: Name = Name("clear");
/// `3des-cbc`
pub const TRIPLE_DES_CBC: Name = Name("3des-cbc");
/// `aes128-ctr`
pub const AES_128_CTR: Name = Name("aes128-ctr");
/// `aes192-ctr`
pub const AES_192_CTR: Name = Name("aes192-ctr");
/// `aes128-cbc`
pub const AES_128_CBC: Name = Name("aes128-cbc");
/// `aes192-cbc`
pub const AES_192_CBC: Name = Name("aes192-cbc");
/// `aes256-cbc`
pub const AES_256_CBC: Name = Name("aes256-cbc");
/// `aes256-ctr`
pub const AES_256_CTR: Name = Name("aes256-ctr");
/// `aes256-gcm@openssh.com`
pub const AES_256_GCM: Name = Name("aes256-gcm@openssh.com");
/// `chacha20-poly1305@openssh.com`
pub const CHACHA20_POLY1305: Name = Name("chacha20-poly1305@openssh.com");
/// `none`
pub const NONE: Name = Name("none");
static _CLEAR: Clear = Clear {};
static _3DES_CBC: SshBlockCipher<CbcWrapper<TdesEde3>> = SshBlockCipher(PhantomData);
static _AES_128_CTR: SshBlockCipher<Ctr128BE<Aes128>> = SshBlockCipher(PhantomData);
static _AES_192_CTR: SshBlockCipher<Ctr128BE<Aes192>> = SshBlockCipher(PhantomData);
static _AES_256_CTR: SshBlockCipher<Ctr128BE<Aes256>> = SshBlockCipher(PhantomData);
static _AES_256_GCM: GcmCipher = GcmCipher {};
static _AES_128_CBC: SshBlockCipher<CbcWrapper<Aes128>> = SshBlockCipher(PhantomData);
static _AES_192_CBC: SshBlockCipher<CbcWrapper<Aes192>> = SshBlockCipher(PhantomData);
static _AES_256_CBC: SshBlockCipher<CbcWrapper<Aes256>> = SshBlockCipher(PhantomData);
static _CHACHA20_POLY1305: SshChacha20Poly1305Cipher = SshChacha20Poly1305Cipher {};
pub static ALL_CIPHERS: &[&Name] = &[
&CLEAR,
&NONE,
&TRIPLE_DES_CBC,
&AES_128_CTR,
&AES_192_CTR,
&AES_256_CTR,
&AES_256_GCM,
&AES_128_CBC,
&AES_192_CBC,
&AES_256_CBC,
&CHACHA20_POLY1305,
];
pub(crate) static CIPHERS: Lazy<HashMap<&'static Name, &(dyn Cipher + Send + Sync)>> =
Lazy::new(|| {
let mut h: HashMap<&'static Name, &(dyn Cipher + Send + Sync)> = HashMap::new();
h.insert(&CLEAR, &_CLEAR);
h.insert(&NONE, &_CLEAR);
h.insert(&TRIPLE_DES_CBC, &_3DES_CBC);
h.insert(&AES_128_CTR, &_AES_128_CTR);
h.insert(&AES_192_CTR, &_AES_192_CTR);
h.insert(&AES_256_CTR, &_AES_256_CTR);
h.insert(&AES_256_GCM, &_AES_256_GCM);
h.insert(&AES_128_CBC, &_AES_128_CBC);
h.insert(&AES_192_CBC, &_AES_192_CBC);
h.insert(&AES_256_CBC, &_AES_256_CBC);
h.insert(&CHACHA20_POLY1305, &_CHACHA20_POLY1305);
assert_eq!(h.len(), ALL_CIPHERS.len());
h
});
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub struct Name(&'static str);
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
self.0
}
}
impl Borrow<str> for &Name {
fn borrow(&self) -> &str {
self.0
}
}
impl TryFrom<&str> for Name {
type Error = ();
fn try_from(s: &str) -> Result<Name, ()> {
CIPHERS.keys().find(|x| x.0 == s).map(|x| **x).ok_or(())
}
}
pub(crate) struct CipherPair {
pub local_to_remote: Box<dyn SealingKey + Send>,
pub remote_to_local: Box<dyn OpeningKey + Send>,
}
impl Debug for CipherPair {
fn fmt(&self, _: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
Ok(())
}
}
pub(crate) trait OpeningKey {
fn packet_length_to_read_for_block_length(&self) -> usize {
4
}
fn decrypt_packet_length(&self, seqn: u32, encrypted_packet_length: &[u8]) -> [u8; 4];
fn tag_len(&self) -> usize;
fn open<'a>(
&mut self,
seqn: u32,
ciphertext_in_plaintext_out: &'a mut [u8],
tag: &[u8],
) -> Result<&'a [u8], Error>;
}
pub(crate) trait SealingKey {
fn padding_length(&self, plaintext: &[u8]) -> usize;
fn fill_padding(&self, padding_out: &mut [u8]);
fn tag_len(&self) -> usize;
fn seal(&mut self, seqn: u32, plaintext_in_ciphertext_out: &mut [u8], tag_out: &mut [u8]);
fn write(&mut self, payload: &[u8], buffer: &mut SSHBuffer) {
// https://tools.ietf.org/html/rfc4253#section-6
//
// The variables `payload`, `packet_length` and `padding_length` refer
// to the protocol fields of the same names.
debug!("writing, seqn = {:?}", buffer.seqn.0);
let padding_length = self.padding_length(payload);
debug!("padding length {:?}", padding_length);
let packet_length = PADDING_LENGTH_LEN + payload.len() + padding_length;
debug!("packet_length {:?}", packet_length);
let offset = buffer.buffer.len();
// Maximum packet length:
// https://tools.ietf.org/html/rfc4253#section-6.1
assert!(packet_length <= u32::MAX as usize);
buffer.buffer.push_u32_be(packet_length as u32);
assert!(padding_length <= u8::MAX as usize);
buffer.buffer.push(padding_length as u8);
buffer.buffer.extend(payload);
self.fill_padding(buffer.buffer.resize_mut(padding_length));
buffer.buffer.resize_mut(self.tag_len());
#[allow(clippy::indexing_slicing)] // length checked
let (plaintext, tag) =
buffer.buffer[offset..].split_at_mut(PACKET_LENGTH_LEN + packet_length);
self.seal(buffer.seqn.0, plaintext, tag);
buffer.bytes += payload.len();
// Sequence numbers are on 32 bits and wrap.
// https://tools.ietf.org/html/rfc4253#section-6.4
buffer.seqn += Wrapping(1);
}
}
pub(crate) async fn read<'a, R: AsyncRead + Unpin>(
stream: &'a mut R,
buffer: &'a mut SSHBuffer,
cipher: &'a mut (dyn OpeningKey + Send),
) -> Result<usize, Error> {
if buffer.len == 0 {
let mut len = vec![0; cipher.packet_length_to_read_for_block_length()];
stream.read_exact(&mut len).await?;
debug!("reading, len = {:?}", len);
{
let seqn = buffer.seqn.0;
buffer.buffer.clear();
buffer.buffer.extend(&len);
debug!("reading, seqn = {:?}", seqn);
let len = cipher.decrypt_packet_length(seqn, &len);
let len = BigEndian::read_u32(&len) as usize;
if len > MAXIMUM_PACKET_LEN {
return Err(Error::PacketSize(len));
}
buffer.len = len + cipher.tag_len();
debug!("reading, clear len = {:?}", buffer.len);
}
}
buffer.buffer.resize(buffer.len + 4);
debug!("read_exact {:?}", buffer.len + 4);
#[allow(clippy::indexing_slicing)] // length checked
stream
.read_exact(&mut buffer.buffer[cipher.packet_length_to_read_for_block_length()..])
.await?;
debug!("read_exact done");
let seqn = buffer.seqn.0;
let ciphertext_len = buffer.buffer.len() - cipher.tag_len();
let (ciphertext, tag) = buffer.buffer.split_at_mut(ciphertext_len);
let plaintext = cipher.open(seqn, ciphertext, tag)?;
let padding_length = *plaintext.first().to_owned().unwrap_or(&0) as usize;
debug!("reading, padding_length {:?}", padding_length);
let plaintext_end = plaintext
.len()
.checked_sub(padding_length)
.ok_or(Error::IndexOutOfBounds)?;
// Sequence numbers are on 32 bits and wrap.
// https://tools.ietf.org/html/rfc4253#section-6.4
buffer.seqn += Wrapping(1);
buffer.len = 0;
// Remove the padding
buffer.buffer.resize(plaintext_end + 4);
Ok(plaintext_end + 4)
}
pub(crate) const PACKET_LENGTH_LEN: usize = 4;
const MINIMUM_PACKET_LEN: usize = 16;
const MAXIMUM_PACKET_LEN: usize = 256 * 1024;
const PADDING_LENGTH_LEN: usize = 1;

1092
vendor/russh/src/client/encrypted.rs vendored Normal file

File diff suppressed because it is too large Load Diff

77
vendor/russh/src/client/kex.rs vendored Normal file
View File

@@ -0,0 +1,77 @@
use log::{debug, trace};
use crate::cipher::SealingKey;
use crate::client::Config;
use crate::kex::KEXES;
use crate::negotiation;
use crate::negotiation::Select;
use crate::session::{KexDhDone, KexInit};
use crate::sshbuffer::SSHBuffer;
impl KexInit {
pub fn client_parse(
mut self,
config: &Config,
cipher: &mut dyn SealingKey,
buf: &[u8],
write_buffer: &mut SSHBuffer,
) -> Result<KexDhDone, crate::Error> {
trace!("client parse {:?} {:?}", buf.len(), buf);
let algo = {
// read algorithms from packet.
debug!("extending {:?}", &self.exchange.server_kex_init[..]);
self.exchange.server_kex_init.extend(buf);
negotiation::Client::read_kex(buf, &config.preferred, None)?
};
debug!("algo = {:?}", algo);
debug!("write = {:?}", &write_buffer.buffer[..]);
if !self.sent {
self.client_write(config, cipher, write_buffer)?
}
// This function is called from the public API.
//
// In order to simplify the public API, we reuse the
// self.exchange.client_kex buffer to send an extra packet,
// then truncate that buffer. Without that, we would need an
// extra buffer.
let i0 = self.exchange.client_kex_init.len();
debug!("i0 = {:?}", i0);
let mut kex = KEXES
.get(&algo.kex)
.ok_or(crate::Error::UnknownAlgo)?
.make();
kex.client_dh(
&mut self.exchange.client_ephemeral,
&mut self.exchange.client_kex_init,
)?;
#[allow(clippy::indexing_slicing)] // length checked
cipher.write(&self.exchange.client_kex_init[i0..], write_buffer);
self.exchange.client_kex_init.resize(i0);
debug!("moving to kexdhdone, exchange = {:?}", self.exchange);
Ok(KexDhDone {
exchange: self.exchange,
names: algo,
kex,
key: 0,
session_id: self.session_id,
})
}
pub fn client_write(
&mut self,
config: &Config,
cipher: &mut dyn SealingKey,
write_buffer: &mut SSHBuffer,
) -> Result<(), crate::Error> {
self.exchange.client_kex_init.clear();
negotiation::write_kex(&config.preferred, &mut self.exchange.client_kex_init, None)?;
self.sent = true;
cipher.write(&self.exchange.client_kex_init, write_buffer);
Ok(())
}
}

1826
vendor/russh/src/client/mod.rs vendored Normal file

File diff suppressed because it is too large Load Diff

462
vendor/russh/src/client/session.rs vendored Normal file
View File

@@ -0,0 +1,462 @@
use log::error;
use tokio::sync::oneshot;
use crate::client::Session;
use crate::keys::encoding::Encoding;
use crate::session::EncryptedState;
use crate::{msg, ChannelId, CryptoVec, Disconnect, Pty, Sig};
impl Session {
fn channel_open_generic<F>(
&mut self,
kind: &[u8],
write_suffix: F,
) -> Result<ChannelId, crate::Error>
where
F: FnOnce(&mut CryptoVec),
{
let result = if let Some(ref mut enc) = self.common.encrypted {
match enc.state {
EncryptedState::Authenticated => {
let sender_channel = enc.new_channel(
self.common.config.window_size,
self.common.config.maximum_packet_size,
);
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_OPEN);
enc.write.extend_ssh_string(kind);
// sender channel id.
enc.write.push_u32_be(sender_channel.0);
// window.
enc.write
.push_u32_be(self.common.config.as_ref().window_size);
// max packet size.
enc.write
.push_u32_be(self.common.config.as_ref().maximum_packet_size);
write_suffix(&mut enc.write);
});
sender_channel
}
_ => return Err(crate::Error::NotAuthenticated),
}
} else {
return Err(crate::Error::Inconsistent);
};
Ok(result)
}
pub fn channel_open_session(&mut self) -> Result<ChannelId, crate::Error> {
self.channel_open_generic(b"session", |_| ())
}
pub fn channel_open_x11(
&mut self,
originator_address: &str,
originator_port: u32,
) -> Result<ChannelId, crate::Error> {
self.channel_open_generic(b"x11", |write| {
write.extend_ssh_string(originator_address.as_bytes());
write.push_u32_be(originator_port); // sender channel id.
})
}
pub fn channel_open_direct_tcpip(
&mut self,
host_to_connect: &str,
port_to_connect: u32,
originator_address: &str,
originator_port: u32,
) -> Result<ChannelId, crate::Error> {
self.channel_open_generic(b"direct-tcpip", |write| {
write.extend_ssh_string(host_to_connect.as_bytes());
write.push_u32_be(port_to_connect); // sender channel id.
write.extend_ssh_string(originator_address.as_bytes());
write.push_u32_be(originator_port); // sender channel id.
})
}
pub fn channel_open_direct_streamlocal(
&mut self,
socket_path: &str,
) -> Result<ChannelId, crate::Error> {
self.channel_open_generic(b"direct-streamlocal@openssh.com", |write| {
write.extend_ssh_string(socket_path.as_bytes());
write.extend_ssh_string("".as_bytes()); // reserved
write.push_u32_be(0); // reserved
})
}
#[allow(clippy::too_many_arguments)]
pub fn request_pty(
&mut self,
channel: ChannelId,
want_reply: bool,
term: &str,
col_width: u32,
row_height: u32,
pix_width: u32,
pix_height: u32,
terminal_modes: &[(Pty, u32)],
) {
if let Some(ref mut enc) = self.common.encrypted {
if let Some(channel) = enc.channels.get(&channel) {
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_REQUEST);
enc.write.push_u32_be(channel.recipient_channel);
enc.write.extend_ssh_string(b"pty-req");
enc.write.push(want_reply as u8);
enc.write.extend_ssh_string(term.as_bytes());
enc.write.push_u32_be(col_width);
enc.write.push_u32_be(row_height);
enc.write.push_u32_be(pix_width);
enc.write.push_u32_be(pix_height);
enc.write.push_u32_be((1 + 5 * terminal_modes.len()) as u32);
for &(code, value) in terminal_modes {
enc.write.push(code as u8);
enc.write.push_u32_be(value)
}
// 0 code (to terminate the list)
enc.write.push(0);
});
}
}
}
pub fn request_x11(
&mut self,
channel: ChannelId,
want_reply: bool,
single_connection: bool,
x11_authentication_protocol: &str,
x11_authentication_cookie: &str,
x11_screen_number: u32,
) {
if let Some(ref mut enc) = self.common.encrypted {
if let Some(channel) = enc.channels.get(&channel) {
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_REQUEST);
enc.write.push_u32_be(channel.recipient_channel);
enc.write.extend_ssh_string(b"x11-req");
enc.write.push(want_reply as u8);
enc.write.push(single_connection as u8);
enc.write
.extend_ssh_string(x11_authentication_protocol.as_bytes());
enc.write
.extend_ssh_string(x11_authentication_cookie.as_bytes());
enc.write.push_u32_be(x11_screen_number);
});
}
}
}
pub fn set_env(
&mut self,
channel: ChannelId,
want_reply: bool,
variable_name: &str,
variable_value: &str,
) {
if let Some(ref mut enc) = self.common.encrypted {
if let Some(channel) = enc.channels.get(&channel) {
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_REQUEST);
enc.write.push_u32_be(channel.recipient_channel);
enc.write.extend_ssh_string(b"env");
enc.write.push(want_reply as u8);
enc.write.extend_ssh_string(variable_name.as_bytes());
enc.write.extend_ssh_string(variable_value.as_bytes());
});
}
}
}
pub fn request_shell(&mut self, want_reply: bool, channel: ChannelId) {
if let Some(ref mut enc) = self.common.encrypted {
if let Some(channel) = enc.channels.get(&channel) {
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_REQUEST);
enc.write.push_u32_be(channel.recipient_channel);
enc.write.extend_ssh_string(b"shell");
enc.write.push(want_reply as u8);
});
}
}
}
pub fn exec(&mut self, channel: ChannelId, want_reply: bool, command: &[u8]) {
if let Some(ref mut enc) = self.common.encrypted {
if let Some(channel) = enc.channels.get(&channel) {
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_REQUEST);
enc.write.push_u32_be(channel.recipient_channel);
enc.write.extend_ssh_string(b"exec");
enc.write.push(want_reply as u8);
enc.write.extend_ssh_string(command);
});
return;
}
}
error!("exec");
}
pub fn signal(&mut self, channel: ChannelId, signal: Sig) {
if let Some(ref mut enc) = self.common.encrypted {
if let Some(channel) = enc.channels.get(&channel) {
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_REQUEST);
enc.write.push_u32_be(channel.recipient_channel);
enc.write.extend_ssh_string(b"signal");
enc.write.push(0);
enc.write.extend_ssh_string(signal.name().as_bytes());
});
}
}
}
pub fn request_subsystem(&mut self, want_reply: bool, channel: ChannelId, name: &str) {
if let Some(ref mut enc) = self.common.encrypted {
if let Some(channel) = enc.channels.get(&channel) {
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_REQUEST);
enc.write.push_u32_be(channel.recipient_channel);
enc.write.extend_ssh_string(b"subsystem");
enc.write.push(want_reply as u8);
enc.write.extend_ssh_string(name.as_bytes());
});
}
}
}
pub fn window_change(
&mut self,
channel: ChannelId,
col_width: u32,
row_height: u32,
pix_width: u32,
pix_height: u32,
) {
if let Some(ref mut enc) = self.common.encrypted {
if let Some(channel) = enc.channels.get(&channel) {
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_REQUEST);
enc.write.push_u32_be(channel.recipient_channel);
enc.write.extend_ssh_string(b"window-change");
enc.write.push(0); // this packet never wants reply
enc.write.push_u32_be(col_width);
enc.write.push_u32_be(row_height);
enc.write.push_u32_be(pix_width);
enc.write.push_u32_be(pix_height);
});
}
}
}
/// Requests a TCP/IP forwarding from the server
///
/// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel,
/// [`Some<u32>`] for a success message with port, or [`None`] for failure
pub fn tcpip_forward(
&mut self,
reply_channel: Option<oneshot::Sender<Option<u32>>>,
address: &str,
port: u32,
) {
if let Some(ref mut enc) = self.common.encrypted {
let want_reply = reply_channel.is_some();
if let Some(reply_channel) = reply_channel {
self.open_global_requests.push_back(
crate::session::GlobalRequestResponse::TcpIpForward(reply_channel),
);
}
push_packet!(enc.write, {
enc.write.push(msg::GLOBAL_REQUEST);
enc.write.extend_ssh_string(b"tcpip-forward");
enc.write.push(want_reply as u8);
enc.write.extend_ssh_string(address.as_bytes());
enc.write.push_u32_be(port);
});
}
}
/// Requests cancellation of TCP/IP forwarding from the server
///
/// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel,
/// `true` for a success message, or `false` for failure
pub fn cancel_tcpip_forward(
&mut self,
reply_channel: Option<oneshot::Sender<bool>>,
address: &str,
port: u32,
) {
if let Some(ref mut enc) = self.common.encrypted {
let want_reply = reply_channel.is_some();
if let Some(reply_channel) = reply_channel {
self.open_global_requests.push_back(
crate::session::GlobalRequestResponse::CancelTcpIpForward(reply_channel),
);
}
push_packet!(enc.write, {
enc.write.push(msg::GLOBAL_REQUEST);
enc.write.extend_ssh_string(b"cancel-tcpip-forward");
enc.write.push(want_reply as u8);
enc.write.extend_ssh_string(address.as_bytes());
enc.write.push_u32_be(port);
});
}
}
/// Requests a UDS forwarding from the server, `socket path` being the server side socket path.
///
/// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel,
/// `true` for a success message, or `false` for failure
pub fn streamlocal_forward(
&mut self,
reply_channel: Option<oneshot::Sender<bool>>,
socket_path: &str,
) {
if let Some(ref mut enc) = self.common.encrypted {
let want_reply = reply_channel.is_some();
if let Some(reply_channel) = reply_channel {
self.open_global_requests.push_back(
crate::session::GlobalRequestResponse::StreamLocalForward(reply_channel),
);
}
push_packet!(enc.write, {
enc.write.push(msg::GLOBAL_REQUEST);
enc.write
.extend_ssh_string(b"streamlocal-forward@openssh.com");
enc.write.push(want_reply as u8);
enc.write.extend_ssh_string(socket_path.as_bytes());
});
}
}
/// Requests cancellation of UDS forwarding from the server
///
/// If `reply_channel` is not None, sets want_reply and returns the server's response via the channel,
/// `true` for a success message and `false` for failure.
pub fn cancel_streamlocal_forward(
&mut self,
reply_channel: Option<oneshot::Sender<bool>>,
socket_path: &str,
) {
if let Some(ref mut enc) = self.common.encrypted {
let want_reply = reply_channel.is_some();
if let Some(reply_channel) = reply_channel {
self.open_global_requests.push_back(
crate::session::GlobalRequestResponse::CancelStreamLocalForward(reply_channel),
);
}
push_packet!(enc.write, {
enc.write.push(msg::GLOBAL_REQUEST);
enc.write
.extend_ssh_string(b"cancel-streamlocal-forward@openssh.com");
enc.write.push(want_reply as u8);
enc.write.extend_ssh_string(socket_path.as_bytes());
});
}
}
pub fn send_keepalive(&mut self, want_reply: bool) {
self.open_global_requests
.push_back(crate::session::GlobalRequestResponse::Keepalive);
if let Some(ref mut enc) = self.common.encrypted {
push_packet!(enc.write, {
enc.write.push(msg::GLOBAL_REQUEST);
enc.write.extend_ssh_string(b"keepalive@openssh.com");
enc.write.push(want_reply as u8);
});
}
}
pub fn data(&mut self, channel: ChannelId, data: CryptoVec) {
if let Some(ref mut enc) = self.common.encrypted {
enc.data(channel, data)
} else {
unreachable!()
}
}
pub fn eof(&mut self, channel: ChannelId) {
if let Some(ref mut enc) = self.common.encrypted {
enc.eof(channel)
} else {
unreachable!()
}
}
pub fn close(&mut self, channel: ChannelId) {
if let Some(ref mut enc) = self.common.encrypted {
enc.close(channel)
} else {
unreachable!()
}
}
pub fn extended_data(&mut self, channel: ChannelId, ext: u32, data: CryptoVec) {
if let Some(ref mut enc) = self.common.encrypted {
enc.extended_data(channel, ext, data)
} else {
unreachable!()
}
}
pub fn agent_forward(&mut self, channel: ChannelId, want_reply: bool) {
if let Some(ref mut enc) = self.common.encrypted {
if let Some(channel) = enc.channels.get(&channel) {
push_packet!(enc.write, {
enc.write.push(msg::CHANNEL_REQUEST);
enc.write.push_u32_be(channel.recipient_channel);
enc.write.extend_ssh_string(b"auth-agent-req@openssh.com");
enc.write.push(want_reply as u8);
});
}
}
}
pub fn disconnect(&mut self, reason: Disconnect, description: &str, language_tag: &str) {
self.common.disconnect(reason, description, language_tag);
}
pub fn has_pending_data(&self, channel: ChannelId) -> bool {
if let Some(ref enc) = self.common.encrypted {
enc.has_pending_data(channel)
} else {
false
}
}
pub fn sender_window_size(&self, channel: ChannelId) -> usize {
if let Some(ref enc) = self.common.encrypted {
enc.sender_window_size(channel)
} else {
0
}
}
/// Returns the SSH ID (Protocol Version + Software Version) the server sent when connecting
///
/// This should contain only ASCII characters for implementations conforming to RFC4253, Section 4.2:
///
/// > Both the 'protoversion' and 'softwareversion' strings MUST consist of
/// > printable US-ASCII characters, with the exception of whitespace
/// > characters and the minus sign (-).
///
/// So it usually is fine to convert it to a `String` using `String::from_utf8_lossy`
pub fn remote_sshid(&self) -> &[u8] {
&self.common.remote_sshid
}
}

193
vendor/russh/src/compression.rs vendored Normal file
View File

@@ -0,0 +1,193 @@
use std::convert::TryFrom;
#[derive(Debug, Clone)]
pub enum Compression {
None,
#[cfg(feature = "flate2")]
Zlib,
}
#[derive(Debug)]
pub enum Compress {
None,
#[cfg(feature = "flate2")]
Zlib(flate2::Compress),
}
#[derive(Debug)]
pub enum Decompress {
None,
#[cfg(feature = "flate2")]
Zlib(flate2::Decompress),
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub struct Name(&'static str);
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
self.0
}
}
impl TryFrom<&str> for Name {
type Error = ();
fn try_from(s: &str) -> Result<Name, ()> {
ALL_COMPRESSION_ALGORITHMS
.iter()
.find(|x| x.0 == s)
.map(|x| **x)
.ok_or(())
}
}
pub const NONE: Name = Name("none");
#[cfg(feature = "flate2")]
pub const ZLIB: Name = Name("zlib");
#[cfg(feature = "flate2")]
pub const ZLIB_LEGACY: Name = Name("zlib@openssh.com");
pub const ALL_COMPRESSION_ALGORITHMS: &[&Name] = &[
&NONE,
#[cfg(feature = "flate2")]
&ZLIB,
#[cfg(feature = "flate2")]
&ZLIB_LEGACY,
];
#[cfg(feature = "flate2")]
impl Compression {
pub fn new(name: &Name) -> Self {
if name == &ZLIB || name == &ZLIB_LEGACY {
Compression::Zlib
} else {
Compression::None
}
}
pub fn init_compress(&self, comp: &mut Compress) {
if let Compression::Zlib = *self {
if let Compress::Zlib(ref mut c) = *comp {
c.reset()
} else {
*comp = Compress::Zlib(flate2::Compress::new(flate2::Compression::fast(), true))
}
} else {
*comp = Compress::None
}
}
pub fn init_decompress(&self, comp: &mut Decompress) {
if let Compression::Zlib = *self {
if let Decompress::Zlib(ref mut c) = *comp {
c.reset(true)
} else {
*comp = Decompress::Zlib(flate2::Decompress::new(true))
}
} else {
*comp = Decompress::None
}
}
}
#[cfg(not(feature = "flate2"))]
impl Compression {
pub fn new(_name: &Name) -> Self {
Compression::None
}
pub fn init_compress(&self, _: &mut Compress) {}
pub fn init_decompress(&self, _: &mut Decompress) {}
}
#[cfg(not(feature = "flate2"))]
impl Compress {
pub fn compress<'a>(
&mut self,
input: &'a [u8],
_: &'a mut russh_cryptovec::CryptoVec,
) -> Result<&'a [u8], crate::Error> {
Ok(input)
}
}
#[cfg(not(feature = "flate2"))]
impl Decompress {
pub fn decompress<'a>(
&mut self,
input: &'a [u8],
_: &'a mut russh_cryptovec::CryptoVec,
) -> Result<&'a [u8], crate::Error> {
Ok(input)
}
}
#[cfg(feature = "flate2")]
impl Compress {
pub fn compress<'a>(
&mut self,
input: &'a [u8],
output: &'a mut russh_cryptovec::CryptoVec,
) -> Result<&'a [u8], crate::Error> {
match *self {
Compress::None => Ok(input),
Compress::Zlib(ref mut z) => {
output.clear();
let n_in = z.total_in() as usize;
let n_out = z.total_out() as usize;
output.resize(input.len() + 10);
let flush = flate2::FlushCompress::Partial;
loop {
let n_in_ = z.total_in() as usize - n_in;
let n_out_ = z.total_out() as usize - n_out;
#[allow(clippy::indexing_slicing)] // length checked
let c = z.compress(&input[n_in_..], &mut output[n_out_..], flush)?;
match c {
flate2::Status::BufError => {
output.resize(output.len() * 2);
}
_ => break,
}
}
let n_out_ = z.total_out() as usize - n_out;
#[allow(clippy::indexing_slicing)] // length checked
Ok(&output[..n_out_])
}
}
}
}
#[cfg(feature = "flate2")]
impl Decompress {
pub fn decompress<'a>(
&mut self,
input: &'a [u8],
output: &'a mut russh_cryptovec::CryptoVec,
) -> Result<&'a [u8], crate::Error> {
match *self {
Decompress::None => Ok(input),
Decompress::Zlib(ref mut z) => {
output.clear();
let n_in = z.total_in() as usize;
let n_out = z.total_out() as usize;
output.resize(input.len());
let flush = flate2::FlushDecompress::None;
loop {
let n_in_ = z.total_in() as usize - n_in;
let n_out_ = z.total_out() as usize - n_out;
#[allow(clippy::indexing_slicing)] // length checked
let d = z.decompress(&input[n_in_..], &mut output[n_out_..], flush);
match d? {
flate2::Status::Ok => {
output.resize(output.len() * 2);
}
_ => break,
}
}
let n_out_ = z.total_out() as usize - n_out;
#[allow(clippy::indexing_slicing)] // length checked
Ok(&output[..n_out_])
}
}
}
}

162
vendor/russh/src/kex/curve25519.rs vendored Normal file
View File

@@ -0,0 +1,162 @@
use byteorder::{BigEndian, ByteOrder};
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
use curve25519_dalek::montgomery::MontgomeryPoint;
use curve25519_dalek::scalar::Scalar;
use log::debug;
use super::{compute_keys, KexAlgorithm, KexType};
use crate::keys::encoding::Encoding;
use crate::mac::{self};
use crate::session::Exchange;
use crate::{cipher, msg, CryptoVec};
pub struct Curve25519KexType {}
impl KexType for Curve25519KexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send> {
Box::new(Curve25519Kex {
local_secret: None,
shared_secret: None,
}) as Box<dyn KexAlgorithm + Send>
}
}
#[doc(hidden)]
pub struct Curve25519Kex {
local_secret: Option<Scalar>,
shared_secret: Option<MontgomeryPoint>,
}
impl std::fmt::Debug for Curve25519Kex {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Algorithm {{ local_secret: [hidden], shared_secret: [hidden] }}",
)
}
}
// We used to support curve "NIST P-256" here, but the security of
// that curve is controversial, see
// http://safecurves.cr.yp.to/rigid.html
impl KexAlgorithm for Curve25519Kex {
fn skip_exchange(&self) -> bool {
false
}
#[doc(hidden)]
fn server_dh(&mut self, exchange: &mut Exchange, payload: &[u8]) -> Result<(), crate::Error> {
debug!("server_dh");
let client_pubkey = {
if payload.first() != Some(&msg::KEX_ECDH_INIT) {
return Err(crate::Error::Inconsistent);
}
#[allow(clippy::indexing_slicing)] // length checked
let pubkey_len = BigEndian::read_u32(&payload[1..]) as usize;
if pubkey_len != 32 {
return Err(crate::Error::Kex);
}
if payload.len() < 5 + pubkey_len {
return Err(crate::Error::Inconsistent);
}
let mut pubkey = MontgomeryPoint([0; 32]);
#[allow(clippy::indexing_slicing)] // length checked
pubkey.0.clone_from_slice(&payload[5..5 + 32]);
pubkey
};
let server_secret = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
let server_pubkey = (ED25519_BASEPOINT_TABLE * &server_secret).to_montgomery();
// fill exchange.
exchange.server_ephemeral.clear();
exchange.server_ephemeral.extend(&server_pubkey.0);
let shared = server_secret * client_pubkey;
self.shared_secret = Some(shared);
Ok(())
}
#[doc(hidden)]
fn client_dh(
&mut self,
client_ephemeral: &mut CryptoVec,
buf: &mut CryptoVec,
) -> Result<(), crate::Error> {
let client_secret = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
let client_pubkey = (ED25519_BASEPOINT_TABLE * &client_secret).to_montgomery();
// fill exchange.
client_ephemeral.clear();
client_ephemeral.extend(&client_pubkey.0);
buf.push(msg::KEX_ECDH_INIT);
buf.extend_ssh_string(&client_pubkey.0);
self.local_secret = Some(client_secret);
Ok(())
}
fn compute_shared_secret(&mut self, remote_pubkey_: &[u8]) -> Result<(), crate::Error> {
let local_secret = self.local_secret.take().ok_or(crate::Error::KexInit)?;
let mut remote_pubkey = MontgomeryPoint([0; 32]);
remote_pubkey.0.clone_from_slice(remote_pubkey_);
let shared = local_secret * remote_pubkey;
self.shared_secret = Some(shared);
Ok(())
}
fn compute_exchange_hash(
&self,
key: &CryptoVec,
exchange: &Exchange,
buffer: &mut CryptoVec,
) -> Result<CryptoVec, crate::Error> {
// Computing the exchange hash, see page 7 of RFC 5656.
buffer.clear();
buffer.extend_ssh_string(&exchange.client_id);
buffer.extend_ssh_string(&exchange.server_id);
buffer.extend_ssh_string(&exchange.client_kex_init);
buffer.extend_ssh_string(&exchange.server_kex_init);
buffer.extend(key);
buffer.extend_ssh_string(&exchange.client_ephemeral);
buffer.extend_ssh_string(&exchange.server_ephemeral);
if let Some(ref shared) = self.shared_secret {
buffer.extend_ssh_mpint(&shared.0);
}
use sha2::Digest;
let mut hasher = sha2::Sha256::new();
hasher.update(&buffer);
let mut res = CryptoVec::new();
res.extend(hasher.finalize().as_slice());
Ok(res)
}
fn compute_keys(
&self,
session_id: &CryptoVec,
exchange_hash: &CryptoVec,
cipher: cipher::Name,
remote_to_local_mac: mac::Name,
local_to_remote_mac: mac::Name,
is_server: bool,
) -> Result<super::cipher::CipherPair, crate::Error> {
compute_keys::<sha2::Sha256>(
self.shared_secret.as_ref().map(|x| x.0.as_slice()),
session_id,
exchange_hash,
cipher,
remote_to_local_mac,
local_to_remote_mac,
is_server,
)
}
}

137
vendor/russh/src/kex/dh/groups.rs vendored Normal file
View File

@@ -0,0 +1,137 @@
use hex_literal::hex;
use num_bigint::{BigUint, RandBigInt};
use rand;
pub struct DhGroup {
pub(crate) prime: &'static [u8],
pub(crate) generator: usize,
pub(crate) exp_size: u64,
}
pub const DH_GROUP1: DhGroup = DhGroup {
prime: hex!(
"
FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE65381
FFFFFFFF FFFFFFFF
"
)
.as_slice(),
generator: 2,
exp_size: 256,
};
pub const DH_GROUP14: DhGroup = DhGroup {
prime: hex!(
"
FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
15728E5A 8AACAA68 FFFFFFFF FFFFFFFF
"
)
.as_slice(),
generator: 2,
exp_size: 256,
};
pub const DH_GROUP16: DhGroup = DhGroup {
prime: hex!(
"
FFFFFFFF FFFFFFFF C90FDAA2 2168C234 C4C6628B 80DC1CD1
29024E08 8A67CC74 020BBEA6 3B139B22 514A0879 8E3404DD
EF9519B3 CD3A431B 302B0A6D F25F1437 4FE1356D 6D51C245
E485B576 625E7EC6 F44C42E9 A637ED6B 0BFF5CB6 F406B7ED
EE386BFB 5A899FA5 AE9F2411 7C4B1FE6 49286651 ECE45B3D
C2007CB8 A163BF05 98DA4836 1C55D39A 69163FA8 FD24CF5F
83655D23 DCA3AD96 1C62F356 208552BB 9ED52907 7096966D
670C354E 4ABC9804 F1746C08 CA18217C 32905E46 2E36CE3B
E39E772C 180E8603 9B2783A2 EC07A28F B5C55DF0 6F4C52C9
DE2BCBF6 95581718 3995497C EA956AE5 15D22618 98FA0510
15728E5A 8AAAC42D AD33170D 04507A33 A85521AB DF1CBA64
ECFB8504 58DBEF0A 8AEA7157 5D060C7D B3970F85 A6E1E4C7
ABF5AE8C DB0933D7 1E8C94E0 4A25619D CEE3D226 1AD2EE6B
F12FFA06 D98A0864 D8760273 3EC86A64 521F2B18 177B200C
BBE11757 7A615D6C 770988C0 BAD946E2 08E24FA0 74E5AB31
43DB5BFC E0FD108E 4B82D120 A9210801 1A723C12 A787E6D7
88719A10 BDBA5B26 99C32718 6AF4E23C 1A946834 B6150BDA
2583E9CA 2AD44CE8 DBBBC2DB 04DE8EF9 2E8EFC14 1FBECAA6
287C5947 4E6BC05D 99B2964F A090C3A2 233BA186 515BE7ED
1F612970 CEE2D7AF B81BDD76 2170481C D0069127 D5B05AA9
93B4EA98 8D8FDDC1 86FFB7DC 90A6C08F 4DF435C9 34063199
FFFFFFFF FFFFFFFF
"
)
.as_slice(),
generator: 2,
exp_size: 512,
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct DH {
prime_num: BigUint,
generator: usize,
exp_size: u64,
private_key: BigUint,
public_key: BigUint,
shared_secret: BigUint,
}
impl DH {
pub fn new(group: &DhGroup) -> Self {
Self {
prime_num: BigUint::from_bytes_be(group.prime),
generator: group.generator,
exp_size: group.exp_size,
private_key: BigUint::default(),
public_key: BigUint::default(),
shared_secret: BigUint::default(),
}
}
pub fn generate_private_key(&mut self, is_server: bool) -> BigUint {
let q = (&self.prime_num - &BigUint::from(1u8)) / &BigUint::from(2u8);
let mut rng = rand::thread_rng();
self.private_key =
rng.gen_biguint_range(&if is_server { 1u8.into() } else { 2u8.into() }, &q);
self.private_key.clone()
}
pub fn generate_public_key(&mut self) -> BigUint {
self.public_key = BigUint::from(self.generator).modpow(&self.private_key, &self.prime_num);
self.public_key.clone()
}
pub fn compute_shared_secret(&mut self, other_public_key: BigUint) -> BigUint {
self.shared_secret = other_public_key.modpow(&self.private_key, &self.prime_num);
self.shared_secret.clone()
}
pub fn validate_shared_secret(&self, shared_secret: &BigUint) -> bool {
let one = BigUint::from(1u8);
let prime_minus_one = &self.prime_num - &one;
shared_secret > &one && shared_secret < &prime_minus_one
}
pub fn decode_public_key(buffer: &[u8]) -> BigUint {
BigUint::from_bytes_be(buffer)
}
pub fn validate_public_key(&self, public_key: &BigUint) -> bool {
let one = BigUint::from(1u8);
let prime_minus_one = &self.prime_num - &one;
public_key > &one && public_key < &prime_minus_one
}
}

227
vendor/russh/src/kex/dh/mod.rs vendored Normal file
View File

@@ -0,0 +1,227 @@
mod groups;
use std::marker::PhantomData;
use byteorder::{BigEndian, ByteOrder};
use digest::Digest;
use groups::DH;
use log::debug;
use num_bigint::BigUint;
use sha1::Sha1;
use sha2::{Sha256, Sha512};
use self::groups::{DhGroup, DH_GROUP1, DH_GROUP14, DH_GROUP16};
use super::{compute_keys, KexAlgorithm, KexType};
use crate::keys::encoding::Encoding;
use crate::session::Exchange;
use crate::{cipher, mac, msg, CryptoVec};
pub struct DhGroup1Sha1KexType {}
impl KexType for DhGroup1Sha1KexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send> {
Box::new(DhGroupKex::<Sha1>::new(&DH_GROUP1)) as Box<dyn KexAlgorithm + Send>
}
}
pub struct DhGroup14Sha1KexType {}
impl KexType for DhGroup14Sha1KexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send> {
Box::new(DhGroupKex::<Sha1>::new(&DH_GROUP14)) as Box<dyn KexAlgorithm + Send>
}
}
pub struct DhGroup14Sha256KexType {}
impl KexType for DhGroup14Sha256KexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send> {
Box::new(DhGroupKex::<Sha256>::new(&DH_GROUP14)) as Box<dyn KexAlgorithm + Send>
}
}
pub struct DhGroup16Sha512KexType {}
impl KexType for DhGroup16Sha512KexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send> {
Box::new(DhGroupKex::<Sha512>::new(&DH_GROUP16)) as Box<dyn KexAlgorithm + Send>
}
}
#[doc(hidden)]
pub struct DhGroupKex<D: Digest> {
dh: DH,
shared_secret: Option<Vec<u8>>,
_digest: PhantomData<D>,
}
impl<D: Digest> DhGroupKex<D> {
pub fn new(group: &DhGroup) -> DhGroupKex<D> {
let dh = DH::new(group);
DhGroupKex {
dh,
shared_secret: None,
_digest: PhantomData,
}
}
}
impl<D: Digest> std::fmt::Debug for DhGroupKex<D> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Algorithm {{ local_secret: [hidden], shared_secret: [hidden] }}",
)
}
}
fn biguint_to_mpint(biguint: &BigUint) -> Vec<u8> {
let mut mpint = Vec::new();
let bytes = biguint.to_bytes_be();
if let Some(b) = bytes.first() {
if b > &0x7f {
mpint.push(0);
}
}
mpint.extend(&bytes);
mpint
}
impl<D: Digest> KexAlgorithm for DhGroupKex<D> {
fn skip_exchange(&self) -> bool {
false
}
#[doc(hidden)]
fn server_dh(&mut self, exchange: &mut Exchange, payload: &[u8]) -> Result<(), crate::Error> {
debug!("server_dh");
let client_pubkey = {
if payload.first() != Some(&msg::KEX_ECDH_INIT) {
return Err(crate::Error::Inconsistent);
}
#[allow(clippy::indexing_slicing)] // length checked
let pubkey_len = BigEndian::read_u32(&payload[1..]) as usize;
if payload.len() < 5 + pubkey_len {
return Err(crate::Error::Inconsistent);
}
&payload
.get(5..(5 + pubkey_len))
.ok_or(crate::Error::Inconsistent)?
};
debug!("client_pubkey: {:?}", client_pubkey);
self.dh.generate_private_key(true);
let server_pubkey = &self.dh.generate_public_key();
if !self.dh.validate_public_key(server_pubkey) {
return Err(crate::Error::Inconsistent);
}
let encoded_server_pubkey = biguint_to_mpint(server_pubkey);
// fill exchange.
exchange.server_ephemeral.clear();
exchange.server_ephemeral.extend(&encoded_server_pubkey);
let decoded_client_pubkey = DH::decode_public_key(client_pubkey);
if !self.dh.validate_public_key(&decoded_client_pubkey) {
return Err(crate::Error::Inconsistent);
}
let shared = self.dh.compute_shared_secret(decoded_client_pubkey);
if !self.dh.validate_shared_secret(&shared) {
return Err(crate::Error::Inconsistent);
}
self.shared_secret = Some(biguint_to_mpint(&shared));
Ok(())
}
#[doc(hidden)]
fn client_dh(
&mut self,
client_ephemeral: &mut CryptoVec,
buf: &mut CryptoVec,
) -> Result<(), crate::Error> {
self.dh.generate_private_key(false);
let client_pubkey = &self.dh.generate_public_key();
if !self.dh.validate_public_key(client_pubkey) {
return Err(crate::Error::Inconsistent);
}
// fill exchange.
let encoded_pubkey = biguint_to_mpint(client_pubkey);
client_ephemeral.clear();
client_ephemeral.extend(&encoded_pubkey);
buf.push(msg::KEX_ECDH_INIT);
buf.extend_ssh_string(&encoded_pubkey);
Ok(())
}
fn compute_shared_secret(&mut self, remote_pubkey_: &[u8]) -> Result<(), crate::Error> {
let remote_pubkey = DH::decode_public_key(remote_pubkey_);
if !self.dh.validate_public_key(&remote_pubkey) {
return Err(crate::Error::Inconsistent);
}
let shared = self.dh.compute_shared_secret(remote_pubkey);
if !self.dh.validate_shared_secret(&shared) {
return Err(crate::Error::Inconsistent);
}
self.shared_secret = Some(biguint_to_mpint(&shared));
Ok(())
}
fn compute_exchange_hash(
&self,
key: &CryptoVec,
exchange: &Exchange,
buffer: &mut CryptoVec,
) -> Result<CryptoVec, crate::Error> {
// Computing the exchange hash, see page 7 of RFC 5656.
buffer.clear();
buffer.extend_ssh_string(&exchange.client_id);
buffer.extend_ssh_string(&exchange.server_id);
buffer.extend_ssh_string(&exchange.client_kex_init);
buffer.extend_ssh_string(&exchange.server_kex_init);
buffer.extend(key);
buffer.extend_ssh_string(&exchange.client_ephemeral);
buffer.extend_ssh_string(&exchange.server_ephemeral);
if let Some(ref shared) = self.shared_secret {
buffer.extend_ssh_mpint(shared);
}
let mut hasher = D::new();
hasher.update(&buffer);
let mut res = CryptoVec::new();
res.extend(hasher.finalize().as_slice());
Ok(res)
}
fn compute_keys(
&self,
session_id: &CryptoVec,
exchange_hash: &CryptoVec,
cipher: cipher::Name,
remote_to_local_mac: mac::Name,
local_to_remote_mac: mac::Name,
is_server: bool,
) -> Result<super::cipher::CipherPair, crate::Error> {
compute_keys::<D>(
self.shared_secret.as_deref(),
session_id,
exchange_hash,
cipher,
remote_to_local_mac,
local_to_remote_mac,
is_server,
)
}
}

234
vendor/russh/src/kex/ecdh_nistp.rs vendored Normal file
View File

@@ -0,0 +1,234 @@
use std::marker::PhantomData;
use byteorder::{BigEndian, ByteOrder};
use elliptic_curve::ecdh::{EphemeralSecret, SharedSecret};
use elliptic_curve::point::PointCompression;
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use elliptic_curve::{AffinePoint, Curve, CurveArithmetic, FieldBytesSize};
use log::debug;
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
use sha2::{Digest, Sha256, Sha384, Sha512};
use crate::kex::{compute_keys, KexAlgorithm, KexType};
use crate::keys::encoding::Encoding;
use crate::mac::{self};
use crate::session::Exchange;
use crate::{cipher, msg, CryptoVec};
pub struct EcdhNistP256KexType {}
impl KexType for EcdhNistP256KexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send> {
Box::new(EcdhNistPKex::<NistP256, Sha256> {
local_secret: None,
shared_secret: None,
_digest: PhantomData,
}) as Box<dyn KexAlgorithm + Send>
}
}
pub struct EcdhNistP384KexType {}
impl KexType for EcdhNistP384KexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send> {
Box::new(EcdhNistPKex::<NistP384, Sha384> {
local_secret: None,
shared_secret: None,
_digest: PhantomData,
}) as Box<dyn KexAlgorithm + Send>
}
}
pub struct EcdhNistP521KexType {}
impl KexType for EcdhNistP521KexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send> {
Box::new(EcdhNistPKex::<NistP521, Sha512> {
local_secret: None,
shared_secret: None,
_digest: PhantomData,
}) as Box<dyn KexAlgorithm + Send>
}
}
#[doc(hidden)]
pub struct EcdhNistPKex<C: Curve + CurveArithmetic, D: Digest> {
local_secret: Option<EphemeralSecret<C>>,
shared_secret: Option<SharedSecret<C>>,
_digest: PhantomData<D>,
}
impl<C: Curve + CurveArithmetic, D: Digest> std::fmt::Debug for EcdhNistPKex<C, D> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"Algorithm {{ local_secret: [hidden], shared_secret: [hidden] }}",
)
}
}
impl<C: Curve + CurveArithmetic, D: Digest> KexAlgorithm for EcdhNistPKex<C, D>
where
C: PointCompression,
FieldBytesSize<C>: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
fn skip_exchange(&self) -> bool {
false
}
#[doc(hidden)]
fn server_dh(&mut self, exchange: &mut Exchange, payload: &[u8]) -> Result<(), crate::Error> {
debug!("server_dh");
let client_pubkey = {
if payload.first() != Some(&msg::KEX_ECDH_INIT) {
return Err(crate::Error::Inconsistent);
}
#[allow(clippy::indexing_slicing)] // length checked
let pubkey_len = BigEndian::read_u32(&payload[1..]) as usize;
if payload.len() < 5 + pubkey_len {
return Err(crate::Error::Inconsistent);
}
#[allow(clippy::indexing_slicing)] // length checked
elliptic_curve::PublicKey::<C>::from_sec1_bytes(&payload[5..(5 + pubkey_len)])
.map_err(|_| crate::Error::Inconsistent)?
};
let server_secret =
elliptic_curve::ecdh::EphemeralSecret::<C>::random(&mut rand_core::OsRng);
let server_pubkey = server_secret.public_key();
// fill exchange.
exchange.server_ephemeral.clear();
exchange
.server_ephemeral
.extend(&server_pubkey.to_sec1_bytes());
let shared = server_secret.diffie_hellman(&client_pubkey);
self.shared_secret = Some(shared);
Ok(())
}
#[doc(hidden)]
fn client_dh(
&mut self,
client_ephemeral: &mut CryptoVec,
buf: &mut CryptoVec,
) -> Result<(), crate::Error> {
let client_secret =
elliptic_curve::ecdh::EphemeralSecret::<C>::random(&mut rand_core::OsRng);
let client_pubkey = client_secret.public_key();
// fill exchange.
client_ephemeral.clear();
client_ephemeral.extend(&client_pubkey.to_sec1_bytes());
buf.push(msg::KEX_ECDH_INIT);
buf.extend_ssh_string(&client_pubkey.to_sec1_bytes());
self.local_secret = Some(client_secret);
Ok(())
}
fn compute_shared_secret(&mut self, remote_pubkey_: &[u8]) -> Result<(), crate::Error> {
let local_secret = self.local_secret.take().ok_or(crate::Error::KexInit)?;
let pubkey = elliptic_curve::PublicKey::<C>::from_sec1_bytes(remote_pubkey_)
.map_err(|_| crate::Error::KexInit)?;
self.shared_secret = Some(local_secret.diffie_hellman(&pubkey));
Ok(())
}
fn compute_exchange_hash(
&self,
key: &CryptoVec,
exchange: &Exchange,
buffer: &mut CryptoVec,
) -> Result<CryptoVec, crate::Error> {
// Computing the exchange hash, see page 7 of RFC 5656.
buffer.clear();
buffer.extend_ssh_string(&exchange.client_id);
buffer.extend_ssh_string(&exchange.server_id);
buffer.extend_ssh_string(&exchange.client_kex_init);
buffer.extend_ssh_string(&exchange.server_kex_init);
buffer.extend(key);
buffer.extend_ssh_string(&exchange.client_ephemeral);
buffer.extend_ssh_string(&exchange.server_ephemeral);
if let Some(ref shared) = self.shared_secret {
buffer.extend_ssh_mpint(shared.raw_secret_bytes());
}
let mut hasher = D::new();
hasher.update(&buffer);
let mut res = CryptoVec::new();
res.extend(hasher.finalize().as_slice());
Ok(res)
}
fn compute_keys(
&self,
session_id: &CryptoVec,
exchange_hash: &CryptoVec,
cipher: cipher::Name,
remote_to_local_mac: mac::Name,
local_to_remote_mac: mac::Name,
is_server: bool,
) -> Result<crate::kex::cipher::CipherPair, crate::Error> {
compute_keys::<D>(
self.shared_secret
.as_ref()
.map(|x| x.raw_secret_bytes() as &[u8]),
session_id,
exchange_hash,
cipher,
remote_to_local_mac,
local_to_remote_mac,
is_server,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_shared_secret() {
let mut party1 = EcdhNistPKex::<NistP256, Sha256> {
local_secret: Some(EphemeralSecret::<NistP256>::random(&mut rand_core::OsRng)),
shared_secret: None,
_digest: PhantomData,
};
let p1_pubkey = party1.local_secret.as_ref().unwrap().public_key();
let mut party2 = EcdhNistPKex::<NistP256, Sha256> {
local_secret: Some(EphemeralSecret::<NistP256>::random(&mut rand_core::OsRng)),
shared_secret: None,
_digest: PhantomData,
};
let p2_pubkey = party2.local_secret.as_ref().unwrap().public_key();
party1
.compute_shared_secret(&p2_pubkey.to_sec1_bytes())
.unwrap();
party2
.compute_shared_secret(&p1_pubkey.to_sec1_bytes())
.unwrap();
let p1_shared_secret = party1.shared_secret.unwrap();
let p2_shared_secret = party2.shared_secret.unwrap();
assert_eq!(
p1_shared_secret.raw_secret_bytes(),
p2_shared_secret.raw_secret_bytes()
)
}
}

286
vendor/russh/src/kex/mod.rs vendored Normal file
View File

@@ -0,0 +1,286 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//!
//! This module exports kex algorithm names for use with [Preferred].
mod curve25519;
mod dh;
mod ecdh_nistp;
mod none;
use std::cell::RefCell;
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::Debug;
use curve25519::Curve25519KexType;
use dh::{
DhGroup14Sha1KexType, DhGroup14Sha256KexType, DhGroup16Sha512KexType, DhGroup1Sha1KexType,
};
use digest::Digest;
use ecdh_nistp::{EcdhNistP256KexType, EcdhNistP384KexType, EcdhNistP521KexType};
use once_cell::sync::Lazy;
use crate::cipher::CIPHERS;
use crate::keys::encoding::Encoding;
use crate::mac::{self, MACS};
use crate::session::Exchange;
use crate::{cipher, CryptoVec};
pub(crate) trait KexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send>;
}
impl Debug for dyn KexAlgorithm + Send {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "KexAlgorithm")
}
}
pub(crate) trait KexAlgorithm {
fn skip_exchange(&self) -> bool;
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
fn server_dh(&mut self, exchange: &mut Exchange, payload: &[u8]) -> Result<(), crate::Error>;
fn client_dh(
&mut self,
client_ephemeral: &mut CryptoVec,
buf: &mut CryptoVec,
) -> Result<(), crate::Error>;
fn compute_shared_secret(&mut self, remote_pubkey_: &[u8]) -> Result<(), crate::Error>;
fn compute_exchange_hash(
&self,
key: &CryptoVec,
exchange: &Exchange,
buffer: &mut CryptoVec,
) -> Result<CryptoVec, crate::Error>;
fn compute_keys(
&self,
session_id: &CryptoVec,
exchange_hash: &CryptoVec,
cipher: cipher::Name,
remote_to_local_mac: mac::Name,
local_to_remote_mac: mac::Name,
is_server: bool,
) -> Result<super::cipher::CipherPair, crate::Error>;
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub struct Name(&'static str);
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
self.0
}
}
impl TryFrom<&str> for Name {
type Error = ();
fn try_from(s: &str) -> Result<Name, ()> {
KEXES.keys().find(|x| x.0 == s).map(|x| **x).ok_or(())
}
}
/// `curve25519-sha256`
pub const CURVE25519: Name = Name("curve25519-sha256");
/// `curve25519-sha256@libssh.org`
pub const CURVE25519_PRE_RFC_8731: Name = Name("curve25519-sha256@libssh.org");
/// `diffie-hellman-group1-sha1`
pub const DH_G1_SHA1: Name = Name("diffie-hellman-group1-sha1");
/// `diffie-hellman-group14-sha1`
pub const DH_G14_SHA1: Name = Name("diffie-hellman-group14-sha1");
/// `diffie-hellman-group14-sha256`
pub const DH_G14_SHA256: Name = Name("diffie-hellman-group14-sha256");
/// `diffie-hellman-group16-sha512`
pub const DH_G16_SHA512: Name = Name("diffie-hellman-group16-sha512");
/// `ecdh-sha2-nistp256`
pub const ECDH_SHA2_NISTP256: Name = Name("ecdh-sha2-nistp256");
/// `ecdh-sha2-nistp384`
pub const ECDH_SHA2_NISTP384: Name = Name("ecdh-sha2-nistp384");
/// `ecdh-sha2-nistp521`
pub const ECDH_SHA2_NISTP521: Name = Name("ecdh-sha2-nistp521");
/// `none`
pub const NONE: Name = Name("none");
/// `ext-info-c`
pub const EXTENSION_SUPPORT_AS_CLIENT: Name = Name("ext-info-c");
/// `ext-info-s`
pub const EXTENSION_SUPPORT_AS_SERVER: Name = Name("ext-info-s");
/// `kex-strict-c-v00@openssh.com`
pub const EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT: Name = Name("kex-strict-c-v00@openssh.com");
/// `kex-strict-s-v00@openssh.com`
pub const EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER: Name = Name("kex-strict-s-v00@openssh.com");
const _CURVE25519: Curve25519KexType = Curve25519KexType {};
const _DH_G1_SHA1: DhGroup1Sha1KexType = DhGroup1Sha1KexType {};
const _DH_G14_SHA1: DhGroup14Sha1KexType = DhGroup14Sha1KexType {};
const _DH_G14_SHA256: DhGroup14Sha256KexType = DhGroup14Sha256KexType {};
const _DH_G16_SHA512: DhGroup16Sha512KexType = DhGroup16Sha512KexType {};
const _ECDH_SHA2_NISTP256: EcdhNistP256KexType = EcdhNistP256KexType {};
const _ECDH_SHA2_NISTP384: EcdhNistP384KexType = EcdhNistP384KexType {};
const _ECDH_SHA2_NISTP521: EcdhNistP521KexType = EcdhNistP521KexType {};
const _NONE: none::NoneKexType = none::NoneKexType {};
pub const ALL_KEX_ALGORITHMS: &[&Name] = &[
&CURVE25519,
&CURVE25519_PRE_RFC_8731,
&DH_G1_SHA1,
&DH_G14_SHA1,
&DH_G14_SHA256,
&DH_G16_SHA512,
&ECDH_SHA2_NISTP256,
&ECDH_SHA2_NISTP384,
&ECDH_SHA2_NISTP521,
&NONE,
];
pub(crate) static KEXES: Lazy<HashMap<&'static Name, &(dyn KexType + Send + Sync)>> =
Lazy::new(|| {
let mut h: HashMap<&'static Name, &(dyn KexType + Send + Sync)> = HashMap::new();
h.insert(&CURVE25519, &_CURVE25519);
h.insert(&CURVE25519_PRE_RFC_8731, &_CURVE25519);
h.insert(&DH_G16_SHA512, &_DH_G16_SHA512);
h.insert(&DH_G14_SHA256, &_DH_G14_SHA256);
h.insert(&DH_G14_SHA1, &_DH_G14_SHA1);
h.insert(&DH_G1_SHA1, &_DH_G1_SHA1);
h.insert(&ECDH_SHA2_NISTP256, &_ECDH_SHA2_NISTP256);
h.insert(&ECDH_SHA2_NISTP384, &_ECDH_SHA2_NISTP384);
h.insert(&ECDH_SHA2_NISTP521, &_ECDH_SHA2_NISTP521);
h.insert(&NONE, &_NONE);
assert_eq!(ALL_KEX_ALGORITHMS.len(), h.len());
h
});
thread_local! {
static KEY_BUF: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());
static NONCE_BUF: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());
static MAC_BUF: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());
static BUFFER: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());
}
pub(crate) fn compute_keys<D: Digest>(
shared_secret: Option<&[u8]>,
session_id: &CryptoVec,
exchange_hash: &CryptoVec,
cipher: cipher::Name,
remote_to_local_mac: mac::Name,
local_to_remote_mac: mac::Name,
is_server: bool,
) -> Result<super::cipher::CipherPair, crate::Error> {
let cipher = CIPHERS.get(&cipher).ok_or(crate::Error::UnknownAlgo)?;
let remote_to_local_mac = MACS
.get(&remote_to_local_mac)
.ok_or(crate::Error::UnknownAlgo)?;
let local_to_remote_mac = MACS
.get(&local_to_remote_mac)
.ok_or(crate::Error::UnknownAlgo)?;
// https://tools.ietf.org/html/rfc4253#section-7.2
BUFFER.with(|buffer| {
KEY_BUF.with(|key| {
NONCE_BUF.with(|nonce| {
MAC_BUF.with(|mac| {
let compute_key = |c, key: &mut CryptoVec, len| -> Result<(), crate::Error> {
let mut buffer = buffer.borrow_mut();
buffer.clear();
key.clear();
if let Some(shared) = shared_secret {
buffer.extend_ssh_mpint(shared);
}
buffer.extend(exchange_hash.as_ref());
buffer.push(c);
buffer.extend(session_id.as_ref());
let hash = {
let mut hasher = D::new();
hasher.update(&buffer[..]);
hasher.finalize()
};
key.extend(hash.as_ref());
while key.len() < len {
// extend.
buffer.clear();
if let Some(shared) = shared_secret {
buffer.extend_ssh_mpint(shared);
}
buffer.extend(exchange_hash.as_ref());
buffer.extend(key);
let hash = {
let mut hasher = D::new();
hasher.update(&buffer[..]);
hasher.finalize()
};
key.extend(hash.as_ref());
}
key.resize(len);
Ok(())
};
let (local_to_remote, remote_to_local) = if is_server {
(b'D', b'C')
} else {
(b'C', b'D')
};
let (local_to_remote_nonce, remote_to_local_nonce) = if is_server {
(b'B', b'A')
} else {
(b'A', b'B')
};
let (local_to_remote_mac_key, remote_to_local_mac_key) = if is_server {
(b'F', b'E')
} else {
(b'E', b'F')
};
let mut key = key.borrow_mut();
let mut nonce = nonce.borrow_mut();
let mut mac = mac.borrow_mut();
compute_key(local_to_remote, &mut key, cipher.key_len())?;
compute_key(local_to_remote_nonce, &mut nonce, cipher.nonce_len())?;
compute_key(
local_to_remote_mac_key,
&mut mac,
local_to_remote_mac.key_len(),
)?;
let local_to_remote =
cipher.make_sealing_key(&key, &nonce, &mac, *local_to_remote_mac);
compute_key(remote_to_local, &mut key, cipher.key_len())?;
compute_key(remote_to_local_nonce, &mut nonce, cipher.nonce_len())?;
compute_key(
remote_to_local_mac_key,
&mut mac,
remote_to_local_mac.key_len(),
)?;
let remote_to_local =
cipher.make_opening_key(&key, &nonce, &mac, *remote_to_local_mac);
Ok(super::cipher::CipherPair {
local_to_remote,
remote_to_local,
})
})
})
})
})
}

67
vendor/russh/src/kex/none.rs vendored Normal file
View File

@@ -0,0 +1,67 @@
use super::{KexAlgorithm, KexType};
use crate::CryptoVec;
pub struct NoneKexType {}
impl KexType for NoneKexType {
fn make(&self) -> Box<dyn KexAlgorithm + Send> {
Box::new(NoneKexAlgorithm {}) as Box<dyn KexAlgorithm + Send>
}
}
struct NoneKexAlgorithm {}
impl KexAlgorithm for NoneKexAlgorithm {
fn skip_exchange(&self) -> bool {
true
}
fn server_dh(
&mut self,
_exchange: &mut crate::session::Exchange,
_payload: &[u8],
) -> Result<(), crate::Error> {
Ok(())
}
fn client_dh(
&mut self,
_client_ephemeral: &mut russh_cryptovec::CryptoVec,
_buf: &mut russh_cryptovec::CryptoVec,
) -> Result<(), crate::Error> {
Ok(())
}
fn compute_shared_secret(&mut self, _remote_pubkey: &[u8]) -> Result<(), crate::Error> {
Ok(())
}
fn compute_exchange_hash(
&self,
_key: &russh_cryptovec::CryptoVec,
_exchange: &crate::session::Exchange,
_buffer: &mut russh_cryptovec::CryptoVec,
) -> Result<russh_cryptovec::CryptoVec, crate::Error> {
Ok(CryptoVec::new())
}
fn compute_keys(
&self,
session_id: &russh_cryptovec::CryptoVec,
exchange_hash: &russh_cryptovec::CryptoVec,
cipher: crate::cipher::Name,
remote_to_local_mac: crate::mac::Name,
local_to_remote_mac: crate::mac::Name,
is_server: bool,
) -> Result<crate::cipher::CipherPair, crate::Error> {
super::compute_keys::<sha2::Sha256>(
None,
session_id,
exchange_hash,
cipher,
remote_to_local_mac,
local_to_remote_mac,
is_server,
)
}
}

77
vendor/russh/src/key.rs vendored Normal file
View File

@@ -0,0 +1,77 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use crate::keys::encoding::*;
use crate::keys::key::*;
use crate::keys::{ec, protocol};
use crate::CryptoVec;
#[doc(hidden)]
pub trait PubKey {
fn push_to(&self, buffer: &mut CryptoVec);
}
impl PubKey for PublicKey {
fn push_to(&self, buffer: &mut CryptoVec) {
match self {
PublicKey::Ed25519(ref public) => {
buffer.push_u32_be((ED25519.0.len() + public.as_bytes().len() + 8) as u32);
buffer.extend_ssh_string(ED25519.0.as_bytes());
buffer.extend_ssh_string(public.as_bytes());
}
PublicKey::RSA { ref key, .. } => {
buffer.extend_wrapped(|buffer| {
buffer.extend_ssh_string(SSH_RSA.0.as_bytes());
buffer.extend_ssh(&protocol::RsaPublicKey::from(key));
});
}
PublicKey::EC { ref key } => {
write_ec_public_key(buffer, key);
}
}
}
}
impl PubKey for KeyPair {
fn push_to(&self, buffer: &mut CryptoVec) {
match self {
KeyPair::Ed25519(ref key) => {
let public = key.verifying_key().to_bytes();
buffer.push_u32_be((ED25519.0.len() + public.len() + 8) as u32);
buffer.extend_ssh_string(ED25519.0.as_bytes());
buffer.extend_ssh_string(public.as_slice());
}
KeyPair::RSA { ref key, .. } => {
buffer.extend_wrapped(|buffer| {
buffer.extend_ssh_string(SSH_RSA.0.as_bytes());
buffer.extend_ssh(&protocol::RsaPublicKey::from(key));
});
}
KeyPair::EC { ref key } => {
write_ec_public_key(buffer, &key.to_public_key());
}
}
}
}
pub(crate) fn write_ec_public_key(buf: &mut CryptoVec, key: &ec::PublicKey) {
let algorithm = key.algorithm().as_bytes();
let ident = key.ident().as_bytes();
let q = key.to_sec1_bytes();
buf.push_u32_be((algorithm.len() + ident.len() + q.len() + 12) as u32);
buf.extend_ssh_string(algorithm);
buf.extend_ssh_string(ident);
buf.extend_ssh_string(&q);
}

546
vendor/russh/src/lib.rs vendored Normal file
View File

@@ -0,0 +1,546 @@
#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic
)]
#![allow(clippy::single_match, clippy::upper_case_acronyms)]
// length checked
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Server and client SSH asynchronous library, based on tokio/futures.
//!
//! The normal way to use this library, both for clients and for
//! servers, is by creating *handlers*, i.e. types that implement
//! `client::Handler` for clients and `server::Handler` for
//! servers.
//!
//! * [Writing SSH clients - the `russh::client` module](client)
//! * [Writing SSH servers - the `russh::server` module](server)
//!
//! # Using non-socket IO / writing tunnels
//!
//! The easy way to implement SSH tunnels, like `ProxyCommand` for
//! OpenSSH, is to use the `russh-config` crate, and use the
//! `Stream::tcp_connect` or `Stream::proxy_command` methods of that
//! crate. That crate is a very lightweight layer above Russh, only
//! implementing for external commands the traits used for sockets.
//!
//! # The SSH protocol
//!
//! If we exclude the key exchange and authentication phases, handled
//! by Russh behind the scenes, the rest of the SSH protocol is
//! relatively simple: clients and servers open *channels*, which are
//! just integers used to handle multiple requests in parallel in a
//! single connection. Once a client has obtained a `ChannelId` by
//! calling one of the many `channel_open_…` methods of
//! `client::Connection`, the client may send exec requests and data
//! to the server.
//!
//! A simple client just asking the server to run one command will
//! usually start by calling
//! `client::Connection::channel_open_session`, then
//! `client::Connection::exec`, then possibly
//! `client::Connection::data` a number of times to send data to the
//! command's standard input, and finally `Connection::channel_eof`
//! and `Connection::channel_close`.
//!
//! # Design principles
//!
//! The main goal of this library is conciseness, and reduced size and
//! readability of the library's code. Moreover, this library is split
//! between Russh, which implements the main logic of SSH clients
//! and servers, and Russh-keys, which implements calls to
//! cryptographic primitives.
//!
//! One non-goal is to implement all possible cryptographic algorithms
//! published since the initial release of SSH. Technical debt is
//! easily acquired, and we would need a very strong reason to go
//! against this principle. If you are designing a system from
//! scratch, we urge you to consider recent cryptographic primitives
//! such as Ed25519 for public key cryptography, and Chacha20-Poly1305
//! for symmetric cryptography and MAC.
//!
//! # Internal details of the event loop
//!
//! It might seem a little odd that the read/write methods for server
//! or client sessions often return neither `Result` nor
//! `Future`. This is because the data sent to the remote side is
//! buffered, because it needs to be encrypted first, and encryption
//! works on buffers, and for many algorithms, not in place.
//!
//! Hence, the event loop keeps waiting for incoming packets, reacts
//! to them by calling the provided `Handler`, which fills some
//! buffers. If the buffers are non-empty, the event loop then sends
//! them to the socket, flushes the socket, empties the buffers and
//! starts again. In the special case of the server, unsollicited
//! messages sent through a `server::Handle` are processed when there
//! is no incoming packet to read.
use std::convert::TryFrom;
use std::fmt::{Debug, Display, Formatter};
use log::debug;
use parsing::ChannelOpenConfirmation;
pub use russh_cryptovec::CryptoVec;
use thiserror::Error;
#[cfg(test)]
mod tests;
mod auth;
/// Cipher names
pub mod cipher;
/// Compression algorithm names
pub mod compression;
/// Key exchange algorithm names
pub mod kex;
/// MAC algorithm names
pub mod mac;
/// Re-export of the `russh-keys` crate.
pub use russh_keys as keys;
mod cert;
mod key;
mod msg;
mod negotiation;
mod ssh_read;
mod sshbuffer;
pub use negotiation::Preferred;
mod pty;
pub use pty::Pty;
pub use sshbuffer::SshId;
macro_rules! push_packet {
( $buffer:expr, $x:expr ) => {{
use byteorder::{BigEndian, ByteOrder};
let i0 = $buffer.len();
$buffer.extend(b"\0\0\0\0");
let x = $x;
let i1 = $buffer.len();
use std::ops::DerefMut;
let buf = $buffer.deref_mut();
#[allow(clippy::indexing_slicing)] // length checked
BigEndian::write_u32(&mut buf[i0..], (i1 - i0 - 4) as u32);
x
}};
}
mod channels;
pub use channels::{Channel, ChannelMsg, ChannelStream};
mod parsing;
mod session;
/// Server side of this library.
#[cfg(not(target_arch = "wasm32"))]
pub mod server;
/// Client side of this library.
pub mod client;
#[derive(Debug)]
pub enum AlgorithmKind {
Kex,
Key,
Cipher,
Compression,
Mac,
}
#[derive(Debug, Error)]
pub enum Error {
/// The key file could not be parsed.
#[error("Could not read key")]
CouldNotReadKey,
/// Unspecified problem with the beginning of key exchange.
#[error("Key exchange init failed")]
KexInit,
/// Unknown algorithm name.
#[error("Unknown algorithm")]
UnknownAlgo,
/// No common algorithm found during key exchange.
#[error("No common algorithm")]
NoCommonAlgo {
kind: AlgorithmKind,
ours: Vec<String>,
theirs: Vec<String>,
},
/// Invalid SSH version string.
#[error("invalid SSH version string")]
Version,
/// Error during key exchange.
#[error("Key exchange failed")]
Kex,
/// Invalid packet authentication code.
#[error("Wrong packet authentication code")]
PacketAuth,
/// The protocol is in an inconsistent state.
#[error("Inconsistent state of the protocol")]
Inconsistent,
/// The client is not yet authenticated.
#[error("Not yet authenticated")]
NotAuthenticated,
/// Index out of bounds.
#[error("Index out of bounds")]
IndexOutOfBounds,
/// Unknown server key.
#[error("Unknown server key")]
UnknownKey,
/// The server provided a wrong signature.
#[error("Wrong server signature")]
WrongServerSig,
/// Excessive packet size.
#[error("Bad packet size: {0}")]
PacketSize(usize),
/// Message received/sent on unopened channel.
#[error("Channel not open")]
WrongChannel,
/// Server refused to open a channel.
#[error("Failed to open channel ({0:?})")]
ChannelOpenFailure(ChannelOpenFailure),
/// Disconnected
#[error("Disconnected")]
Disconnect,
/// No home directory found when trying to learn new host key.
#[error("No home directory when saving host key")]
NoHomeDir,
/// Remote key changed, this could mean a man-in-the-middle attack
/// is being performed on the connection.
#[error("Key changed, line {}", line)]
KeyChanged { line: usize },
/// Connection closed by the remote side.
#[error("Connection closed by the remote side")]
HUP,
/// Connection timeout.
#[error("Connection timeout")]
ConnectionTimeout,
/// Keepalive timeout.
#[error("Keepalive timeout")]
KeepaliveTimeout,
/// Inactivity timeout.
#[error("Inactivity timeout")]
InactivityTimeout,
/// Missing authentication method.
#[error("No authentication method")]
NoAuthMethod,
#[error("Channel send error")]
SendError,
#[error("Pending buffer limit reached")]
Pending,
#[error("Failed to decrypt a packet")]
DecryptionError,
#[error("The request was rejected by the other party")]
RequestDenied,
#[error(transparent)]
Keys(#[from] russh_keys::Error),
#[error(transparent)]
IO(#[from] std::io::Error),
#[error(transparent)]
Utf8(#[from] std::str::Utf8Error),
#[error(transparent)]
#[cfg(feature = "flate2")]
Compress(#[from] flate2::CompressError),
#[error(transparent)]
#[cfg(feature = "flate2")]
Decompress(#[from] flate2::DecompressError),
#[error(transparent)]
Join(#[from] russh_util::runtime::JoinError),
#[error(transparent)]
#[cfg(feature = "openssl")]
Openssl(#[from] openssl::error::ErrorStack),
#[error(transparent)]
Elapsed(#[from] tokio::time::error::Elapsed),
#[error("Violation detected during strict key exchange, message {message_type} at seq no {sequence_number}")]
StrictKeyExchangeViolation {
message_type: u8,
sequence_number: usize,
},
}
pub(crate) fn strict_kex_violation(message_type: u8, sequence_number: usize) -> crate::Error {
debug!(
"strict kex violated at sequence no. {:?}, message type: {:?}",
sequence_number, message_type
);
crate::Error::StrictKeyExchangeViolation {
message_type,
sequence_number,
}
}
#[derive(Debug, Error)]
#[error("Could not reach the event loop")]
pub struct SendError {}
/// The number of bytes read/written, and the number of seconds before a key
/// re-exchange is requested.
#[derive(Debug, Clone)]
pub struct Limits {
pub rekey_write_limit: usize,
pub rekey_read_limit: usize,
pub rekey_time_limit: std::time::Duration,
}
impl Limits {
/// Create a new `Limits`, checking that the given bounds cannot lead to
/// nonce reuse.
pub fn new(write_limit: usize, read_limit: usize, time_limit: std::time::Duration) -> Limits {
assert!(write_limit <= 1 << 30 && read_limit <= 1 << 30);
Limits {
rekey_write_limit: write_limit,
rekey_read_limit: read_limit,
rekey_time_limit: time_limit,
}
}
}
impl Default for Limits {
fn default() -> Self {
// Following the recommendations of
// https://tools.ietf.org/html/rfc4253#section-9
Limits {
rekey_write_limit: 1 << 30, // 1 Gb
rekey_read_limit: 1 << 30, // 1 Gb
rekey_time_limit: std::time::Duration::from_secs(3600),
}
}
}
pub use auth::{AgentAuthError, MethodSet, Signer};
/// A reason for disconnection.
#[allow(missing_docs)] // This should be relatively self-explanatory.
#[allow(clippy::manual_non_exhaustive)]
#[derive(Debug)]
pub enum Disconnect {
HostNotAllowedToConnect = 1,
ProtocolError = 2,
KeyExchangeFailed = 3,
#[doc(hidden)]
Reserved = 4,
MACError = 5,
CompressionError = 6,
ServiceNotAvailable = 7,
ProtocolVersionNotSupported = 8,
HostKeyNotVerifiable = 9,
ConnectionLost = 10,
ByApplication = 11,
TooManyConnections = 12,
AuthCancelledByUser = 13,
NoMoreAuthMethodsAvailable = 14,
IllegalUserName = 15,
}
impl TryFrom<u32> for Disconnect {
type Error = crate::Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
Ok(match value {
1 => Self::HostNotAllowedToConnect,
2 => Self::ProtocolError,
3 => Self::KeyExchangeFailed,
4 => Self::Reserved,
5 => Self::MACError,
6 => Self::CompressionError,
7 => Self::ServiceNotAvailable,
8 => Self::ProtocolVersionNotSupported,
9 => Self::HostKeyNotVerifiable,
10 => Self::ConnectionLost,
11 => Self::ByApplication,
12 => Self::TooManyConnections,
13 => Self::AuthCancelledByUser,
14 => Self::NoMoreAuthMethodsAvailable,
15 => Self::IllegalUserName,
_ => return Err(crate::Error::Inconsistent),
})
}
}
/// The type of signals that can be sent to a remote process. If you
/// plan to use custom signals, read [the
/// RFC](https://tools.ietf.org/html/rfc4254#section-6.10) to
/// understand the encoding.
#[allow(missing_docs)]
// This should be relatively self-explanatory.
#[derive(Debug, Clone)]
pub enum Sig {
ABRT,
ALRM,
FPE,
HUP,
ILL,
INT,
KILL,
PIPE,
QUIT,
SEGV,
TERM,
USR1,
Custom(String),
}
impl Sig {
fn name(&self) -> &str {
match *self {
Sig::ABRT => "ABRT",
Sig::ALRM => "ALRM",
Sig::FPE => "FPE",
Sig::HUP => "HUP",
Sig::ILL => "ILL",
Sig::INT => "INT",
Sig::KILL => "KILL",
Sig::PIPE => "PIPE",
Sig::QUIT => "QUIT",
Sig::SEGV => "SEGV",
Sig::TERM => "TERM",
Sig::USR1 => "USR1",
Sig::Custom(ref c) => c,
}
}
fn from_name(name: &[u8]) -> Result<Sig, Error> {
match name {
b"ABRT" => Ok(Sig::ABRT),
b"ALRM" => Ok(Sig::ALRM),
b"FPE" => Ok(Sig::FPE),
b"HUP" => Ok(Sig::HUP),
b"ILL" => Ok(Sig::ILL),
b"INT" => Ok(Sig::INT),
b"KILL" => Ok(Sig::KILL),
b"PIPE" => Ok(Sig::PIPE),
b"QUIT" => Ok(Sig::QUIT),
b"SEGV" => Ok(Sig::SEGV),
b"TERM" => Ok(Sig::TERM),
b"USR1" => Ok(Sig::USR1),
x => Ok(Sig::Custom(std::str::from_utf8(x)?.to_string())),
}
}
}
/// Reason for not being able to open a channel.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[allow(missing_docs)]
pub enum ChannelOpenFailure {
AdministrativelyProhibited = 1,
ConnectFailed = 2,
UnknownChannelType = 3,
ResourceShortage = 4,
Unknown = 0,
}
impl ChannelOpenFailure {
fn from_u32(x: u32) -> Option<ChannelOpenFailure> {
match x {
1 => Some(ChannelOpenFailure::AdministrativelyProhibited),
2 => Some(ChannelOpenFailure::ConnectFailed),
3 => Some(ChannelOpenFailure::UnknownChannelType),
4 => Some(ChannelOpenFailure::ResourceShortage),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
/// The identifier of a channel.
pub struct ChannelId(u32);
impl From<ChannelId> for u32 {
fn from(c: ChannelId) -> u32 {
c.0
}
}
impl Display for ChannelId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
/// The parameters of a channel.
#[derive(Debug)]
pub(crate) struct ChannelParams {
recipient_channel: u32,
sender_channel: ChannelId,
recipient_window_size: u32,
sender_window_size: u32,
recipient_maximum_packet_size: u32,
sender_maximum_packet_size: u32,
/// Has the other side confirmed the channel?
pub confirmed: bool,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
wants_reply: bool,
pending_data: std::collections::VecDeque<(CryptoVec, Option<u32>, usize)>,
pending_eof: bool,
pending_close: bool,
}
impl ChannelParams {
pub fn confirm(&mut self, c: &ChannelOpenConfirmation) {
self.recipient_channel = c.sender_channel; // "sender" is the sender of the confirmation
self.recipient_window_size = c.initial_window_size;
self.recipient_maximum_packet_size = c.maximum_packet_size;
self.confirmed = true;
}
}
pub(crate) fn future_or_pending<F: futures::Future, T>(
val: Option<T>,
f: impl FnOnce(T) -> F,
) -> futures::future::Either<futures::future::Pending<<F as futures::Future>::Output>, F> {
val.map_or(
futures::future::Either::Left(futures::future::pending()),
|x| futures::future::Either::Right(f(x)),
)
}

60
vendor/russh/src/mac/crypto.rs vendored Normal file
View File

@@ -0,0 +1,60 @@
use std::marker::PhantomData;
use byteorder::{BigEndian, ByteOrder};
use digest::typenum::Unsigned;
use digest::KeyInit;
use generic_array::{ArrayLength, GenericArray};
use subtle::ConstantTimeEq;
use super::{Mac, MacAlgorithm};
pub struct CryptoMacAlgorithm<
M: digest::Mac + KeyInit + Send + 'static,
KL: ArrayLength<u8> + 'static,
>(pub PhantomData<M>, pub PhantomData<KL>);
pub struct CryptoMac<M: digest::Mac + KeyInit + Send + 'static, KL: ArrayLength<u8> + 'static> {
pub(crate) key: GenericArray<u8, KL>,
pub(crate) p: PhantomData<M>,
}
impl<M: digest::Mac + KeyInit + Send + 'static, KL: ArrayLength<u8> + 'static> MacAlgorithm
for CryptoMacAlgorithm<M, KL>
{
fn key_len(&self) -> usize {
KL::to_usize()
}
fn make_mac(&self, mac_key: &[u8]) -> Box<dyn Mac + Send> {
let mut key = GenericArray::<u8, KL>::default();
key.clone_from_slice(mac_key);
Box::new(CryptoMac::<M, KL> {
key,
p: PhantomData,
}) as Box<dyn Mac + Send>
}
}
impl<M: digest::Mac + KeyInit + Send + 'static, KL: ArrayLength<u8> + 'static> Mac
for CryptoMac<M, KL>
{
fn mac_len(&self) -> usize {
M::OutputSize::to_usize()
}
fn compute(&self, sequence_number: u32, payload: &[u8], output: &mut [u8]) {
#[allow(clippy::unwrap_used)]
let mut hmac = <M as digest::Mac>::new_from_slice(&self.key).unwrap();
let mut seqno_buf = [0; 4];
BigEndian::write_u32(&mut seqno_buf, sequence_number);
hmac.update(&seqno_buf);
hmac.update(payload);
output.clone_from_slice(&hmac.finalize().into_bytes());
}
fn verify(&self, sequence_number: u32, payload: &[u8], mac: &[u8]) -> bool {
let mut buf = GenericArray::<u8, M::OutputSize>::default();
self.compute(sequence_number, payload, &mut buf);
buf.ct_eq(mac).into()
}
}

53
vendor/russh/src/mac/crypto_etm.rs vendored Normal file
View File

@@ -0,0 +1,53 @@
use std::marker::PhantomData;
use digest::KeyInit;
use generic_array::{ArrayLength, GenericArray};
use super::crypto::{CryptoMac, CryptoMacAlgorithm};
use super::{Mac, MacAlgorithm};
pub struct CryptoEtmMacAlgorithm<
M: digest::Mac + KeyInit + Send + 'static,
KL: ArrayLength<u8> + 'static,
>(pub PhantomData<M>, pub PhantomData<KL>);
impl<M: digest::Mac + KeyInit + Send + 'static, KL: ArrayLength<u8> + 'static> MacAlgorithm
for CryptoEtmMacAlgorithm<M, KL>
{
fn key_len(&self) -> usize {
CryptoMacAlgorithm::<M, KL>(self.0, self.1).key_len()
}
fn make_mac(&self, mac_key: &[u8]) -> Box<dyn Mac + Send> {
let mut key = GenericArray::<u8, KL>::default();
key.clone_from_slice(mac_key);
Box::new(CryptoEtmMac::<M, KL>(CryptoMac::<M, KL> {
key,
p: PhantomData,
})) as Box<dyn Mac + Send>
}
}
pub struct CryptoEtmMac<M: digest::Mac + KeyInit + Send + 'static, KL: ArrayLength<u8> + 'static>(
CryptoMac<M, KL>,
);
impl<M: digest::Mac + KeyInit + Send + 'static, KL: ArrayLength<u8> + 'static> Mac
for CryptoEtmMac<M, KL>
{
fn is_etm(&self) -> bool {
true
}
fn mac_len(&self) -> usize {
self.0.mac_len()
}
fn compute(&self, sequence_number: u32, payload: &[u8], output: &mut [u8]) {
self.0.compute(sequence_number, payload, output)
}
fn verify(&self, sequence_number: u32, payload: &[u8], mac: &[u8]) -> bool {
self.0.verify(sequence_number, payload, mac)
}
}

114
vendor/russh/src/mac/mod.rs vendored Normal file
View File

@@ -0,0 +1,114 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//!
//! This module exports cipher names for use with [Preferred].
use std::collections::HashMap;
use std::convert::TryFrom;
use std::marker::PhantomData;
use digest::typenum::{U20, U32, U64};
use hmac::Hmac;
use once_cell::sync::Lazy;
use sha1::Sha1;
use sha2::{Sha256, Sha512};
use self::crypto::CryptoMacAlgorithm;
use self::crypto_etm::CryptoEtmMacAlgorithm;
use self::none::NoMacAlgorithm;
mod crypto;
mod crypto_etm;
mod none;
pub(crate) trait MacAlgorithm {
fn key_len(&self) -> usize;
fn make_mac(&self, key: &[u8]) -> Box<dyn Mac + Send>;
}
pub(crate) trait Mac {
fn mac_len(&self) -> usize;
fn is_etm(&self) -> bool {
false
}
fn compute(&self, sequence_number: u32, payload: &[u8], output: &mut [u8]);
fn verify(&self, sequence_number: u32, payload: &[u8], mac: &[u8]) -> bool;
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub struct Name(&'static str);
impl AsRef<str> for Name {
fn as_ref(&self) -> &str {
self.0
}
}
impl TryFrom<&str> for Name {
type Error = ();
fn try_from(s: &str) -> Result<Name, ()> {
MACS.keys().find(|x| x.0 == s).map(|x| **x).ok_or(())
}
}
/// `none`
pub const NONE: Name = Name("none");
/// `hmac-sha1`
pub const HMAC_SHA1: Name = Name("hmac-sha1");
/// `hmac-sha2-256`
pub const HMAC_SHA256: Name = Name("hmac-sha2-256");
/// `hmac-sha2-512`
pub const HMAC_SHA512: Name = Name("hmac-sha2-512");
/// `hmac-sha1-etm@openssh.com`
pub const HMAC_SHA1_ETM: Name = Name("hmac-sha1-etm@openssh.com");
/// `hmac-sha2-256-etm@openssh.com`
pub const HMAC_SHA256_ETM: Name = Name("hmac-sha2-256-etm@openssh.com");
/// `hmac-sha2-512-etm@openssh.com`
pub const HMAC_SHA512_ETM: Name = Name("hmac-sha2-512-etm@openssh.com");
static _NONE: NoMacAlgorithm = NoMacAlgorithm {};
static _HMAC_SHA1: CryptoMacAlgorithm<Hmac<Sha1>, U20> =
CryptoMacAlgorithm(PhantomData, PhantomData);
static _HMAC_SHA256: CryptoMacAlgorithm<Hmac<Sha256>, U32> =
CryptoMacAlgorithm(PhantomData, PhantomData);
static _HMAC_SHA512: CryptoMacAlgorithm<Hmac<Sha512>, U64> =
CryptoMacAlgorithm(PhantomData, PhantomData);
static _HMAC_SHA1_ETM: CryptoEtmMacAlgorithm<Hmac<Sha1>, U20> =
CryptoEtmMacAlgorithm(PhantomData, PhantomData);
static _HMAC_SHA256_ETM: CryptoEtmMacAlgorithm<Hmac<Sha256>, U32> =
CryptoEtmMacAlgorithm(PhantomData, PhantomData);
static _HMAC_SHA512_ETM: CryptoEtmMacAlgorithm<Hmac<Sha512>, U64> =
CryptoEtmMacAlgorithm(PhantomData, PhantomData);
pub const ALL_MAC_ALGORITHMS: &[&Name] = &[
&NONE,
&HMAC_SHA1,
&HMAC_SHA256,
&HMAC_SHA512,
&HMAC_SHA1_ETM,
&HMAC_SHA256_ETM,
&HMAC_SHA512_ETM,
];
pub(crate) static MACS: Lazy<HashMap<&'static Name, &(dyn MacAlgorithm + Send + Sync)>> =
Lazy::new(|| {
let mut h: HashMap<&'static Name, &(dyn MacAlgorithm + Send + Sync)> = HashMap::new();
h.insert(&NONE, &_NONE);
h.insert(&HMAC_SHA1, &_HMAC_SHA1);
h.insert(&HMAC_SHA256, &_HMAC_SHA256);
h.insert(&HMAC_SHA512, &_HMAC_SHA512);
h.insert(&HMAC_SHA1_ETM, &_HMAC_SHA1_ETM);
h.insert(&HMAC_SHA256_ETM, &_HMAC_SHA256_ETM);
h.insert(&HMAC_SHA512_ETM, &_HMAC_SHA512_ETM);
assert_eq!(h.len(), ALL_MAC_ALGORITHMS.len());
h
});

26
vendor/russh/src/mac/none.rs vendored Normal file
View File

@@ -0,0 +1,26 @@
use super::{Mac, MacAlgorithm};
pub struct NoMacAlgorithm {}
pub struct NoMac {}
impl MacAlgorithm for NoMacAlgorithm {
fn key_len(&self) -> usize {
0
}
fn make_mac(&self, _: &[u8]) -> Box<dyn Mac + Send> {
Box::new(NoMac {})
}
}
impl Mac for NoMac {
fn mac_len(&self) -> usize {
0
}
fn compute(&self, _: u32, _: &[u8], _: &mut [u8]) {}
fn verify(&self, _: u32, _: &[u8], _: &[u8]) -> bool {
true
}
}

78
vendor/russh/src/msg.rs vendored Normal file
View File

@@ -0,0 +1,78 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// https://tools.ietf.org/html/rfc4253#section-12
#[cfg(not(target_arch = "wasm32"))]
pub use server::*;
pub const DISCONNECT: u8 = 1;
#[allow(dead_code)]
pub const IGNORE: u8 = 2;
#[allow(dead_code)]
pub const UNIMPLEMENTED: u8 = 3;
#[allow(dead_code)]
pub const DEBUG: u8 = 4;
pub const SERVICE_ACCEPT: u8 = 6;
pub const EXT_INFO: u8 = 7;
pub const KEXINIT: u8 = 20;
pub const NEWKEYS: u8 = 21;
// http://tools.ietf.org/html/rfc5656#section-7.1
pub const KEX_ECDH_INIT: u8 = 30;
pub const KEX_ECDH_REPLY: u8 = 31;
// https://tools.ietf.org/html/rfc4250#section-4.1.2
pub const USERAUTH_REQUEST: u8 = 50;
pub const USERAUTH_FAILURE: u8 = 51;
pub const USERAUTH_SUCCESS: u8 = 52;
pub const USERAUTH_BANNER: u8 = 53;
pub const USERAUTH_INFO_RESPONSE: u8 = 61;
// some numbers have same meaning
pub const USERAUTH_INFO_REQUEST_OR_USERAUTH_PK_OK: u8 = 60;
// https://tools.ietf.org/html/rfc4254#section-9
pub const GLOBAL_REQUEST: u8 = 80;
pub const REQUEST_SUCCESS: u8 = 81;
pub const REQUEST_FAILURE: u8 = 82;
pub const CHANNEL_OPEN: u8 = 90;
pub const CHANNEL_OPEN_CONFIRMATION: u8 = 91;
pub const CHANNEL_OPEN_FAILURE: u8 = 92;
pub const CHANNEL_WINDOW_ADJUST: u8 = 93;
pub const CHANNEL_DATA: u8 = 94;
pub const CHANNEL_EXTENDED_DATA: u8 = 95;
pub const CHANNEL_EOF: u8 = 96;
pub const CHANNEL_CLOSE: u8 = 97;
pub const CHANNEL_REQUEST: u8 = 98;
pub const CHANNEL_SUCCESS: u8 = 99;
pub const CHANNEL_FAILURE: u8 = 100;
#[allow(dead_code)]
pub const SSH_OPEN_CONNECT_FAILED: u8 = 2;
pub const SSH_OPEN_UNKNOWN_CHANNEL_TYPE: u8 = 3;
#[allow(dead_code)]
pub const SSH_OPEN_RESOURCE_SHORTAGE: u8 = 4;
#[cfg(not(target_arch = "wasm32"))]
mod server {
// https://tools.ietf.org/html/rfc4256#section-5
pub const USERAUTH_INFO_REQUEST: u8 = 60;
pub const USERAUTH_PK_OK: u8 = 60;
pub const SERVICE_REQUEST: u8 = 5;
pub const SSH_OPEN_ADMINISTRATIVELY_PROHIBITED: u8 = 1;
}

441
vendor/russh/src/negotiation.rs vendored Normal file
View File

@@ -0,0 +1,441 @@
use std::borrow::Cow;
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use std::str::from_utf8;
use log::debug;
use rand::RngCore;
use crate::cipher::CIPHERS;
use crate::kex::{EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT, EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER};
use crate::keys::encoding::{Encoding, Reader};
use crate::keys::key;
use crate::keys::key::{KeyPair, PublicKey};
#[cfg(not(target_arch = "wasm32"))]
use crate::server::Config;
use crate::{cipher, compression, kex, mac, msg, AlgorithmKind, CryptoVec, Error};
#[cfg(target_arch = "wasm32")]
/// WASM-only stub
pub struct Config {
keys: Vec<KeyPair>,
}
#[derive(Debug, Clone)]
pub struct Names {
pub kex: kex::Name,
pub key: key::Name,
pub cipher: cipher::Name,
pub client_mac: mac::Name,
pub server_mac: mac::Name,
pub server_compression: compression::Compression,
pub client_compression: compression::Compression,
pub ignore_guessed: bool,
pub strict_kex: bool,
}
/// Lists of preferred algorithms. This is normally hard-coded into implementations.
#[derive(Debug, Clone)]
pub struct Preferred {
/// Preferred key exchange algorithms.
pub kex: Cow<'static, [kex::Name]>,
/// Preferred host & public key algorithms.
pub key: Cow<'static, [key::Name]>,
/// Preferred symmetric ciphers.
pub cipher: Cow<'static, [cipher::Name]>,
/// Preferred MAC algorithms.
pub mac: Cow<'static, [mac::Name]>,
/// Preferred compression algorithms.
pub compression: Cow<'static, [compression::Name]>,
}
impl Preferred {
pub(crate) fn possible_host_key_algos_for_keys(
&self,
available_host_keys: &[KeyPair],
) -> Vec<key::Name> {
self.key
.iter()
.filter(|n| available_host_keys.iter().any(|k| k.name() == n.0))
.copied()
.collect::<Vec<_>>()
}
}
const SAFE_KEX_ORDER: &[kex::Name] = &[
kex::CURVE25519,
kex::CURVE25519_PRE_RFC_8731,
kex::DH_G16_SHA512,
kex::DH_G14_SHA256,
kex::EXTENSION_SUPPORT_AS_CLIENT,
kex::EXTENSION_SUPPORT_AS_SERVER,
kex::EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT,
kex::EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER,
];
const CIPHER_ORDER: &[cipher::Name] = &[
cipher::CHACHA20_POLY1305,
cipher::AES_256_GCM,
cipher::AES_256_CTR,
cipher::AES_192_CTR,
cipher::AES_128_CTR,
];
const HMAC_ORDER: &[mac::Name] = &[
mac::HMAC_SHA512_ETM,
mac::HMAC_SHA256_ETM,
mac::HMAC_SHA512,
mac::HMAC_SHA256,
mac::HMAC_SHA1_ETM,
mac::HMAC_SHA1,
];
const COMPRESSION_ORDER: &[compression::Name] = &[
compression::NONE,
#[cfg(feature = "flate2")]
compression::ZLIB,
#[cfg(feature = "flate2")]
compression::ZLIB_LEGACY,
];
impl Preferred {
pub const DEFAULT: Preferred = Preferred {
kex: Cow::Borrowed(SAFE_KEX_ORDER),
key: Cow::Borrowed(&[
key::ED25519,
key::ECDSA_SHA2_NISTP256,
key::ECDSA_SHA2_NISTP521,
key::RSA_SHA2_256,
key::RSA_SHA2_512,
]),
cipher: Cow::Borrowed(CIPHER_ORDER),
mac: Cow::Borrowed(HMAC_ORDER),
compression: Cow::Borrowed(COMPRESSION_ORDER),
};
pub const COMPRESSED: Preferred = Preferred {
kex: Cow::Borrowed(SAFE_KEX_ORDER),
key: Preferred::DEFAULT.key,
cipher: Cow::Borrowed(CIPHER_ORDER),
mac: Cow::Borrowed(HMAC_ORDER),
compression: Cow::Borrowed(COMPRESSION_ORDER),
};
}
impl Default for Preferred {
fn default() -> Preferred {
Preferred::DEFAULT
}
}
/// Named algorithms.
pub trait Named {
/// The name of this algorithm.
fn name(&self) -> &'static str;
}
impl Named for () {
fn name(&self) -> &'static str {
""
}
}
use crate::keys::key::ED25519;
impl Named for PublicKey {
fn name(&self) -> &'static str {
match self {
PublicKey::Ed25519(_) => ED25519.0,
PublicKey::RSA { ref hash, .. } => hash.name().0,
PublicKey::EC { ref key } => key.algorithm(),
}
}
}
impl Named for KeyPair {
fn name(&self) -> &'static str {
match self {
KeyPair::Ed25519 { .. } => ED25519.0,
KeyPair::RSA { ref hash, .. } => hash.name().0,
KeyPair::EC { ref key } => key.algorithm(),
}
}
}
pub(crate) fn parse_kex_algo_list(list: &[u8]) -> Vec<&str> {
list.split(|&x| x == b',')
.map(|x| from_utf8(x).unwrap_or_default())
.collect()
}
pub(crate) trait Select {
fn is_server() -> bool;
fn select<S: AsRef<str> + Clone>(
a: &[S],
b: &[&str],
kind: AlgorithmKind,
) -> Result<(bool, S), Error>;
/// `available_host_keys`, if present, is used to limit the host key algorithms to the ones we have keys for.
fn read_kex(
buffer: &[u8],
pref: &Preferred,
available_host_keys: Option<&[KeyPair]>,
) -> Result<Names, Error> {
let mut r = buffer.reader(17);
// Key exchange
let kex_string = r.read_string()?;
let (kex_both_first, kex_algorithm) = Self::select(
&pref.kex,
&parse_kex_algo_list(kex_string),
AlgorithmKind::Kex,
)?;
// Strict kex detection
let strict_kex_requested = pref.kex.contains(if Self::is_server() {
&EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER
} else {
&EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT
});
let strict_kex_provided = Self::select(
&[if Self::is_server() {
EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT
} else {
EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER
}],
&parse_kex_algo_list(kex_string),
AlgorithmKind::Kex,
)
.is_ok();
if strict_kex_requested && strict_kex_provided {
debug!("strict kex enabled")
}
// Host key
let key_string: &[u8] = r.read_string()?;
let possible_host_key_algos = match available_host_keys {
Some(available_host_keys) => pref.possible_host_key_algos_for_keys(available_host_keys),
None => pref.key.iter().map(ToOwned::to_owned).collect::<Vec<_>>(),
};
let (key_both_first, key_algorithm) = Self::select(
&possible_host_key_algos[..],
&parse_kex_algo_list(key_string),
AlgorithmKind::Key,
)?;
// Cipher
let cipher_string = r.read_string()?;
let (_cipher_both_first, cipher) = Self::select(
&pref.cipher,
&parse_kex_algo_list(cipher_string),
AlgorithmKind::Cipher,
)?;
r.read_string()?; // cipher server-to-client.
debug!("kex {}", line!());
// MAC
let need_mac = CIPHERS.get(&cipher).map(|x| x.needs_mac()).unwrap_or(false);
let client_mac = match Self::select(
&pref.mac,
&parse_kex_algo_list(r.read_string()?),
AlgorithmKind::Mac,
) {
Ok((_, m)) => m,
Err(e) => {
if need_mac {
return Err(e);
} else {
mac::NONE
}
}
};
let server_mac = match Self::select(
&pref.mac,
&parse_kex_algo_list(r.read_string()?),
AlgorithmKind::Mac,
) {
Ok((_, m)) => m,
Err(e) => {
if need_mac {
return Err(e);
} else {
mac::NONE
}
}
};
// Compression
debug!("kex {}", line!());
// client-to-server compression.
let client_compression = compression::Compression::new(
&Self::select(
&pref.compression,
&parse_kex_algo_list(r.read_string()?),
AlgorithmKind::Compression,
)?
.1,
);
debug!("kex {}", line!());
// server-to-client compression.
let server_compression = compression::Compression::new(
&Self::select(
&pref.compression,
&parse_kex_algo_list(r.read_string()?),
AlgorithmKind::Compression,
)?
.1,
);
debug!("client_compression = {:?}", client_compression);
r.read_string()?; // languages client-to-server
r.read_string()?; // languages server-to-client
let follows = r.read_byte()? != 0;
Ok(Names {
kex: kex_algorithm,
key: key_algorithm,
cipher,
client_mac,
server_mac,
client_compression,
server_compression,
// Ignore the next packet if (1) it follows and (2) it's not the correct guess.
ignore_guessed: follows && !(kex_both_first && key_both_first),
strict_kex: strict_kex_requested && strict_kex_provided,
})
}
}
pub struct Server;
pub struct Client;
impl Select for Server {
fn is_server() -> bool {
true
}
fn select<S: AsRef<str> + Clone>(
server_list: &[S],
client_list: &[&str],
kind: AlgorithmKind,
) -> Result<(bool, S), Error> {
let mut both_first_choice = true;
for c in client_list {
for s in server_list {
if c == &s.as_ref() {
return Ok((both_first_choice, s.clone()));
}
both_first_choice = false
}
}
Err(Error::NoCommonAlgo {
kind,
ours: server_list.iter().map(|x| x.as_ref().to_owned()).collect(),
theirs: client_list.iter().map(|x| (*x).to_owned()).collect(),
})
}
}
impl Select for Client {
fn is_server() -> bool {
false
}
fn select<S: AsRef<str> + Clone>(
client_list: &[S],
server_list: &[&str],
kind: AlgorithmKind,
) -> Result<(bool, S), Error> {
let mut both_first_choice = true;
for c in client_list {
for s in server_list {
if s == &c.as_ref() {
return Ok((both_first_choice, c.clone()));
}
both_first_choice = false
}
}
Err(Error::NoCommonAlgo {
kind,
ours: client_list.iter().map(|x| x.as_ref().to_owned()).collect(),
theirs: server_list.iter().map(|x| (*x).to_owned()).collect(),
})
}
}
pub fn write_kex(
prefs: &Preferred,
buf: &mut CryptoVec,
server_config: Option<&Config>,
) -> Result<(), Error> {
// buf.clear();
buf.push(msg::KEXINIT);
let mut cookie = [0; 16];
rand::thread_rng().fill_bytes(&mut cookie);
buf.extend(&cookie); // cookie
buf.extend_list(prefs.kex.iter().filter(|k| {
!(if server_config.is_some() {
[
crate::kex::EXTENSION_SUPPORT_AS_CLIENT,
crate::kex::EXTENSION_OPENSSH_STRICT_KEX_AS_CLIENT,
]
} else {
[
crate::kex::EXTENSION_SUPPORT_AS_SERVER,
crate::kex::EXTENSION_OPENSSH_STRICT_KEX_AS_SERVER,
]
})
.contains(*k)
})); // kex algo
if let Some(server_config) = server_config {
// Only advertise host key algorithms that we have keys for.
buf.extend_list(
prefs
.key
.iter()
.filter(|name| server_config.keys.iter().any(|k| k.name() == name.0)),
);
} else {
buf.extend_list(prefs.key.iter());
}
buf.extend_list(prefs.cipher.iter()); // cipher client to server
buf.extend_list(prefs.cipher.iter()); // cipher server to client
buf.extend_list(prefs.mac.iter()); // mac client to server
buf.extend_list(prefs.mac.iter()); // mac server to client
buf.extend_list(prefs.compression.iter()); // compress client to server
buf.extend_list(prefs.compression.iter()); // compress server to client
buf.write_empty_list(); // languages client to server
buf.write_empty_list(); // languagesserver to client
buf.push(0); // doesn't follow
buf.extend(&[0, 0, 0, 0]); // reserved
Ok(())
}

169
vendor/russh/src/parsing.rs vendored Normal file
View File

@@ -0,0 +1,169 @@
use crate::keys::encoding::{Encoding, Position};
use crate::{msg, CryptoVec};
#[derive(Debug)]
pub struct OpenChannelMessage {
pub typ: ChannelType,
pub recipient_channel: u32,
pub recipient_window_size: u32,
pub recipient_maximum_packet_size: u32,
}
impl OpenChannelMessage {
pub fn parse(r: &mut Position) -> Result<Self, crate::Error> {
// https://tools.ietf.org/html/rfc4254#section-5.1
let typ = r.read_string().map_err(crate::Error::from)?;
let sender = r.read_u32().map_err(crate::Error::from)?;
let window = r.read_u32().map_err(crate::Error::from)?;
let maxpacket = r.read_u32().map_err(crate::Error::from)?;
let typ = match typ {
b"session" => ChannelType::Session,
b"x11" => {
let originator_address =
std::str::from_utf8(r.read_string().map_err(crate::Error::from)?)
.map_err(crate::Error::from)?
.to_owned();
let originator_port = r.read_u32().map_err(crate::Error::from)?;
ChannelType::X11 {
originator_address,
originator_port,
}
}
b"direct-tcpip" => ChannelType::DirectTcpip(TcpChannelInfo::new(r)?),
b"forwarded-tcpip" => ChannelType::ForwardedTcpIp(TcpChannelInfo::new(r)?),
b"forwarded-streamlocal@openssh.com" => {
ChannelType::ForwardedStreamLocal(StreamLocalChannelInfo::new(r)?)
}
b"auth-agent@openssh.com" => ChannelType::AgentForward,
t => ChannelType::Unknown { typ: t.to_vec() },
};
Ok(Self {
typ,
recipient_channel: sender,
recipient_window_size: window,
recipient_maximum_packet_size: maxpacket,
})
}
/// Pushes a confirmation that this channel was opened to the vec.
pub fn confirm(
&self,
buffer: &mut CryptoVec,
sender_channel: u32,
window_size: u32,
packet_size: u32,
) {
push_packet!(buffer, {
buffer.push(msg::CHANNEL_OPEN_CONFIRMATION);
buffer.push_u32_be(self.recipient_channel); // remote channel number.
buffer.push_u32_be(sender_channel); // our channel number.
buffer.push_u32_be(window_size);
buffer.push_u32_be(packet_size);
});
}
/// Pushes a failure message to the vec.
pub fn fail(&self, buffer: &mut CryptoVec, reason: u8, message: &[u8]) {
push_packet!(buffer, {
buffer.push(msg::CHANNEL_OPEN_FAILURE);
buffer.push_u32_be(self.recipient_channel);
buffer.push_u32_be(reason as u32);
buffer.extend_ssh_string(message);
buffer.extend_ssh_string(b"en");
});
}
/// Pushes an unknown type error to the vec.
pub fn unknown_type(&self, buffer: &mut CryptoVec) {
self.fail(
buffer,
msg::SSH_OPEN_UNKNOWN_CHANNEL_TYPE,
b"Unknown channel type",
);
}
}
#[derive(Debug)]
pub enum ChannelType {
Session,
X11 {
originator_address: String,
originator_port: u32,
},
DirectTcpip(TcpChannelInfo),
ForwardedTcpIp(TcpChannelInfo),
ForwardedStreamLocal(StreamLocalChannelInfo),
AgentForward,
Unknown {
typ: Vec<u8>,
},
}
#[derive(Debug)]
pub struct TcpChannelInfo {
pub host_to_connect: String,
pub port_to_connect: u32,
pub originator_address: String,
pub originator_port: u32,
}
#[derive(Debug)]
pub struct StreamLocalChannelInfo {
pub socket_path: String,
}
impl StreamLocalChannelInfo {
fn new(r: &mut Position) -> Result<Self, crate::Error> {
let socket_path = std::str::from_utf8(r.read_string().map_err(crate::Error::from)?)
.map_err(crate::Error::from)?
.to_owned();
Ok(Self { socket_path })
}
}
impl TcpChannelInfo {
fn new(r: &mut Position) -> Result<Self, crate::Error> {
let host_to_connect = std::str::from_utf8(r.read_string().map_err(crate::Error::from)?)
.map_err(crate::Error::from)?
.to_owned();
let port_to_connect = r.read_u32().map_err(crate::Error::from)?;
let originator_address = std::str::from_utf8(r.read_string().map_err(crate::Error::from)?)
.map_err(crate::Error::from)?
.to_owned();
let originator_port = r.read_u32().map_err(crate::Error::from)?;
Ok(Self {
host_to_connect,
port_to_connect,
originator_address,
originator_port,
})
}
}
#[derive(Debug)]
pub(crate) struct ChannelOpenConfirmation {
pub recipient_channel: u32,
pub sender_channel: u32,
pub initial_window_size: u32,
pub maximum_packet_size: u32,
}
impl ChannelOpenConfirmation {
pub fn parse(r: &mut Position) -> Result<Self, crate::Error> {
let recipient_channel = r.read_u32().map_err(crate::Error::from)?;
let sender_channel = r.read_u32().map_err(crate::Error::from)?;
let initial_window_size = r.read_u32().map_err(crate::Error::from)?;
let maximum_packet_size = r.read_u32().map_err(crate::Error::from)?;
Ok(Self {
recipient_channel,
sender_channel,
initial_window_size,
maximum_packet_size,
})
}
}

134
vendor/russh/src/pty.rs vendored Executable file
View File

@@ -0,0 +1,134 @@
#[allow(non_camel_case_types, missing_docs)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
/// Standard pseudo-terminal codes.
pub enum Pty {
TTY_OP_END = 0,
VINTR = 1,
VQUIT = 2,
VERASE = 3,
VKILL = 4,
VEOF = 5,
VEOL = 6,
VEOL2 = 7,
VSTART = 8,
VSTOP = 9,
VSUSP = 10,
VDSUSP = 11,
VREPRINT = 12,
VWERASE = 13,
VLNEXT = 14,
VFLUSH = 15,
VSWTCH = 16,
VSTATUS = 17,
VDISCARD = 18,
IGNPAR = 30,
PARMRK = 31,
INPCK = 32,
ISTRIP = 33,
INLCR = 34,
IGNCR = 35,
ICRNL = 36,
IUCLC = 37,
IXON = 38,
IXANY = 39,
IXOFF = 40,
IMAXBEL = 41,
IUTF8 = 42,
ISIG = 50,
ICANON = 51,
XCASE = 52,
ECHO = 53,
ECHOE = 54,
ECHOK = 55,
ECHONL = 56,
NOFLSH = 57,
TOSTOP = 58,
IEXTEN = 59,
ECHOCTL = 60,
ECHOKE = 61,
PENDIN = 62,
OPOST = 70,
OLCUC = 71,
ONLCR = 72,
OCRNL = 73,
ONOCR = 74,
ONLRET = 75,
CS7 = 90,
CS8 = 91,
PARENB = 92,
PARODD = 93,
TTY_OP_ISPEED = 128,
TTY_OP_OSPEED = 129,
}
impl Pty {
#[doc(hidden)]
pub fn from_u8(x: u8) -> Option<Pty> {
match x {
0 => None,
1 => Some(Pty::VINTR),
2 => Some(Pty::VQUIT),
3 => Some(Pty::VERASE),
4 => Some(Pty::VKILL),
5 => Some(Pty::VEOF),
6 => Some(Pty::VEOL),
7 => Some(Pty::VEOL2),
8 => Some(Pty::VSTART),
9 => Some(Pty::VSTOP),
10 => Some(Pty::VSUSP),
11 => Some(Pty::VDSUSP),
12 => Some(Pty::VREPRINT),
13 => Some(Pty::VWERASE),
14 => Some(Pty::VLNEXT),
15 => Some(Pty::VFLUSH),
16 => Some(Pty::VSWTCH),
17 => Some(Pty::VSTATUS),
18 => Some(Pty::VDISCARD),
30 => Some(Pty::IGNPAR),
31 => Some(Pty::PARMRK),
32 => Some(Pty::INPCK),
33 => Some(Pty::ISTRIP),
34 => Some(Pty::INLCR),
35 => Some(Pty::IGNCR),
36 => Some(Pty::ICRNL),
37 => Some(Pty::IUCLC),
38 => Some(Pty::IXON),
39 => Some(Pty::IXANY),
40 => Some(Pty::IXOFF),
41 => Some(Pty::IMAXBEL),
42 => Some(Pty::IUTF8),
50 => Some(Pty::ISIG),
51 => Some(Pty::ICANON),
52 => Some(Pty::XCASE),
53 => Some(Pty::ECHO),
54 => Some(Pty::ECHOE),
55 => Some(Pty::ECHOK),
56 => Some(Pty::ECHONL),
57 => Some(Pty::NOFLSH),
58 => Some(Pty::TOSTOP),
59 => Some(Pty::IEXTEN),
60 => Some(Pty::ECHOCTL),
61 => Some(Pty::ECHOKE),
62 => Some(Pty::PENDIN),
70 => Some(Pty::OPOST),
71 => Some(Pty::OLCUC),
72 => Some(Pty::ONLCR),
73 => Some(Pty::OCRNL),
74 => Some(Pty::ONOCR),
75 => Some(Pty::ONLRET),
90 => Some(Pty::CS7),
91 => Some(Pty::CS8),
92 => Some(Pty::PARENB),
93 => Some(Pty::PARODD),
128 => Some(Pty::TTY_OP_ISPEED),
129 => Some(Pty::TTY_OP_OSPEED),
_ => None,
}
}
}

1307
vendor/russh/src/server/encrypted.rs vendored Normal file

File diff suppressed because it is too large Load Diff

140
vendor/russh/src/server/kex.rs vendored Normal file
View File

@@ -0,0 +1,140 @@
use std::cell::RefCell;
use log::debug;
use super::*;
use crate::cipher::SealingKey;
use crate::kex::KEXES;
use crate::key::PubKey;
use crate::keys::encoding::{Encoding, Reader};
use crate::negotiation::Select;
use crate::{msg, negotiation};
thread_local! {
static HASH_BUF: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());
}
impl KexInit {
pub fn server_parse(
mut self,
config: &Config,
cipher: &mut dyn SealingKey,
buf: &[u8],
write_buffer: &mut SSHBuffer,
) -> Result<Kex, Error> {
if buf.first() == Some(&msg::KEXINIT) {
let algo = {
// read algorithms from packet.
self.exchange.client_kex_init.extend(buf);
super::negotiation::Server::read_kex(buf, &config.preferred, Some(&config.keys))?
};
if !self.sent {
self.server_write(config, cipher, write_buffer)?
}
let mut key = 0;
#[allow(clippy::indexing_slicing)] // length checked
while key < config.keys.len() && config.keys[key].name() != algo.key.as_ref() {
key += 1
}
let next_kex = if key < config.keys.len() {
Kex::Dh(KexDh {
exchange: self.exchange,
key,
names: algo,
session_id: self.session_id,
})
} else {
debug!("unknown key {:?}", algo.key);
return Err(Error::UnknownKey);
};
Ok(next_kex)
} else {
Ok(Kex::Init(self))
}
}
pub fn server_write(
&mut self,
config: &Config,
cipher: &mut dyn SealingKey,
write_buffer: &mut SSHBuffer,
) -> Result<(), Error> {
self.exchange.server_kex_init.clear();
negotiation::write_kex(
&config.preferred,
&mut self.exchange.server_kex_init,
Some(config),
)?;
debug!("server kex init: {:?}", &self.exchange.server_kex_init[..]);
self.sent = true;
cipher.write(&self.exchange.server_kex_init, write_buffer);
Ok(())
}
}
impl KexDh {
pub fn parse(
mut self,
config: &Config,
cipher: &mut dyn SealingKey,
buf: &[u8],
write_buffer: &mut SSHBuffer,
) -> Result<Kex, Error> {
if self.names.ignore_guessed {
// If we need to ignore this packet.
self.names.ignore_guessed = false;
Ok(Kex::Dh(self))
} else {
// Else, process it.
assert!(buf.first() == Some(&msg::KEX_ECDH_INIT));
let mut r = buf.reader(1);
self.exchange.client_ephemeral.extend(r.read_string()?);
let mut kex = KEXES.get(&self.names.kex).ok_or(Error::UnknownAlgo)?.make();
kex.server_dh(&mut self.exchange, buf)?;
// Then, we fill the write buffer right away, so that we
// can output it immediately when the time comes.
let kexdhdone = KexDhDone {
exchange: self.exchange,
kex,
key: self.key,
names: self.names,
session_id: self.session_id,
};
#[allow(clippy::indexing_slicing)] // key index checked
let hash: Result<_, Error> = HASH_BUF.with(|buffer| {
let mut buffer = buffer.borrow_mut();
buffer.clear();
debug!("server kexdhdone.exchange = {:?}", kexdhdone.exchange);
let mut pubkey_vec = CryptoVec::new();
config.keys[kexdhdone.key].push_to(&mut pubkey_vec);
let hash = kexdhdone.kex.compute_exchange_hash(
&pubkey_vec,
&kexdhdone.exchange,
&mut buffer,
)?;
debug!("exchange hash: {:?}", hash);
buffer.clear();
buffer.push(msg::KEX_ECDH_REPLY);
config.keys[kexdhdone.key].push_to(&mut buffer);
// Server ephemeral
buffer.extend_ssh_string(&kexdhdone.exchange.server_ephemeral);
// Hash signature
debug!("signing with key {:?}", kexdhdone.key);
debug!("hash: {:?}", hash);
debug!("key: {:?}", config.keys[kexdhdone.key]);
config.keys[kexdhdone.key].add_signature(&mut buffer, &hash)?;
cipher.write(&buffer, write_buffer);
cipher.write(&[msg::NEWKEYS], write_buffer);
Ok(hash)
});
Ok(Kex::Keys(kexdhdone.compute_keys(hash?, true)?))
}
}
}

846
vendor/russh/src/server/mod.rs vendored Normal file
View File

@@ -0,0 +1,846 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//! # Writing servers
//!
//! There are two ways of accepting connections:
//! * implement the [Server](server::Server) trait and let [run_on_socket](server::Server::run_on_socket)/[run_on_address](server::Server::run_on_address) handle everything
//! * accept connections yourself and pass them to [run_stream](server::run_stream)
//!
//! In both cases, you'll first need to implement the [Handler](server::Handler) trait -
//! this is where you'll handle various events.
//!
//! Check out the following examples:
//!
//! * [Server that forwards your input to all connected clients](https://github.com/warp-tech/russh/blob/main/russh/examples/echoserver.rs)
//! * [Server handing channel processing off to a library (here, `russh-sftp`)](https://github.com/warp-tech/russh/blob/main/russh/examples/sftp_server.rs)
//! * Serving `ratatui` based TUI app to clients: [per-client](https://github.com/warp-tech/russh/blob/main/russh/examples/ratatui_app.rs), [shared](https://github.com/warp-tech/russh/blob/main/russh/examples/ratatui_shared_app.rs)
use std;
use std::collections::{HashMap, VecDeque};
use std::num::Wrapping;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};
use async_trait::async_trait;
use futures::future::Future;
use log::{debug, error};
use russh_util::runtime::JoinHandle;
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
use tokio::net::{TcpListener, ToSocketAddrs};
use tokio::pin;
use crate::cipher::{clear, CipherPair, OpeningKey};
use crate::keys::key;
use crate::session::*;
use crate::ssh_read::*;
use crate::sshbuffer::*;
use crate::*;
mod kex;
mod session;
pub use self::session::*;
mod encrypted;
#[derive(Debug)]
/// Configuration of a server.
pub struct Config {
/// The server ID string sent at the beginning of the protocol.
pub server_id: SshId,
/// Authentication methods proposed to the client.
pub methods: auth::MethodSet,
/// The authentication banner, usually a warning message shown to the client.
pub auth_banner: Option<&'static str>,
/// Authentication rejections must happen in constant time for
/// security reasons. Russh does not handle this by default.
pub auth_rejection_time: std::time::Duration,
/// Authentication rejection time override for the initial "none" auth attempt.
/// OpenSSH clients will send an initial "none" auth to probe for authentication methods.
pub auth_rejection_time_initial: Option<std::time::Duration>,
/// The server's keys. The first key pair in the client's preference order will be chosen.
pub keys: Vec<key::KeyPair>,
/// The bytes and time limits before key re-exchange.
pub limits: Limits,
/// The initial size of a channel (used for flow control).
pub window_size: u32,
/// The maximal size of a single packet.
pub maximum_packet_size: u32,
/// Internal event buffer size
pub event_buffer_size: usize,
/// Lists of preferred algorithms.
pub preferred: Preferred,
/// Maximal number of allowed authentication attempts.
pub max_auth_attempts: usize,
/// Time after which the connection is garbage-collected.
pub inactivity_timeout: Option<std::time::Duration>,
/// If nothing is received from the client for this amount of time, send a keepalive message.
pub keepalive_interval: Option<std::time::Duration>,
/// If this many keepalives have been sent without reply, close the connection.
pub keepalive_max: usize,
}
impl Default for Config {
fn default() -> Config {
Config {
server_id: SshId::Standard(format!(
"SSH-2.0-{}_{}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)),
methods: auth::MethodSet::all(),
auth_banner: None,
auth_rejection_time: std::time::Duration::from_secs(1),
auth_rejection_time_initial: None,
keys: Vec::new(),
window_size: 2097152,
maximum_packet_size: 32768,
event_buffer_size: 10,
limits: Limits::default(),
preferred: Default::default(),
max_auth_attempts: 10,
inactivity_timeout: Some(std::time::Duration::from_secs(600)),
keepalive_interval: None,
keepalive_max: 3,
}
}
}
/// A client's response in a challenge-response authentication.
///
/// You should iterate it to get `&[u8]` response slices.
#[derive(Debug)]
pub struct Response<'a> {
pos: russh_keys::encoding::Position<'a>,
n: u32,
}
impl<'a> Iterator for Response<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<Self::Item> {
if self.n == 0 {
None
} else {
self.n -= 1;
self.pos.read_string().ok()
}
}
}
use std::borrow::Cow;
/// An authentication result, in a challenge-response authentication.
#[derive(Debug, PartialEq, Eq)]
pub enum Auth {
/// Reject the authentication request.
Reject {
proceed_with_methods: Option<MethodSet>,
},
/// Accept the authentication request.
Accept,
/// Method was not accepted, but no other check was performed.
UnsupportedMethod,
/// Partially accept the challenge-response authentication
/// request, providing more instructions for the client to follow.
Partial {
/// Name of this challenge.
name: Cow<'static, str>,
/// Instructions for this challenge.
instructions: Cow<'static, str>,
/// A number of prompts to the user. Each prompt has a `bool`
/// indicating whether the terminal must echo the characters
/// typed by the user.
prompts: Cow<'static, [(Cow<'static, str>, bool)]>,
},
}
/// Server handler. Each client will have their own handler.
///
/// Note: this is an `async_trait`. Click `[source]` on the right to see actual async function definitions.
#[async_trait]
pub trait Handler: Sized {
type Error: From<crate::Error> + Send;
/// Check authentication using the "none" method. Russh makes
/// sure rejection happens in time `config.auth_rejection_time`,
/// except if this method takes more than that.
#[allow(unused_variables)]
async fn auth_none(&mut self, user: &str) -> Result<Auth, Self::Error> {
Ok(Auth::Reject {
proceed_with_methods: None,
})
}
/// Check authentication using the "password" method. Russh
/// makes sure rejection happens in time
/// `config.auth_rejection_time`, except if this method takes more
/// than that.
#[allow(unused_variables)]
async fn auth_password(&mut self, user: &str, password: &str) -> Result<Auth, Self::Error> {
Ok(Auth::Reject {
proceed_with_methods: None,
})
}
/// Check authentication using the "publickey" method. This method
/// should just check whether the public key matches the
/// authorized ones. Russh then checks the signature. If the key
/// is unknown, or the signature is invalid, Russh guarantees
/// that rejection happens in constant time
/// `config.auth_rejection_time`, except if this method takes more
/// time than that.
#[allow(unused_variables)]
async fn auth_publickey_offered(
&mut self,
user: &str,
public_key: &key::PublicKey,
) -> Result<Auth, Self::Error> {
Ok(Auth::Accept)
}
/// Check authentication using the "publickey" method. This method
/// is called after the signature has been verified and key
/// ownership has been confirmed.
/// Russh guarantees that rejection happens in constant time
/// `config.auth_rejection_time`, except if this method takes more
/// time than that.
#[allow(unused_variables)]
async fn auth_publickey(
&mut self,
user: &str,
public_key: &key::PublicKey,
) -> Result<Auth, Self::Error> {
Ok(Auth::Reject {
proceed_with_methods: None,
})
}
/// Check authentication using the "keyboard-interactive"
/// method. Russh makes sure rejection happens in time
/// `config.auth_rejection_time`, except if this method takes more
/// than that.
#[allow(unused_variables)]
async fn auth_keyboard_interactive(
&mut self,
user: &str,
submethods: &str,
response: Option<Response<'async_trait>>,
) -> Result<Auth, Self::Error> {
Ok(Auth::Reject {
proceed_with_methods: None,
})
}
/// Called when authentication succeeds for a session.
#[allow(unused_variables)]
async fn auth_succeeded(&mut self, session: &mut Session) -> Result<(), Self::Error> {
Ok(())
}
/// Called when the client closes a channel.
#[allow(unused_variables)]
async fn channel_close(
&mut self,
channel: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// Called when the client sends EOF to a channel.
#[allow(unused_variables)]
async fn channel_eof(
&mut self,
channel: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// Called when a new session channel is created.
/// Return value indicates whether the channel request should be granted.
#[allow(unused_variables)]
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// Called when a new X11 channel is created.
/// Return value indicates whether the channel request should be granted.
#[allow(unused_variables)]
async fn channel_open_x11(
&mut self,
channel: Channel<Msg>,
originator_address: &str,
originator_port: u32,
session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// Called when a new TCP/IP is created.
/// Return value indicates whether the channel request should be granted.
#[allow(unused_variables)]
async fn channel_open_direct_tcpip(
&mut self,
channel: Channel<Msg>,
host_to_connect: &str,
port_to_connect: u32,
originator_address: &str,
originator_port: u32,
session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// Called when a new forwarded connection comes in.
/// <https://www.rfc-editor.org/rfc/rfc4254#section-7>
#[allow(unused_variables)]
async fn channel_open_forwarded_tcpip(
&mut self,
channel: Channel<Msg>,
host_to_connect: &str,
port_to_connect: u32,
originator_address: &str,
originator_port: u32,
session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// Called when the client confirmed our request to open a
/// channel. A channel can only be written to after receiving this
/// message (this library panics otherwise).
#[allow(unused_variables)]
async fn channel_open_confirmation(
&mut self,
id: ChannelId,
max_packet_size: u32,
window_size: u32,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// Called when a data packet is received. A response can be
/// written to the `response` argument.
#[allow(unused_variables)]
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// Called when an extended data packet is received. Code 1 means
/// that this packet comes from stderr, other codes are not
/// defined (see
/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-5.2)).
#[allow(unused_variables)]
async fn extended_data(
&mut self,
channel: ChannelId,
code: u32,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// Called when the network window is adjusted, meaning that we
/// can send more bytes.
#[allow(unused_variables)]
async fn window_adjusted(
&mut self,
channel: ChannelId,
new_size: u32,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// Called when this server adjusts the network window. Return the
/// next target window.
#[allow(unused_variables)]
fn adjust_window(&mut self, channel: ChannelId, current: u32) -> u32 {
current
}
/// The client requests a pseudo-terminal with the given
/// specifications.
#[allow(unused_variables, clippy::too_many_arguments)]
async fn pty_request(
&mut self,
channel: ChannelId,
term: &str,
col_width: u32,
row_height: u32,
pix_width: u32,
pix_height: u32,
modes: &[(Pty, u32)],
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// The client requests an X11 connection.
#[allow(unused_variables)]
async fn x11_request(
&mut self,
channel: ChannelId,
single_connection: bool,
x11_auth_protocol: &str,
x11_auth_cookie: &str,
x11_screen_number: u32,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// The client wants to set the given environment variable. Check
/// these carefully, as it is dangerous to allow any variable
/// environment to be set.
#[allow(unused_variables)]
async fn env_request(
&mut self,
channel: ChannelId,
variable_name: &str,
variable_value: &str,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// The client requests a shell.
#[allow(unused_variables)]
async fn shell_request(
&mut self,
channel: ChannelId,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// The client sends a command to execute, to be passed to a
/// shell. Make sure to check the command before doing so.
#[allow(unused_variables)]
async fn exec_request(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// The client asks to start the subsystem with the given name
/// (such as sftp).
#[allow(unused_variables)]
async fn subsystem_request(
&mut self,
channel: ChannelId,
name: &str,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// The client's pseudo-terminal window size has changed.
#[allow(unused_variables)]
async fn window_change_request(
&mut self,
channel: ChannelId,
col_width: u32,
row_height: u32,
pix_width: u32,
pix_height: u32,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// The client requests OpenSSH agent forwarding
#[allow(unused_variables)]
async fn agent_request(
&mut self,
channel: ChannelId,
session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// The client is sending a signal (usually to pass to the
/// currently running process).
#[allow(unused_variables)]
async fn signal(
&mut self,
channel: ChannelId,
signal: Sig,
session: &mut Session,
) -> Result<(), Self::Error> {
Ok(())
}
/// Used for reverse-forwarding ports, see
/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-7).
/// If `port` is 0, you should set it to the allocated port number.
#[allow(unused_variables)]
async fn tcpip_forward(
&mut self,
address: &str,
port: &mut u32,
session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
/// Used to stop the reverse-forwarding of a port, see
/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-7).
#[allow(unused_variables)]
async fn cancel_tcpip_forward(
&mut self,
address: &str,
port: u32,
session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
#[allow(unused_variables)]
async fn streamlocal_forward(
&mut self,
socket_path: &str,
session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
#[allow(unused_variables)]
async fn cancel_streamlocal_forward(
&mut self,
socket_path: &str,
session: &mut Session,
) -> Result<bool, Self::Error> {
Ok(false)
}
}
#[async_trait]
/// Trait used to create new handlers when clients connect.
pub trait Server {
/// The type of handlers.
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 a server on a specified `tokio::net::TcpListener`. Useful when dropping
/// privileges immediately after socket binding, for example.
async fn run_on_socket(
&mut self,
config: Arc<Config>,
socket: &TcpListener,
) -> Result<(), std::io::Error> {
if config.maximum_packet_size > 65535 {
error!(
"Maximum packet size ({:?}) should not larger than a TCP packet (65535)",
config.maximum_packet_size
);
}
let (error_tx, mut error_rx) = tokio::sync::mpsc::unbounded_channel();
loop {
tokio::select! {
accept_result = socket.accept() => {
match accept_result {
Ok((socket, _)) => {
let config = config.clone();
let handler = self.new_client(socket.peer_addr().ok());
let error_tx = error_tx.clone();
russh_util::runtime::spawn(async move {
let session = match run_stream(config, socket, handler).await {
Ok(s) => s,
Err(e) => {
debug!("Connection setup failed");
let _ = error_tx.send(e);
return
}
};
match session.await {
Ok(_) => debug!("Connection closed"),
Err(e) => {
debug!("Connection closed with error");
let _ = error_tx.send(e);
}
}
});
}
_ => break,
}
},
Some(error) = error_rx.recv() => {
self.handle_session_error(error);
}
}
}
Ok(())
}
/// Run a server.
/// Create a new `Connection` from the server's configuration, a
/// stream and a [`Handler`](trait.Handler.html).
async fn run_on_address<A: ToSocketAddrs + Send>(
&mut self,
config: Arc<Config>,
addrs: A,
) -> Result<(), std::io::Error> {
let socket = TcpListener::bind(addrs).await?;
self.run_on_socket(config, &socket).await
}
}
use std::cell::RefCell;
thread_local! {
static B1: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());
static B2: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());
}
async fn start_reading<R: AsyncRead + Unpin>(
mut stream_read: R,
mut buffer: SSHBuffer,
mut cipher: Box<dyn OpeningKey + Send>,
) -> Result<(usize, R, SSHBuffer, Box<dyn OpeningKey + Send>), Error> {
buffer.buffer.clear();
let n = cipher::read(&mut stream_read, &mut buffer, &mut *cipher).await?;
Ok((n, stream_read, buffer, cipher))
}
/// An active server session returned by [run_stream].
///
/// Implements [Future] and can be awaited to wait for the session to finish.
pub struct RunningSession<H: Handler> {
handle: Handle,
join: JoinHandle<Result<(), H::Error>>,
}
impl<H: Handler> RunningSession<H> {
/// Returns a new handle for the session.
pub fn handle(&self) -> Handle {
self.handle.clone()
}
}
impl<H: Handler> Future for RunningSession<H> {
type Output = Result<(), H::Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
match Future::poll(Pin::new(&mut self.join), cx) {
Poll::Ready(r) => Poll::Ready(match r {
Ok(Ok(x)) => Ok(x),
Err(e) => Err(crate::Error::from(e).into()),
Ok(Err(e)) => Err(e),
}),
Poll::Pending => Poll::Pending,
}
}
}
/// Start a single connection in the background.
pub async fn run_stream<H, R>(
config: Arc<Config>,
mut stream: R,
handler: H,
) -> Result<RunningSession<H>, H::Error>
where
H: Handler + Send + 'static,
R: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
// Writing SSH id.
let mut write_buffer = SSHBuffer::new();
write_buffer.send_ssh_id(&config.as_ref().server_id);
stream
.write_all(&write_buffer.buffer[..])
.await
.map_err(crate::Error::from)?;
// Reading SSH id and allocating a session.
let mut stream = SshRead::new(stream);
let (sender, receiver) = tokio::sync::mpsc::channel(config.event_buffer_size);
let common = read_ssh_id(config, &mut stream).await?;
let handle = server::session::Handle { sender };
let session = Session {
target_window_size: common.config.window_size,
common,
receiver,
sender: handle.clone(),
pending_reads: Vec::new(),
pending_len: 0,
channels: HashMap::new(),
open_global_requests: VecDeque::new(),
};
let join = russh_util::runtime::spawn(session.run(stream, handler));
Ok(RunningSession { handle, join })
}
async fn read_ssh_id<R: AsyncRead + Unpin>(
config: Arc<Config>,
read: &mut SshRead<R>,
) -> Result<CommonSession<Arc<Config>>, Error> {
let sshid = if let Some(t) = config.inactivity_timeout {
tokio::time::timeout(t, read.read_ssh_id()).await??
} else {
read.read_ssh_id().await?
};
let mut exchange = Exchange::new();
exchange.client_id.extend(sshid);
// Preparing the response
exchange
.server_id
.extend(config.as_ref().server_id.as_kex_hash_bytes());
let mut kexinit = KexInit {
exchange,
algo: None,
sent: false,
session_id: None,
};
let mut cipher = CipherPair {
local_to_remote: Box::new(clear::Key),
remote_to_local: Box::new(clear::Key),
};
let mut write_buffer = SSHBuffer::new();
kexinit.server_write(
config.as_ref(),
&mut *cipher.local_to_remote,
&mut write_buffer,
)?;
Ok(CommonSession {
write_buffer,
kex: Some(Kex::Init(kexinit)),
auth_user: String::new(),
auth_method: None, // Client only.
auth_attempts: 0,
cipher,
encrypted: None,
config,
wants_reply: false,
disconnected: false,
buffer: CryptoVec::new(),
strict_kex: false,
alive_timeouts: 0,
received_data: false,
remote_sshid: sshid.into(),
})
}
const STRICT_KEX_MSG_ORDER: &[u8] = &[msg::KEXINIT, msg::KEX_ECDH_INIT, msg::NEWKEYS];
async fn reply<H: Handler + Send>(
session: &mut Session,
handler: &mut H,
seqn: &mut Wrapping<u32>,
buf: &[u8],
) -> Result<(), H::Error> {
if let Some(message_type) = buf.first() {
if session.common.strict_kex && session.common.encrypted.is_none() {
let seqno = seqn.0 - 1; // was incremented after read()
if let Some(expected) = STRICT_KEX_MSG_ORDER.get(seqno as usize) {
if message_type != expected {
return Err(strict_kex_violation(*message_type, seqno as usize).into());
}
}
}
if [msg::IGNORE, msg::UNIMPLEMENTED, msg::DEBUG].contains(message_type) {
return Ok(());
}
}
// Handle key exchange/re-exchange.
if session.common.encrypted.is_none() {
match session.common.kex.take() {
Some(Kex::Init(kexinit)) => {
if kexinit.algo.is_some() || buf.first() == Some(&msg::KEXINIT) {
session.common.kex = Some(kexinit.server_parse(
session.common.config.as_ref(),
&mut *session.common.cipher.local_to_remote,
buf,
&mut session.common.write_buffer,
)?);
if let Some(Kex::Dh(KexDh { ref names, .. })) = session.common.kex {
session.common.strict_kex = names.strict_kex;
}
// seqno has already been incremented after read()
if session.common.strict_kex && seqn.0 != 1 {
return Err(strict_kex_violation(msg::KEXINIT, seqn.0 as usize - 1).into());
}
return Ok(());
} else {
// Else, i.e. if the other side has not started
// the key exchange, process its packets by simple
// not returning.
session.common.kex = Some(Kex::Init(kexinit))
}
}
Some(Kex::Dh(kexdh)) => {
session.common.kex = Some(kexdh.parse(
session.common.config.as_ref(),
&mut *session.common.cipher.local_to_remote,
buf,
&mut session.common.write_buffer,
)?);
if let Some(Kex::Keys(_)) = session.common.kex {
// just sent NEWKEYS
session.common.maybe_reset_seqn();
}
return Ok(());
}
Some(Kex::Keys(newkeys)) => {
if buf.first() != Some(&msg::NEWKEYS) {
return Err(Error::Kex.into());
}
// Ok, NEWKEYS received, now encrypted.
session.common.encrypted(
EncryptedState::WaitingAuthServiceRequest {
sent: false,
accepted: false,
},
newkeys,
);
session.maybe_send_ext_info();
if session.common.strict_kex {
*seqn = Wrapping(0);
}
return Ok(());
}
Some(kex) => {
session.common.kex = Some(kex);
return Ok(());
}
None => {}
}
Ok(())
} else {
Ok(session.server_read_encrypted(handler, seqn, buf).await?)
}
}

1137
vendor/russh/src/server/session.rs vendored Normal file

File diff suppressed because it is too large Load Diff

636
vendor/russh/src/session.rs vendored Normal file
View File

@@ -0,0 +1,636 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::num::Wrapping;
use byteorder::{BigEndian, ByteOrder};
use log::{debug, trace};
use tokio::sync::oneshot;
use crate::cipher::SealingKey;
use crate::kex::KexAlgorithm;
use crate::keys::encoding::Encoding;
use crate::sshbuffer::SSHBuffer;
use crate::{
auth, cipher, mac, msg, negotiation, ChannelId, ChannelParams, CryptoVec, Disconnect, Limits,
};
#[derive(Debug)]
pub(crate) struct Encrypted {
pub state: EncryptedState,
// It's always Some, except when we std::mem::replace it temporarily.
pub exchange: Option<Exchange>,
pub kex: Box<dyn KexAlgorithm + Send>,
pub key: usize,
pub client_mac: mac::Name,
pub server_mac: mac::Name,
pub session_id: CryptoVec,
pub rekey: Option<Kex>,
pub channels: HashMap<ChannelId, ChannelParams>,
pub last_channel_id: Wrapping<u32>,
pub write: CryptoVec,
pub write_cursor: usize,
pub last_rekey: russh_util::time::Instant,
pub server_compression: crate::compression::Compression,
pub client_compression: crate::compression::Compression,
pub compress: crate::compression::Compress,
pub decompress: crate::compression::Decompress,
pub compress_buffer: CryptoVec,
}
pub(crate) struct CommonSession<Config> {
pub auth_user: String,
pub remote_sshid: Vec<u8>,
pub config: Config,
pub encrypted: Option<Encrypted>,
pub auth_method: Option<auth::Method>,
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(crate) auth_attempts: usize,
pub write_buffer: SSHBuffer,
pub kex: Option<Kex>,
pub cipher: cipher::CipherPair,
pub wants_reply: bool,
pub disconnected: bool,
pub buffer: CryptoVec,
pub strict_kex: bool,
pub alive_timeouts: usize,
pub received_data: bool,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum ChannelFlushResult {
Incomplete {
wrote: usize,
},
Complete {
wrote: usize,
pending_eof: bool,
pending_close: bool,
},
}
impl ChannelFlushResult {
pub(crate) fn wrote(&self) -> usize {
match self {
ChannelFlushResult::Incomplete { wrote } => *wrote,
ChannelFlushResult::Complete { wrote, .. } => *wrote,
}
}
pub(crate) fn complete(wrote: usize, channel: &ChannelParams) -> Self {
ChannelFlushResult::Complete {
wrote,
pending_eof: channel.pending_eof,
pending_close: channel.pending_close,
}
}
}
impl<C> CommonSession<C> {
pub fn newkeys(&mut self, newkeys: NewKeys) {
if let Some(ref mut enc) = self.encrypted {
enc.exchange = Some(newkeys.exchange);
enc.kex = newkeys.kex;
enc.key = newkeys.key;
enc.client_mac = newkeys.names.client_mac;
enc.server_mac = newkeys.names.server_mac;
self.cipher = newkeys.cipher;
self.strict_kex = self.strict_kex || newkeys.names.strict_kex;
}
}
pub fn encrypted(&mut self, state: EncryptedState, newkeys: NewKeys) {
self.encrypted = Some(Encrypted {
exchange: Some(newkeys.exchange),
kex: newkeys.kex,
key: newkeys.key,
client_mac: newkeys.names.client_mac,
server_mac: newkeys.names.server_mac,
session_id: newkeys.session_id,
state,
rekey: None,
channels: HashMap::new(),
last_channel_id: Wrapping(1),
write: CryptoVec::new(),
write_cursor: 0,
last_rekey: russh_util::time::Instant::now(),
server_compression: newkeys.names.server_compression,
client_compression: newkeys.names.client_compression,
compress: crate::compression::Compress::None,
compress_buffer: CryptoVec::new(),
decompress: crate::compression::Decompress::None,
});
self.cipher = newkeys.cipher;
self.strict_kex = newkeys.names.strict_kex;
}
/// Send a disconnect message.
pub fn disconnect(&mut self, reason: Disconnect, description: &str, language_tag: &str) {
let disconnect = |buf: &mut CryptoVec| {
push_packet!(buf, {
buf.push(msg::DISCONNECT);
buf.push_u32_be(reason as u32);
buf.extend_ssh_string(description.as_bytes());
buf.extend_ssh_string(language_tag.as_bytes());
});
};
if !self.disconnected {
self.disconnected = true;
if let Some(ref mut enc) = self.encrypted {
disconnect(&mut enc.write)
} else {
disconnect(&mut self.write_buffer.buffer)
}
}
}
/// Send a single byte message onto the channel.
#[cfg(not(target_arch = "wasm32"))]
pub fn byte(&mut self, channel: ChannelId, msg: u8) {
if let Some(ref mut enc) = self.encrypted {
enc.byte(channel, msg)
}
}
pub(crate) fn maybe_reset_seqn(&mut self) {
if self.strict_kex {
self.write_buffer.seqn = Wrapping(0);
}
}
}
impl Encrypted {
pub fn byte(&mut self, channel: ChannelId, msg: u8) {
if let Some(channel) = self.channels.get(&channel) {
push_packet!(self.write, {
self.write.push(msg);
self.write.push_u32_be(channel.recipient_channel);
});
}
}
/*
pub fn authenticated(&mut self) {
self.server_compression.init_compress(&mut self.compress);
self.state = EncryptedState::Authenticated;
}
*/
pub fn eof(&mut self, channel: ChannelId) {
if let Some(channel) = self.has_pending_data_mut(channel) {
channel.pending_eof = true;
} else {
self.byte(channel, msg::CHANNEL_EOF);
}
}
pub fn close(&mut self, channel: ChannelId) {
if let Some(channel) = self.has_pending_data_mut(channel) {
channel.pending_close = true;
} else {
self.byte(channel, msg::CHANNEL_CLOSE);
self.channels.remove(&channel);
}
}
pub fn sender_window_size(&self, channel: ChannelId) -> usize {
if let Some(channel) = self.channels.get(&channel) {
channel.sender_window_size as usize
} else {
0
}
}
pub fn adjust_window_size(&mut self, channel: ChannelId, data: &[u8], target: u32) -> bool {
if let Some(channel) = self.channels.get_mut(&channel) {
trace!(
"adjust_window_size, channel = {}, size = {},",
channel.sender_channel,
target
);
// Ignore extra data.
// https://tools.ietf.org/html/rfc4254#section-5.2
if data.len() as u32 <= channel.sender_window_size {
channel.sender_window_size -= data.len() as u32;
}
if channel.sender_window_size < target / 2 {
debug!(
"sender_window_size {:?}, target {:?}",
channel.sender_window_size, target
);
push_packet!(self.write, {
self.write.push(msg::CHANNEL_WINDOW_ADJUST);
self.write.push_u32_be(channel.recipient_channel);
self.write.push_u32_be(target - channel.sender_window_size);
});
channel.sender_window_size = target;
return true;
}
}
false
}
fn flush_channel(write: &mut CryptoVec, channel: &mut ChannelParams) -> ChannelFlushResult {
let mut pending_size = 0;
while let Some((buf, a, from)) = channel.pending_data.pop_front() {
let size = Self::data_noqueue(write, channel, &buf, a, from);
pending_size += size;
if from + size < buf.len() {
channel.pending_data.push_front((buf, a, from + size));
return ChannelFlushResult::Incomplete {
wrote: pending_size,
};
}
}
ChannelFlushResult::complete(pending_size, channel)
}
fn handle_flushed_channel(&mut self, channel: ChannelId, flush_result: ChannelFlushResult) {
if let ChannelFlushResult::Complete {
wrote: _,
pending_eof,
pending_close,
} = flush_result
{
if pending_eof {
self.eof(channel);
}
if pending_close {
self.close(channel);
}
}
}
pub fn flush_pending(&mut self, channel: ChannelId) -> usize {
let mut pending_size = 0;
let mut maybe_flush_result = Option::<ChannelFlushResult>::None;
if let Some(channel) = self.channels.get_mut(&channel) {
let flush_result = Self::flush_channel(&mut self.write, channel);
pending_size += flush_result.wrote();
maybe_flush_result = Some(flush_result);
}
if let Some(flush_result) = maybe_flush_result {
self.handle_flushed_channel(channel, flush_result)
}
pending_size
}
pub fn flush_all_pending(&mut self) {
for channel in self.channels.values_mut() {
Self::flush_channel(&mut self.write, channel);
}
}
fn has_pending_data_mut(&mut self, channel: ChannelId) -> Option<&mut ChannelParams> {
self.channels
.get_mut(&channel)
.filter(|c| !c.pending_data.is_empty())
}
pub fn has_pending_data(&self, channel: ChannelId) -> bool {
if let Some(channel) = self.channels.get(&channel) {
!channel.pending_data.is_empty()
} else {
false
}
}
/// Push the largest amount of `&buf0[from..]` that can fit into
/// the window, dividing it into packets if it is too large, and
/// return the length that was written.
fn data_noqueue(
write: &mut CryptoVec,
channel: &mut ChannelParams,
buf0: &[u8],
a: Option<u32>,
from: usize,
) -> usize {
if from >= buf0.len() {
return 0;
}
let mut buf = if buf0.len() as u32 > from as u32 + channel.recipient_window_size {
#[allow(clippy::indexing_slicing)] // length checked
&buf0[from..from + channel.recipient_window_size as usize]
} else {
#[allow(clippy::indexing_slicing)] // length checked
&buf0[from..]
};
let buf_len = buf.len();
while !buf.is_empty() {
// Compute the length we're allowed to send.
let off = std::cmp::min(buf.len(), channel.recipient_maximum_packet_size as usize);
match a {
None => push_packet!(write, {
write.push(msg::CHANNEL_DATA);
write.push_u32_be(channel.recipient_channel);
#[allow(clippy::indexing_slicing)] // length checked
write.extend_ssh_string(&buf[..off]);
}),
Some(ext) => push_packet!(write, {
write.push(msg::CHANNEL_EXTENDED_DATA);
write.push_u32_be(channel.recipient_channel);
write.push_u32_be(ext);
#[allow(clippy::indexing_slicing)] // length checked
write.extend_ssh_string(&buf[..off]);
}),
}
trace!(
"buffer: {:?} {:?}",
write.len(),
channel.recipient_window_size
);
channel.recipient_window_size -= off as u32;
#[allow(clippy::indexing_slicing)] // length checked
{
buf = &buf[off..]
}
}
trace!("buf.len() = {:?}, buf_len = {:?}", buf.len(), buf_len);
buf_len
}
pub fn data(&mut self, channel: ChannelId, buf0: CryptoVec) {
if let Some(channel) = self.channels.get_mut(&channel) {
assert!(channel.confirmed);
if !channel.pending_data.is_empty() || self.rekey.is_some() {
channel.pending_data.push_back((buf0, None, 0));
return;
}
let buf_len = Self::data_noqueue(&mut self.write, channel, &buf0, None, 0);
if buf_len < buf0.len() {
channel.pending_data.push_back((buf0, None, buf_len))
}
} else {
debug!("{:?} not saved for this session", channel);
}
}
pub fn extended_data(&mut self, channel: ChannelId, ext: u32, buf0: CryptoVec) {
if let Some(channel) = self.channels.get_mut(&channel) {
assert!(channel.confirmed);
if !channel.pending_data.is_empty() {
channel.pending_data.push_back((buf0, Some(ext), 0));
return;
}
let buf_len = Self::data_noqueue(&mut self.write, channel, &buf0, Some(ext), 0);
if buf_len < buf0.len() {
channel.pending_data.push_back((buf0, Some(ext), buf_len))
}
}
}
pub fn flush(
&mut self,
limits: &Limits,
cipher: &mut dyn SealingKey,
write_buffer: &mut SSHBuffer,
) -> Result<bool, crate::Error> {
// If there are pending packets (and we've not started to rekey), flush them.
{
while self.write_cursor < self.write.len() {
// Read a single packet, encrypt and send it.
#[allow(clippy::indexing_slicing)] // length checked
let len = BigEndian::read_u32(&self.write[self.write_cursor..]) as usize;
#[allow(clippy::indexing_slicing)]
let to_write = &self.write[(self.write_cursor + 4)..(self.write_cursor + 4 + len)];
trace!("server_write_encrypted, buf = {:?}", to_write);
#[allow(clippy::indexing_slicing)]
let packet = self
.compress
.compress(to_write, &mut self.compress_buffer)?;
cipher.write(packet, write_buffer);
self.write_cursor += 4 + len
}
}
if self.write_cursor >= self.write.len() {
// If all packets have been written, clear.
self.write_cursor = 0;
self.write.clear();
}
if self.kex.skip_exchange() {
return Ok(false);
}
let now = russh_util::time::Instant::now();
let dur = now.duration_since(self.last_rekey);
Ok(write_buffer.bytes >= limits.rekey_write_limit || dur >= limits.rekey_time_limit)
}
pub fn new_channel_id(&mut self) -> ChannelId {
self.last_channel_id += Wrapping(1);
while self
.channels
.contains_key(&ChannelId(self.last_channel_id.0))
{
self.last_channel_id += Wrapping(1)
}
ChannelId(self.last_channel_id.0)
}
pub fn new_channel(&mut self, window_size: u32, maxpacket: u32) -> ChannelId {
loop {
self.last_channel_id += Wrapping(1);
if let std::collections::hash_map::Entry::Vacant(vacant_entry) =
self.channels.entry(ChannelId(self.last_channel_id.0))
{
vacant_entry.insert(ChannelParams {
recipient_channel: 0,
sender_channel: ChannelId(self.last_channel_id.0),
sender_window_size: window_size,
recipient_window_size: 0,
sender_maximum_packet_size: maxpacket,
recipient_maximum_packet_size: 0,
confirmed: false,
wants_reply: false,
pending_data: std::collections::VecDeque::new(),
pending_eof: false,
pending_close: false,
});
return ChannelId(self.last_channel_id.0);
}
}
}
}
#[derive(Debug)]
pub enum EncryptedState {
WaitingAuthServiceRequest { sent: bool, accepted: bool },
WaitingAuthRequest(auth::AuthRequest),
InitCompression,
Authenticated,
}
#[derive(Debug, Default, Clone)]
pub struct Exchange {
pub client_id: CryptoVec,
pub server_id: CryptoVec,
pub client_kex_init: CryptoVec,
pub server_kex_init: CryptoVec,
pub client_ephemeral: CryptoVec,
pub server_ephemeral: CryptoVec,
}
impl Exchange {
pub fn new() -> Self {
Exchange {
client_id: CryptoVec::new(),
server_id: CryptoVec::new(),
client_kex_init: CryptoVec::new(),
server_kex_init: CryptoVec::new(),
client_ephemeral: CryptoVec::new(),
server_ephemeral: CryptoVec::new(),
}
}
}
#[derive(Debug)]
pub(crate) enum Kex {
/// Version number sent. `algo` and `sent` tell wether kexinit has
/// been received, and sent, respectively.
Init(KexInit),
/// Algorithms have been determined, the DH algorithm should run.
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
Dh(KexDh),
/// The kex has run.
DhDone(KexDhDone),
/// The DH is over, we've sent the NEWKEYS packet, and are waiting
/// the NEWKEYS from the other side.
Keys(NewKeys),
}
#[derive(Debug)]
pub(crate) struct KexInit {
pub algo: Option<negotiation::Names>,
pub exchange: Exchange,
pub session_id: Option<CryptoVec>,
pub sent: bool,
}
impl KexInit {
pub fn received_rekey(ex: Exchange, algo: negotiation::Names, session_id: &CryptoVec) -> Self {
let mut kexinit = KexInit {
exchange: ex,
algo: Some(algo),
sent: false,
session_id: Some(session_id.clone()),
};
kexinit.exchange.client_kex_init.clear();
kexinit.exchange.server_kex_init.clear();
kexinit.exchange.client_ephemeral.clear();
kexinit.exchange.server_ephemeral.clear();
kexinit
}
pub fn initiate_rekey(ex: Exchange, session_id: &CryptoVec) -> Self {
let mut kexinit = KexInit {
exchange: ex,
algo: None,
sent: true,
session_id: Some(session_id.clone()),
};
kexinit.exchange.client_kex_init.clear();
kexinit.exchange.server_kex_init.clear();
kexinit.exchange.client_ephemeral.clear();
kexinit.exchange.server_ephemeral.clear();
kexinit
}
}
#[derive(Debug)]
#[cfg_attr(target_arch = "wasm32", allow(dead_code))]
pub(crate) struct KexDh {
pub exchange: Exchange,
pub names: negotiation::Names,
pub key: usize,
pub session_id: Option<CryptoVec>,
}
pub(crate) struct KexDhDone {
pub exchange: Exchange,
pub kex: Box<dyn KexAlgorithm + Send>,
pub key: usize,
pub session_id: Option<CryptoVec>,
pub names: negotiation::Names,
}
impl Debug for KexDhDone {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "KexDhDone")
}
}
impl KexDhDone {
pub fn compute_keys(self, hash: CryptoVec, is_server: bool) -> Result<NewKeys, crate::Error> {
let session_id = if let Some(session_id) = self.session_id {
session_id
} else {
hash.clone()
};
// Now computing keys.
let c = self.kex.compute_keys(
&session_id,
&hash,
self.names.cipher,
if is_server {
self.names.client_mac
} else {
self.names.server_mac
},
if is_server {
self.names.server_mac
} else {
self.names.client_mac
},
is_server,
)?;
Ok(NewKeys {
exchange: self.exchange,
names: self.names,
kex: self.kex,
key: self.key,
cipher: c,
session_id,
sent: false,
})
}
}
#[derive(Debug)]
pub(crate) struct NewKeys {
pub exchange: Exchange,
pub names: negotiation::Names,
pub kex: Box<dyn KexAlgorithm + Send>,
pub key: usize,
pub cipher: cipher::CipherPair,
pub session_id: CryptoVec,
pub sent: bool,
}
pub(crate) enum GlobalRequestResponse {
/// request was for Keepalive, ignore result
Keepalive,
/// request was for TcpIpForward, sends Some(port) for success or None for failure
TcpIpForward(oneshot::Sender<Option<u32>>),
/// request was for CancelTcpIpForward, sends true for success or false for failure
CancelTcpIpForward(oneshot::Sender<bool>),
/// request was for StreamLocalForward, sends true for success or false for failure
StreamLocalForward(oneshot::Sender<bool>),
CancelStreamLocalForward(oneshot::Sender<bool>),
}

171
vendor/russh/src/ssh_read.rs vendored Normal file
View File

@@ -0,0 +1,171 @@
use std::pin::Pin;
use futures::task::*;
use log::debug;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, ReadBuf};
use crate::{CryptoVec, Error};
/// The buffer to read the identification string (first line in the
/// protocol).
struct ReadSshIdBuffer {
pub buf: CryptoVec,
pub total: usize,
pub bytes_read: usize,
pub sshid_len: usize,
}
impl ReadSshIdBuffer {
pub fn id(&self) -> &[u8] {
#[allow(clippy::indexing_slicing)] // length checked
&self.buf[..self.sshid_len]
}
pub fn new() -> ReadSshIdBuffer {
let mut buf = CryptoVec::new();
buf.resize(256);
ReadSshIdBuffer {
buf,
sshid_len: 0,
bytes_read: 0,
total: 0,
}
}
}
impl std::fmt::Debug for ReadSshIdBuffer {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(fmt, "ReadSshId {:?}", self.id())
}
}
/// SshRead<R> is the same as R, plus a small buffer in the beginning to
/// read the identification string. After the first line in the
/// connection, the `id` parameter is never used again.
pub struct SshRead<R> {
id: Option<ReadSshIdBuffer>,
pub r: R,
}
impl<R: AsyncRead + AsyncWrite> SshRead<R> {
pub fn split(self) -> (SshRead<tokio::io::ReadHalf<R>>, tokio::io::WriteHalf<R>) {
let (r, w) = tokio::io::split(self.r);
(SshRead { id: self.id, r }, w)
}
}
impl<R: AsyncRead + Unpin> AsyncRead for SshRead<R> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context,
buf: &mut ReadBuf,
) -> Poll<Result<(), std::io::Error>> {
if let Some(mut id) = self.id.take() {
debug!("id {:?} {:?}", id.total, id.bytes_read);
if id.total > id.bytes_read {
let total = id.total.min(id.bytes_read + buf.remaining());
#[allow(clippy::indexing_slicing)] // length checked
buf.put_slice(&id.buf[id.bytes_read..total]);
id.bytes_read += total - id.bytes_read;
self.id = Some(id);
return Poll::Ready(Ok(()));
}
}
AsyncRead::poll_read(Pin::new(&mut self.get_mut().r), cx, buf)
}
}
impl<R: std::io::Write> std::io::Write for SshRead<R> {
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
self.r.write(buf)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
self.r.flush()
}
}
impl<R: AsyncWrite + Unpin> AsyncWrite for SshRead<R> {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
AsyncWrite::poll_write(Pin::new(&mut self.r), cx, buf)
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), std::io::Error>> {
AsyncWrite::poll_flush(Pin::new(&mut self.r), cx)
}
fn poll_shutdown(
mut self: Pin<&mut Self>,
cx: &mut Context,
) -> Poll<Result<(), std::io::Error>> {
AsyncWrite::poll_shutdown(Pin::new(&mut self.r), cx)
}
}
impl<R: AsyncRead + Unpin> SshRead<R> {
pub fn new(r: R) -> Self {
SshRead {
id: Some(ReadSshIdBuffer::new()),
r,
}
}
#[allow(clippy::unwrap_used)]
pub async fn read_ssh_id(&mut self) -> Result<&[u8], Error> {
let ssh_id = self.id.as_mut().unwrap();
loop {
let mut i = 0;
debug!("read_ssh_id: reading");
#[allow(clippy::indexing_slicing)] // length checked
let n = AsyncReadExt::read(&mut self.r, &mut ssh_id.buf[ssh_id.total..]).await?;
debug!("read {:?}", n);
ssh_id.total += n;
#[allow(clippy::indexing_slicing)] // length checked
{
debug!("{:?}", std::str::from_utf8(&ssh_id.buf[..ssh_id.total]));
}
if n == 0 {
return Err(Error::Disconnect);
}
#[allow(clippy::indexing_slicing)] // length checked
loop {
if i >= ssh_id.total - 1 {
break;
}
if ssh_id.buf[i] == b'\r' && ssh_id.buf[i + 1] == b'\n' {
ssh_id.bytes_read = i + 2;
break;
} else if ssh_id.buf[i + 1] == b'\n' {
// This is really wrong, but OpenSSH 7.4 uses
// it.
ssh_id.bytes_read = i + 2;
i += 1;
break;
} else {
i += 1;
}
}
if ssh_id.bytes_read > 0 {
// If we have a full line, handle it.
if i >= 8 && ssh_id.buf.get(0..8) == Some(b"SSH-2.0-") {
// Either the line starts with "SSH-2.0-"
ssh_id.sshid_len = i;
#[allow(clippy::indexing_slicing)] // length checked
return Ok(&ssh_id.buf[..ssh_id.sshid_len]);
}
// Else, it is a "preliminary" (see
// https://tools.ietf.org/html/rfc4253#section-4.2),
// and we can discard it and read the next one.
ssh_id.total = 0;
ssh_id.bytes_read = 0;
}
debug!("bytes_read: {:?}", ssh_id.bytes_read);
}
}
}

88
vendor/russh/src/sshbuffer.rs vendored Normal file
View File

@@ -0,0 +1,88 @@
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
use std::num::Wrapping;
use super::*;
/// The SSH client/server identification string.
#[derive(Debug)]
pub enum SshId {
/// When sending the id, append RFC standard `\r\n`. Example: `SshId::Standard("SSH-2.0-acme")`
Standard(String),
/// When sending the id, use this buffer as it is and do not append additional line terminators.
Raw(String),
}
impl SshId {
pub(crate) fn as_kex_hash_bytes(&self) -> &[u8] {
match self {
Self::Standard(s) => s.as_bytes(),
Self::Raw(s) => s.trim_end_matches(['\n', '\r']).as_bytes(),
}
}
pub(crate) fn write(&self, buffer: &mut CryptoVec) {
match self {
Self::Standard(s) => buffer.extend(format!("{}\r\n", s).as_bytes()),
Self::Raw(s) => buffer.extend(s.as_bytes()),
}
}
}
#[test]
fn test_ssh_id() {
let mut buffer = CryptoVec::new();
SshId::Standard("SSH-2.0-acme".to_string()).write(&mut buffer);
assert_eq!(&buffer[..], b"SSH-2.0-acme\r\n");
let mut buffer = CryptoVec::new();
SshId::Raw("SSH-2.0-raw\n".to_string()).write(&mut buffer);
assert_eq!(&buffer[..], b"SSH-2.0-raw\n");
assert_eq!(
SshId::Standard("SSH-2.0-acme".to_string()).as_kex_hash_bytes(),
b"SSH-2.0-acme"
);
assert_eq!(
SshId::Raw("SSH-2.0-raw\n".to_string()).as_kex_hash_bytes(),
b"SSH-2.0-raw"
);
}
#[derive(Debug, Default)]
pub struct SSHBuffer {
pub buffer: CryptoVec,
pub len: usize, // next packet length.
pub bytes: usize,
// Sequence numbers are on 32 bits and wrap.
// https://tools.ietf.org/html/rfc4253#section-6.4
pub seqn: Wrapping<u32>,
}
impl SSHBuffer {
pub fn new() -> Self {
SSHBuffer {
buffer: CryptoVec::new(),
len: 0,
bytes: 0,
seqn: Wrapping(0),
}
}
pub fn send_ssh_id(&mut self, id: &SshId) {
id.write(&mut self.buffer);
}
}

470
vendor/russh/src/tests.rs vendored Normal file
View File

@@ -0,0 +1,470 @@
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] // Allow unwraps, expects and panics in the test suite
use futures::Future;
use super::*;
mod compress {
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use log::debug;
use super::server::{Server as _, Session};
use super::*;
use crate::server::Msg;
#[tokio::test]
async fn compress_local_test() {
let _ = env_logger::try_init();
let client_key = russh_keys::key::KeyPair::generate_ed25519();
let mut config = server::Config::default();
config.preferred = Preferred::COMPRESSED;
config.inactivity_timeout = None; // Some(std::time::Duration::from_secs(3));
config.auth_rejection_time = std::time::Duration::from_secs(3);
config
.keys
.push(russh_keys::key::KeyPair::generate_ed25519());
let config = Arc::new(config);
let mut sh = Server {
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
};
let socket = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = socket.local_addr().unwrap();
tokio::spawn(async move {
let (socket, _) = socket.accept().await.unwrap();
let server = sh.new_client(socket.peer_addr().ok());
server::run_stream(config, socket, server).await.unwrap();
});
let mut config = client::Config::default();
config.preferred = Preferred::COMPRESSED;
let config = Arc::new(config);
dbg!(&addr);
let mut session = client::connect(config, addr, Client {}).await.unwrap();
let authenticated = session
.authenticate_publickey(
std::env::var("USER").unwrap_or("user".to_owned()),
Arc::new(client_key),
)
.await
.unwrap();
assert!(authenticated);
let mut channel = session.channel_open_session().await.unwrap();
let data = &b"Hello, world!"[..];
channel.data(data).await.unwrap();
let msg = channel.wait().await.unwrap();
match msg {
ChannelMsg::Data { data: msg_data } => {
assert_eq!(*data, *msg_data)
}
msg => panic!("Unexpected message {:?}", msg),
}
}
#[derive(Clone)]
struct Server {
clients: Arc<Mutex<HashMap<(usize, ChannelId), super::server::Handle>>>,
id: usize,
}
impl server::Server for Server {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
self.id += 1;
s
}
}
#[async_trait]
impl server::Handler for Server {
type Error = super::Error;
async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().unwrap();
clients.insert((self.id, channel.id()), session.handle());
}
Ok(true)
}
async fn auth_publickey(
&mut self,
_: &str,
_: &russh_keys::key::PublicKey,
) -> Result<server::Auth, Self::Error> {
debug!("auth_publickey");
Ok(server::Auth::Accept)
}
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
debug!("server data = {:?}", std::str::from_utf8(data));
session.data(channel, CryptoVec::from_slice(data));
Ok(())
}
}
struct Client {}
#[async_trait]
impl client::Handler for Client {
type Error = super::Error;
async fn check_server_key(
&mut self,
_server_public_key: &russh_keys::key::PublicKey,
) -> Result<bool, Self::Error> {
// println!("check_server_key: {:?}", server_public_key);
Ok(true)
}
}
}
mod channels {
use async_trait::async_trait;
use server::Session;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use super::*;
use crate::CryptoVec;
async fn test_session<RC, RS, CH, SH, F1, F2>(
client_handler: CH,
server_handler: SH,
run_client: RC,
run_server: RS,
) where
RC: FnOnce(crate::client::Handle<CH>) -> F1 + Send + Sync + 'static,
RS: FnOnce(crate::server::Handle) -> F2 + Send + Sync + 'static,
F1: Future<Output = crate::client::Handle<CH>> + Send + Sync + 'static,
F2: Future<Output = crate::server::Handle> + Send + Sync + 'static,
CH: crate::client::Handler + Send + Sync + 'static,
SH: crate::server::Handler + Send + Sync + 'static,
{
use std::sync::Arc;
use crate::*;
let _ = env_logger::try_init();
let client_key = russh_keys::key::KeyPair::generate_ed25519();
let mut config = server::Config::default();
config.inactivity_timeout = None;
config.auth_rejection_time = std::time::Duration::from_secs(3);
config
.keys
.push(russh_keys::key::KeyPair::generate_ed25519());
let config = Arc::new(config);
let socket = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = socket.local_addr().unwrap();
let server_join = tokio::spawn(async move {
let (socket, _) = socket.accept().await.unwrap();
server::run_stream(config, socket, server_handler)
.await
.map_err(|_| ())
.unwrap()
});
let client_join = tokio::spawn(async move {
let config = Arc::new(client::Config::default());
let mut session = client::connect(config, addr, client_handler)
.await
.map_err(|_| ())
.unwrap();
let authenticated = session
.authenticate_publickey(
std::env::var("USER").unwrap_or("user".to_owned()),
Arc::new(client_key),
)
.await
.unwrap();
assert!(authenticated);
session
});
let (server_session, client_session) = tokio::join!(server_join, client_join);
let client_handle = tokio::spawn(run_client(client_session.unwrap()));
let server_handle = tokio::spawn(run_server(server_session.unwrap().handle()));
let (server_session, client_session) = tokio::join!(server_handle, client_handle);
drop(client_session);
drop(server_session);
}
#[tokio::test]
async fn test_server_channels() {
#[derive(Debug)]
struct Client {}
#[async_trait]
impl client::Handler for Client {
type Error = crate::Error;
async fn check_server_key(
&mut self,
_server_public_key: &russh_keys::key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut client::Session,
) -> Result<(), Self::Error> {
assert_eq!(data, &b"hello world!"[..]);
session.data(channel, CryptoVec::from_slice(&b"hey there!"[..]));
Ok(())
}
}
struct ServerHandle {
did_auth: Option<tokio::sync::oneshot::Sender<()>>,
}
impl ServerHandle {
fn get_auth_waiter(&mut self) -> tokio::sync::oneshot::Receiver<()> {
let (tx, rx) = tokio::sync::oneshot::channel();
self.did_auth = Some(tx);
rx
}
}
#[async_trait]
impl server::Handler for ServerHandle {
type Error = crate::Error;
async fn auth_publickey(
&mut self,
_: &str,
_: &russh_keys::key::PublicKey,
) -> Result<server::Auth, Self::Error> {
Ok(server::Auth::Accept)
}
async fn auth_succeeded(&mut self, _session: &mut Session) -> Result<(), Self::Error> {
if let Some(a) = self.did_auth.take() {
a.send(()).unwrap();
}
Ok(())
}
}
let mut sh = ServerHandle { did_auth: None };
let a = sh.get_auth_waiter();
test_session(
Client {},
sh,
|c| async move { c },
|s| async move {
a.await.unwrap();
let mut ch = s.channel_open_session().await.unwrap();
ch.data(&b"hello world!"[..]).await.unwrap();
let msg = ch.wait().await.unwrap();
if let ChannelMsg::Data { data } = msg {
assert_eq!(data.as_ref(), &b"hey there!"[..]);
} else {
panic!("Unexpected message {:?}", msg);
}
s
},
)
.await;
}
#[tokio::test]
async fn test_channel_streams() {
#[derive(Debug)]
struct Client {}
#[async_trait]
impl client::Handler for Client {
type Error = crate::Error;
async fn check_server_key(
&mut self,
_server_public_key: &russh_keys::key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
struct ServerHandle {
channel: Option<tokio::sync::oneshot::Sender<Channel<server::Msg>>>,
}
impl ServerHandle {
fn get_channel_waiter(
&mut self,
) -> tokio::sync::oneshot::Receiver<Channel<server::Msg>> {
let (tx, rx) = tokio::sync::oneshot::channel::<Channel<server::Msg>>();
self.channel = Some(tx);
rx
}
}
#[async_trait]
impl server::Handler for ServerHandle {
type Error = crate::Error;
async fn auth_publickey(
&mut self,
_: &str,
_: &russh_keys::key::PublicKey,
) -> Result<server::Auth, Self::Error> {
Ok(server::Auth::Accept)
}
async fn channel_open_session(
&mut self,
channel: Channel<server::Msg>,
_session: &mut server::Session,
) -> Result<bool, Self::Error> {
if let Some(a) = self.channel.take() {
println!("channel open session {:?}", a);
a.send(channel).unwrap();
}
Ok(true)
}
}
let mut sh = ServerHandle { channel: None };
let scw = sh.get_channel_waiter();
test_session(
Client {},
sh,
|client| async move {
let ch = client.channel_open_session().await.unwrap();
let mut stream = ch.into_stream();
stream.write_all(&b"request"[..]).await.unwrap();
let mut buf = Vec::new();
stream.read_buf(&mut buf).await.unwrap();
assert_eq!(&buf, &b"response"[..]);
stream.write_all(&b"reply"[..]).await.unwrap();
client
},
|server| async move {
let channel = scw.await.unwrap();
let mut stream = channel.into_stream();
let mut buf = Vec::new();
stream.read_buf(&mut buf).await.unwrap();
assert_eq!(&buf, &b"request"[..]);
stream.write_all(&b"response"[..]).await.unwrap();
buf.clear();
stream.read_buf(&mut buf).await.unwrap();
assert_eq!(&buf, &b"reply"[..]);
server
},
)
.await;
}
#[tokio::test]
async fn test_channel_objects() {
#[derive(Debug)]
struct Client {}
#[async_trait]
impl client::Handler for Client {
type Error = crate::Error;
async fn check_server_key(
&mut self,
_server_public_key: &russh_keys::key::PublicKey,
) -> Result<bool, Self::Error> {
Ok(true)
}
}
struct ServerHandle {}
impl ServerHandle {}
#[async_trait]
impl server::Handler for ServerHandle {
type Error = crate::Error;
async fn auth_publickey(
&mut self,
_: &str,
_: &russh_keys::key::PublicKey,
) -> Result<server::Auth, Self::Error> {
Ok(server::Auth::Accept)
}
async fn channel_open_session(
&mut self,
mut channel: Channel<server::Msg>,
_session: &mut Session,
) -> Result<bool, Self::Error> {
tokio::spawn(async move {
while let Some(msg) = channel.wait().await {
match msg {
ChannelMsg::Data { data } => {
channel.data(&data[..]).await.unwrap();
channel.close().await.unwrap();
break;
}
_ => {}
}
}
});
Ok(true)
}
}
let sh = ServerHandle {};
test_session(
Client {},
sh,
|c| async move {
let mut ch = c.channel_open_session().await.unwrap();
ch.data(&b"hello world!"[..]).await.unwrap();
let msg = ch.wait().await.unwrap();
if let ChannelMsg::Data { data } = msg {
assert_eq!(data.as_ref(), &b"hey there!"[..]);
} else {
panic!("Unexpected message {:?}", msg);
}
let msg = ch.wait().await.unwrap();
let ChannelMsg::Close = msg else {
panic!("Unexpected message {:?}", msg);
};
ch.close().await.unwrap();
c
},
|s| async move { s },
)
.await;
}
}

Binary file not shown.

View File

File diff suppressed because one or more lines are too long

Binary file not shown.

142
vendor/russh/tests/test_data_stream.rs vendored Normal file
View File

@@ -0,0 +1,142 @@
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::sync::Arc;
use rand::RngCore;
use russh::keys::key;
use russh::server::{self, Auth, Msg, Server as _, Session};
use russh::{client, Channel};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
pub const WINDOW_SIZE: u32 = 8 * 2048;
#[tokio::test]
async fn test_reader_and_writer() -> Result<(), anyhow::Error> {
env_logger::init();
let addr = addr();
let data = data();
tokio::spawn(Server::run(addr));
// Wait until the server is started
while TcpStream::connect(addr).is_err() {
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
}
stream(addr, &data).await?;
Ok(())
}
async fn stream(addr: SocketAddr, data: &[u8]) -> Result<(), anyhow::Error> {
let config = Arc::new(client::Config::default());
let key = Arc::new(russh_keys::key::KeyPair::generate_ed25519());
let mut session = russh::client::connect(config, addr, Client).await?;
let mut channel = match session.authenticate_publickey("user", key).await {
Ok(true) => session.channel_open_session().await?,
Ok(false) => panic!("Authentication failed"),
Err(err) => return Err(err.into()),
};
let mut buf = Vec::<u8>::new();
let (mut writer, mut reader) = (channel.make_writer_ext(Some(1)), channel.make_reader());
let (r0, r1) = tokio::join!(
async {
writer.write_all(data).await?;
writer.shutdown().await?;
Ok::<_, anyhow::Error>(())
},
reader.read_to_end(&mut buf)
);
r0?;
let count = r1?;
assert_eq!(data.len(), count);
assert_eq!(data, buf);
Ok(())
}
fn data() -> Vec<u8> {
let mut rng = rand::thread_rng();
let mut data = vec![0u8; WINDOW_SIZE as usize * 2 + 7]; // Check whether the window_size resizing works
rng.fill_bytes(&mut data);
data
}
/// Find a unused local address to bind our server to
fn addr() -> SocketAddr {
TcpListener::bind(("127.0.0.1", 0))
.unwrap()
.local_addr()
.unwrap()
}
#[derive(Clone)]
struct Server;
impl Server {
async fn run(addr: SocketAddr) {
let config = Arc::new(server::Config {
keys: vec![russh_keys::key::KeyPair::generate_ed25519()],
window_size: WINDOW_SIZE,
..Default::default()
});
let mut sh = Server {};
sh.run_on_address(config, addr).await.unwrap();
}
}
impl russh::server::Server for Server {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self::Handler {
self.clone()
}
}
#[async_trait::async_trait]
impl russh::server::Handler for Server {
type Error = anyhow::Error;
async fn auth_publickey(&mut self, _: &str, _: &key::PublicKey) -> Result<Auth, Self::Error> {
Ok(Auth::Accept)
}
async fn channel_open_session(
&mut self,
mut channel: Channel<Msg>,
_session: &mut Session,
) -> Result<bool, Self::Error> {
tokio::spawn(async move {
let (mut writer, mut reader) =
(channel.make_writer(), channel.make_reader_ext(Some(1)));
tokio::io::copy(&mut reader, &mut writer)
.await
.expect("Data transfer failed");
writer.shutdown().await.expect("Shutdown failed");
});
Ok(true)
}
}
struct Client;
#[async_trait::async_trait]
impl russh::client::Handler for Client {
type Error = anyhow::Error;
async fn check_server_key(&mut self, _: &key::PublicKey) -> Result<bool, Self::Error> {
Ok(true)
}
}