├── thrussh ├── src │ ├── compression.rs │ ├── tcp.rs │ ├── sshbuffer.rs │ ├── msg.rs │ ├── client │ │ ├── kex.rs │ │ ├── authenticate.rs │ │ ├── wait.rs │ │ ├── channel_open.rs │ │ ├── data.rs │ │ └── mod.rs │ ├── cipher │ │ ├── clear.rs │ │ ├── chacha20poly1305.rs │ │ └── mod.rs │ ├── key.rs │ ├── read_exact_from.rs │ ├── pty.rs │ ├── auth.rs │ ├── server │ │ ├── kex.rs │ │ ├── session.rs │ │ └── mod.rs │ ├── ssh_read.rs │ ├── encoding.rs │ ├── negotiation.rs │ ├── kex.rs │ └── session.rs └── Cargo.toml ├── .gitignore ├── thrussh-libsodium ├── Cargo.toml ├── build.rs └── src │ └── lib.rs ├── thrussh-keys ├── src │ ├── agent │ │ ├── mod.rs │ │ └── msg.rs │ ├── format │ │ ├── pkcs5.rs │ │ ├── openssh.rs │ │ └── mod.rs │ ├── signature.rs │ ├── bcrypt_pbkdf.rs │ ├── encoding.rs │ ├── pem.rs │ └── key.rs └── Cargo.toml ├── Cargo.toml ├── thrussh-agent ├── Cargo.toml └── src │ ├── thrussh-agent.rs │ └── thrussh-add.rs └── README.md /thrussh/src/compression.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pijul/ 2 | target/ 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /thrussh-libsodium/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thrussh-libsodium" 3 | version = "0.1.3" 4 | license = "Apache-2.0/MIT" 5 | description = "Straightforward bindings to libsodium" 6 | homepage = "https://nest.pijul.com/pijul_org/thrussh" 7 | repository = "https://nest.pijul.com/pijul_org/thrussh" 8 | authors = ["pe@pijul.org "] 9 | include = [ "build.rs", "Cargo.toml", "src/lib.rs" ] 10 | build = "build.rs" 11 | 12 | [dependencies] 13 | libc = "0.2" 14 | 15 | [build-dependencies] 16 | pkg-config = "0.3" 17 | -------------------------------------------------------------------------------- /thrussh-keys/src/agent/mod.rs: -------------------------------------------------------------------------------- 1 | mod msg; 2 | /// Write clients for SSH agents. 3 | pub mod client; 4 | /// Write servers for SSH agents. 5 | pub mod server; 6 | 7 | /// Constraints on how keys can be used 8 | #[derive(Debug)] 9 | pub enum Constraint { 10 | /// The key shall disappear from the agent's memory after that many seconds. 11 | KeyLifetime { seconds: u32 }, 12 | /// Signatures need to be confirmed by the agent (for instance using a dialog). 13 | Confirm, 14 | /// Custom constraints 15 | Extensions { name: Vec, details: Vec } 16 | } 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ "thrussh-keys", "thrussh", "thrussh-libsodium", "thrussh-agent" ] 4 | 5 | [replace] 6 | 7 | "thrussh-keys:0.9.5" = { path = "thrussh-keys" } 8 | "thrussh-libsodium:0.1.3" = { path = "thrussh-libsodium" } 9 | "net2:0.2.32" = { git = "https://gitlab.redox-os.org/redox-os/net2-rs" } 10 | "mio:0.6.14" = { git = "https://gitlab.redox-os.org/redox-os/mio" } 11 | 12 | #[patch.crates-io] 13 | #thrussh-keys = { path = "thrussh-keys" } 14 | #thrussh-libsodium = { path = "thrussh-libsodium" } 15 | #net2 = { git = "https://gitlab.redox-os.org/redox-os/net2-rs" } 16 | #mio = { git = "https://gitlab.redox-os.org/redox-os/mio" } 17 | -------------------------------------------------------------------------------- /thrussh-libsodium/build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | extern crate pkg_config; 3 | 4 | fn main() { 5 | 6 | println!("cargo:rerun-if-env-changed=SODIUM_LIB_DIR"); 7 | println!("cargo:rerun-if-env-changed=SODIUM_STATIC"); 8 | if let Ok(lib_dir) = env::var("SODIUM_LIB_DIR") { 9 | 10 | println!("cargo:rustc-link-search=native={}", lib_dir); 11 | 12 | let mode = match env::var_os("SODIUM_STATIC") { 13 | Some(_) => "static", 14 | None => "dylib", 15 | }; 16 | println!("cargo:rustc-link-lib={}=libsodium", mode); 17 | 18 | } else { 19 | 20 | pkg_config::find_library("libsodium").unwrap(); 21 | 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /thrussh-keys/src/agent/msg.rs: -------------------------------------------------------------------------------- 1 | pub const FAILURE: u8 = 5; 2 | pub const SUCCESS: u8 = 6; 3 | pub const IDENTITIES_ANSWER: u8 = 12; 4 | pub const SIGN_RESPONSE: u8 = 14; 5 | pub const EXTENSION_FAILURE: u8 = 28; 6 | 7 | pub const REQUEST_IDENTITIES:u8 = 11; 8 | pub const SIGN_REQUEST:u8 = 13; 9 | pub const ADD_IDENTITY:u8 = 17; 10 | pub const REMOVE_IDENTITY:u8 = 18; 11 | pub const REMOVE_ALL_IDENTITIES:u8 = 19; 12 | pub const ADD_ID_CONSTRAINED:u8 = 25; 13 | pub const ADD_SMARTCARD_KEY:u8 = 20; 14 | pub const REMOVE_SMARTCARD_KEY:u8 = 21; 15 | pub const LOCK:u8 = 22; 16 | pub const UNLOCK:u8 = 23; 17 | pub const ADD_SMARTCARD_KEY_CONSTRAINED:u8 = 26; 18 | pub const EXTENSION:u8 = 27; 19 | 20 | pub const CONSTRAIN_LIFETIME:u8 = 1; 21 | pub const CONSTRAIN_CONFIRM:u8 = 2; 22 | pub const CONSTRAIN_EXTENSION:u8 = 3; 23 | -------------------------------------------------------------------------------- /thrussh-agent/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thrussh-agent" 3 | version = "0.1.0" 4 | authors = ["pe@pijul.org "] 5 | license = "Apache-2.0/MIT" 6 | description = "An SSH agent, including a server (thrussh-agent) and a client (thrussh-add)." 7 | repository = "https://nest.pijul.com/pijul_org/thrussh" 8 | include = [ "Cargo.toml", "src/thrussh-agent.rs", "src/thrussh-add.rs" ] 9 | 10 | [dependencies] 11 | #thrussh-keys = "0.8.1" 12 | thrussh-keys = { path = "../thrussh-keys" } 13 | futures = "0.1" 14 | tokio-uds = { git = "https://github.com/redox-os/tokio" } 15 | tokio-core = { git = "https://github.com/redox-os/tokio-core" } 16 | clap = "2.28" 17 | rand = "0.3" 18 | libc = "0.2" 19 | termion = "1.5" 20 | 21 | [[bin]] 22 | name = "thrussh-agent" 23 | path = "src/thrussh-agent.rs" 24 | 25 | [[bin]] 26 | name = "thrussh-add" 27 | path = "src/thrussh-add.rs" 28 | -------------------------------------------------------------------------------- /thrussh/src/tcp.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use tokio::net::TcpStream; 3 | 4 | /// Types that have a "TCP shutdown" operation. 5 | pub trait Tcp { 6 | /// Shutdown the TCP connection cleanly. 7 | fn tcp_shutdown(&mut self) -> Result<(), std::io::Error> { 8 | Ok(()) 9 | } 10 | } 11 | 12 | impl Tcp for TcpStream { 13 | fn tcp_shutdown(&mut self) -> Result<(), std::io::Error> { 14 | debug!("tcp shutdown for tcpstream"); 15 | self.shutdown(std::net::Shutdown::Both) 16 | } 17 | } 18 | 19 | impl Tcp for Box { 20 | fn tcp_shutdown(&mut self) -> Result<(), std::io::Error> { 21 | self.as_mut().tcp_shutdown() 22 | } 23 | } 24 | impl<'a, T: ?Sized + Tcp> Tcp for &'a mut T { 25 | fn tcp_shutdown(&mut self) -> Result<(), std::io::Error> { 26 | (*self).tcp_shutdown() 27 | } 28 | } 29 | impl<'a> Tcp for std::io::Cursor<&'a mut [u8]> {} 30 | impl Tcp for std::io::Cursor> {} 31 | impl Tcp for std::io::Cursor> {} 32 | -------------------------------------------------------------------------------- /thrussh-keys/src/format/pkcs5.rs: -------------------------------------------------------------------------------- 1 | use {Error, ErrorKind}; 2 | use std; 3 | use key; 4 | use super::{Encryption, pkcs_unpad, decode_rsa}; 5 | 6 | use openssl::symm::{decrypt, Cipher}; 7 | use openssl::hash::{MessageDigest, Hasher}; 8 | 9 | /// Decode a secret key in the PKCS#5 format, possible deciphering it 10 | /// using the supplied password. 11 | pub fn decode_pkcs5( 12 | secret: &[u8], 13 | password: Option<&[u8]>, 14 | enc: Encryption, 15 | ) -> Result { 16 | if let Some(pass) = password { 17 | let sec = match enc { 18 | Encryption::Aes128Cbc(ref iv) => { 19 | let mut h = Hasher::new(MessageDigest::md5()).unwrap(); 20 | h.update(pass).unwrap(); 21 | h.update(&iv[..8]).unwrap(); 22 | let md5 = h.finish().unwrap(); 23 | 24 | let mut secret = secret.to_vec(); 25 | let len = 32 - (secret.len() % 32); 26 | secret.extend(std::iter::repeat(len as u8).take(len)); 27 | let mut dec = decrypt( 28 | Cipher::aes_128_cbc(), 29 | &md5, 30 | Some(&iv[..]), 31 | &secret 32 | )?; 33 | pkcs_unpad(&mut dec); 34 | dec 35 | } 36 | Encryption::Aes256Cbc(_) => unimplemented!(), 37 | }; 38 | decode_rsa(&sec) 39 | } else { 40 | Err(ErrorKind::KeyIsEncrypted.into()) 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /thrussh-keys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thrussh-keys" 3 | version = "0.9.5" 4 | authors = ["Pierre-Étienne Meunier "] 5 | description = "Deal with SSH keys: load them, decrypt them, call an SSH agent." 6 | keywords = ["ssh"] 7 | repository = "https://nest.pijul.com/pijul_org/thrussh" 8 | homepage = "https://pijul.org/thrussh" 9 | documentation = "https://docs.rs/thrussh-keys" 10 | license = "Apache-2.0" 11 | include = [ 12 | "Cargo.toml", 13 | "src/lib.rs", 14 | "src/pem.rs", 15 | "src/agent/mod.rs", 16 | "src/agent/msg.rs", 17 | "src/agent/server.rs", 18 | "src/agent/client.rs", 19 | "src/bcrypt_pbkdf.rs", 20 | "src/blowfish.rs", 21 | "src/encoding.rs", 22 | "src/format/mod.rs", 23 | "src/format/openssh.rs", 24 | "src/format/pkcs5.rs", 25 | "src/format/pkcs8.rs", 26 | "src/key.rs", 27 | "src/signature.rs" 28 | ] 29 | 30 | [dependencies] 31 | base64 = "0.8" 32 | byteorder = "1.2" 33 | tokio-core = { git = "https://github.com/redox-os/tokio-core" } 34 | tokio-io = { git = "https://github.com/redox-os/tokio" } 35 | futures = "0.1" 36 | #cryptovec = "0.4" 37 | cryptovec = { git = "https://github.com/redox-os/cryptovec" } 38 | error-chain = "0.11" 39 | hex = "0.3" 40 | yasna = "0.1" 41 | num-bigint = { version = "0.1", default-features = false } 42 | num-integer = { version = "0.1", default-features = false } 43 | openssl = "0.10" 44 | bit-vec = "0.4" 45 | thrussh-libsodium = "0.1" 46 | serde_derive = "1.0" 47 | serde = "1.0" 48 | 49 | [dev-dependencies] 50 | env_logger = "0.3" 51 | tempdir="0.3" 52 | tokio-uds = { git = "https://github.com/redox-os/tokio" } 53 | -------------------------------------------------------------------------------- /thrussh/src/sshbuffer.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | use super::*; 17 | use std::num::Wrapping; 18 | use tokio::io::{WriteAll, write_all}; 19 | use tokio::io::AsyncWrite; 20 | 21 | #[derive(Debug)] 22 | pub struct SSHBuffer { 23 | pub buffer: CryptoVec, 24 | pub len: usize, // next packet length. 25 | pub bytes: usize, 26 | 27 | // Sequence numbers are on 32 bits and wrap. 28 | // https://tools.ietf.org/html/rfc4253#section-6.4 29 | pub seqn: Wrapping, 30 | } 31 | 32 | impl SSHBuffer { 33 | pub fn new() -> Self { 34 | SSHBuffer { 35 | buffer: CryptoVec::new(), 36 | len: 0, 37 | bytes: 0, 38 | seqn: Wrapping(0), 39 | } 40 | } 41 | 42 | pub fn len(&self) -> usize { 43 | self.buffer.len() - self.len 44 | } 45 | 46 | pub fn send_ssh_id(&mut self, id: &[u8]) { 47 | self.buffer.extend(id); 48 | self.buffer.push(b'\r'); 49 | self.buffer.push(b'\n'); 50 | } 51 | 52 | pub fn write_all(&mut self, stream: W) -> WriteAll { 53 | debug!("write_all: {:?}", self.buffer.as_ref()); 54 | write_all( 55 | stream, 56 | std::mem::replace(&mut self.buffer, CryptoVec::new()), 57 | ) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /thrussh/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "thrussh" 3 | description = "A client and server SSH library." 4 | keywords = ["ssh"] 5 | version = "0.20.0" 6 | authors = ["Pierre-Étienne Meunier "] 7 | repository = "https://nest.pijul.com/pijul_org/thrussh" 8 | homepage = "https://pijul.org/thrussh" 9 | documentation = "https://docs.rs/thrussh" 10 | license = "Apache-2.0" 11 | include = [ 12 | "Cargo.toml", 13 | "src/auth.rs", 14 | "src/kex.rs", 15 | "src/key.rs", 16 | "src/lib.rs", 17 | "src/msg.rs", 18 | "src/negotiation.rs", 19 | "src/pty.rs", 20 | "src/session.rs", 21 | "src/sshbuffer.rs", 22 | "src/ssh_read.rs", 23 | "src/tcp.rs", 24 | "src/bin/client.rs", 25 | "src/bin/server.rs", 26 | "src/cipher/chacha20poly1305.rs", 27 | "src/cipher/clear.rs", 28 | "src/cipher/mod.rs", 29 | "src/client/mod.rs", 30 | "src/client/encrypted.rs", 31 | "src/client/authenticate.rs", 32 | "src/client/channel_open.rs", 33 | "src/client/connection.rs", 34 | "src/client/data.rs", 35 | "src/client/kex.rs", 36 | "src/client/session.rs", 37 | "src/client/wait.rs", 38 | "src/server/mod.rs", 39 | "src/server/encrypted.rs", 40 | "src/server/connection.rs", 41 | "src/server/kex.rs", 42 | "src/server/session.rs", 43 | "src/sodium.rs", 44 | "src/read_exact_from.rs" 45 | ] 46 | 47 | [dependencies] 48 | byteorder = "1.1" 49 | bitflags = "1.0" 50 | log = "0.3" 51 | #thrussh-keys = "0.9.5" 52 | thrussh-keys = { path = "../thrussh-keys" } 53 | openssl = "0.10" 54 | thrussh-libsodium = "0.1" 55 | #cryptovec = "0.4.1" 56 | cryptovec = { git = "https://github.com/redox-os/cryptovec" } 57 | tokio = { git = "https://github.com/redox-os/tokio" } 58 | tokio-io = { git = "https://github.com/redox-os/tokio" } 59 | futures = "0.1" 60 | tokio-timer = { git = "https://github.com/redox-os/tokio" } 61 | 62 | [dev-dependencies] 63 | env_logger = "0.5" 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thrussh 2 | 3 | A full implementation of the SSH 2 protocol, both server-side and client-side. 4 | 5 | Thrussh is completely asynchronous, and can be combined with other protocols using [Tokio](//tokio.rs). 6 | 7 | ## Contributing 8 | 9 | We welcome contributions. Currently, the main areas where we need help are: 10 | 11 | - Handling SSH keys correctly on all platforms. In particular, interactions with agents, PGP, and password-protected/encrypted keys are not yet implemented. 12 | 13 | - Auditing our code, and writing tests. The code is written so that the protocol can be entirely run inside vectors (instead of network sockets). 14 | 15 | By contributing, you agree to license all your contributions under the Apache 2.0 license. 16 | 17 | Moreover, the main platform for contributing is [the Nest](//nest.pijul.com/pijul_org/thrussh), which is still at an experimental stage. Therefore, even though we do our best to avoid it, our repository might be reset, causing the patches of all contributors to be merged. 18 | 19 | ## Issue Reporting 20 | 21 | Please report bugs on the [issue page of this repository](//nest.pijul.com/pijul_org/thrussh). 22 | Thrussh has a full disclosure vulnerability policy. 23 | Please do NOT attempt to report any security vulnerability in this code privately to anybody. 24 | 25 | # Redox Porting Notes 26 | Please add to this section with anything you'd like others to know if you contribute to this port. 27 | 28 | - All deps have been taken care of. In order to compile for redox, openssl and libsodium must be set up on your system for redox building. I really have no idea how this works, there is a cookbook recipe for libsodium that works fine (I think), so clone this repo into a cookbook recipe `source/` and add this to your recipe.sh: 29 | ```sh 30 | BUILD_DEPENDS=(openssl libsodium) 31 | ``` 32 | - There are four crates in this repository, 3 of them build under redox: `thrussh`, `thrussh-keys`, and `thrussh-libsodium`. The fourth, `thrussh-agent`, depends on Unix specific features (sockets) that are not yet implemented to an adequate degree on Redox. It also requires two functions to be added to liblibc, and probably has a dependency issue. Kudos if anybody fixes this. 33 | -------------------------------------------------------------------------------- /thrussh/src/msg.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | // https://tools.ietf.org/html/rfc4253#section-12 16 | pub const DISCONNECT: u8 = 1; 17 | #[allow(dead_code)] 18 | pub const IGNORE: u8 = 2; 19 | #[allow(dead_code)] 20 | pub const UNIMPLEMENTED: u8 = 3; 21 | #[allow(dead_code)] 22 | pub const DEBUG: u8 = 4; 23 | 24 | pub const SERVICE_REQUEST: u8 = 5; 25 | pub const SERVICE_ACCEPT: u8 = 6; 26 | pub const KEXINIT: u8 = 20; 27 | pub const NEWKEYS: u8 = 21; 28 | 29 | 30 | // http://tools.ietf.org/html/rfc5656#section-7.1 31 | pub const KEX_ECDH_INIT: u8 = 30; 32 | pub const KEX_ECDH_REPLY: u8 = 31; 33 | 34 | // https://tools.ietf.org/html/rfc4250#section-4.1.2 35 | pub const USERAUTH_REQUEST: u8 = 50; 36 | pub const USERAUTH_FAILURE: u8 = 51; 37 | pub const USERAUTH_SUCCESS: u8 = 52; 38 | pub const USERAUTH_BANNER: u8 = 53; 39 | pub const USERAUTH_PK_OK: u8 = 60; 40 | 41 | // https://tools.ietf.org/html/rfc4256#section-5 42 | pub const USERAUTH_INFO_REQUEST: u8 = 60; 43 | pub const USERAUTH_INFO_RESPONSE: u8 = 61; 44 | 45 | 46 | // https://tools.ietf.org/html/rfc4254#section-9 47 | pub const GLOBAL_REQUEST: u8 = 80; 48 | pub const REQUEST_SUCCESS: u8 = 81; 49 | pub const REQUEST_FAILURE: u8 = 82; 50 | 51 | pub const CHANNEL_OPEN: u8 = 90; 52 | pub const CHANNEL_OPEN_CONFIRMATION: u8 = 91; 53 | pub const CHANNEL_OPEN_FAILURE: u8 = 92; 54 | pub const CHANNEL_WINDOW_ADJUST: u8 = 93; 55 | pub const CHANNEL_DATA: u8 = 94; 56 | pub const CHANNEL_EXTENDED_DATA: u8 = 95; 57 | pub const CHANNEL_EOF: u8 = 96; 58 | pub const CHANNEL_CLOSE: u8 = 97; 59 | pub const CHANNEL_REQUEST: u8 = 98; 60 | pub const CHANNEL_SUCCESS: u8 = 99; 61 | pub const CHANNEL_FAILURE: u8 = 100; 62 | -------------------------------------------------------------------------------- /thrussh-agent/src/thrussh-agent.rs: -------------------------------------------------------------------------------- 1 | extern crate thrussh_keys; 2 | extern crate futures; 3 | extern crate tokio_uds; 4 | extern crate tokio_core; 5 | extern crate rand; 6 | extern crate clap; 7 | extern crate libc; 8 | 9 | use thrussh_keys::agent; 10 | use std::path::Path; 11 | use rand::Rng; 12 | 13 | fn main() { 14 | let matches = clap::App::new("Thrussh-agent") 15 | .version("0.1") 16 | .author("Pierre-Étienne Meunier ") 17 | .arg(clap::Arg::with_name("address") 18 | .short("a") 19 | .takes_value(true) 20 | .help("Bind the agent to that address")) 21 | .arg(clap::Arg::with_name("foreground") 22 | .short("D") 23 | .help("Foreground mode")) 24 | .get_matches(); 25 | 26 | let mut rng = rand::thread_rng(); 27 | let agent_path = { 28 | if let Some(addr) = matches.value_of("address") { 29 | std::path::Path::new(addr).to_path_buf() 30 | } else if let Ok(tmp) = std::env::var("TMPDIR") { 31 | let file: String = "thrussh-".chars().chain(rng.gen_ascii_chars().take(10)).collect(); 32 | let mut path = Path::new(&tmp).join(&file); 33 | path.push("agent.ppid"); 34 | path 35 | } else { 36 | eprintln!("No $TMPDIR, and no address was given"); 37 | std::process::exit(1) 38 | } 39 | }; 40 | 41 | if let Some(parent) = agent_path.parent() { 42 | std::fs::create_dir_all(parent).unwrap() 43 | } 44 | 45 | let foreground = matches.is_present("foreground"); 46 | 47 | let pid = if foreground { 48 | unsafe { libc::getpid() } 49 | } else { 50 | unsafe { libc::fork() } 51 | }; 52 | 53 | if pid > 0 || foreground { 54 | 55 | println!("SSH_AUTH_SOCK={:?}; export SSH_AUTH_SOCK;\nSSH_AGENT_ID={}; export SSH_AGENT_ID; echo Agent pid {}", agent_path, pid, pid); 56 | 57 | } 58 | 59 | if pid == 0 || foreground { 60 | let mut core = tokio_core::reactor::Core::new().unwrap(); 61 | let h = core.handle(); 62 | let listener = tokio_uds::UnixListener::bind(&agent_path, &h).unwrap().incoming(); 63 | core.run(agent::server::AgentServer::new(listener, h, ())).unwrap(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /thrussh/src/client/kex.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use negotiation::Select; 3 | use cipher::CipherPair; 4 | use negotiation; 5 | 6 | use kex; 7 | 8 | impl KexInit { 9 | pub fn client_parse( 10 | mut self, 11 | config: &Config, 12 | cipher: &CipherPair, 13 | buf: &[u8], 14 | write_buffer: &mut SSHBuffer, 15 | ) -> Result { 16 | debug!("client parse"); 17 | let algo = if self.algo.is_none() { 18 | // read algorithms from packet. 19 | self.exchange.server_kex_init.extend(buf); 20 | super::negotiation::Client::read_kex(buf, &config.preferred)? 21 | } else { 22 | return Err(Error::Kex); 23 | }; 24 | if !self.sent { 25 | self.client_write(config, cipher, write_buffer)? 26 | } 27 | 28 | // This function is called from the public API. 29 | // 30 | // In order to simplify the public API, we reuse the 31 | // self.exchange.client_kex buffer to send an extra packet, 32 | // then truncate that buffer. Without that, we would need an 33 | // extra buffer. 34 | let i0 = self.exchange.client_kex_init.len(); 35 | let kex = kex::Algorithm::client_dh( 36 | algo.kex, 37 | &mut self.exchange.client_ephemeral, 38 | &mut self.exchange.client_kex_init, 39 | )?; 40 | 41 | cipher.write(&self.exchange.client_kex_init[i0..], write_buffer); 42 | self.exchange.client_kex_init.resize(i0); 43 | 44 | debug!("moving to kexdhdone"); 45 | Ok(KexDhDone { 46 | exchange: self.exchange, 47 | names: algo, 48 | kex: kex, 49 | key: 0, 50 | session_id: self.session_id, 51 | }) 52 | } 53 | 54 | pub fn client_write( 55 | &mut self, 56 | config: &Config, 57 | cipher: &CipherPair, 58 | write_buffer: &mut SSHBuffer, 59 | ) -> Result<(), Error> { 60 | self.exchange.client_kex_init.clear(); 61 | negotiation::write_kex( 62 | &config.preferred, 63 | &mut self.exchange.client_kex_init, 64 | )?; 65 | self.sent = true; 66 | cipher.write(&self.exchange.client_kex_init, write_buffer); 67 | Ok(()) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /thrussh/src/cipher/clear.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | use Error; 17 | 18 | #[derive(Debug)] 19 | pub struct Key; 20 | 21 | impl super::OpeningKey for Key { 22 | fn decrypt_packet_length(&self, _seqn: u32, packet_length: [u8; 4]) -> [u8; 4] { 23 | packet_length 24 | } 25 | 26 | fn tag_len(&self) -> usize { 27 | 0 28 | } 29 | 30 | fn open<'a>( 31 | &self, 32 | _seqn: u32, 33 | ciphertext_in_plaintext_out: &'a mut [u8], 34 | tag: &[u8], 35 | ) -> Result<&'a [u8], Error> { 36 | 37 | debug_assert_eq!(tag.len(), 0); // self.tag_len()); 38 | Ok(&ciphertext_in_plaintext_out[4..]) 39 | } 40 | } 41 | 42 | impl super::SealingKey for Key { 43 | // Cleartext packets (including lengths) must be multiple of 8 in 44 | // length. 45 | fn padding_length(&self, payload: &[u8]) -> usize { 46 | let block_size = 8; 47 | let padding_len = block_size - ((5 + payload.len()) % block_size); 48 | if padding_len < 4 { 49 | padding_len + block_size 50 | } else { 51 | padding_len 52 | } 53 | } 54 | 55 | fn fill_padding(&self, padding_out: &mut [u8]) { 56 | // Since the packet is unencrypted anyway, there's no advantage to 57 | // randomizing the padding, so avoid possibly leaking extra RNG state 58 | // by padding with zeros. 59 | for padding_byte in padding_out { 60 | *padding_byte = 0; 61 | } 62 | } 63 | 64 | fn tag_len(&self) -> usize { 65 | 0 66 | } 67 | 68 | fn seal(&self, _seqn: u32, _plaintext_in_ciphertext_out: &mut [u8], tag_out: &mut [u8]) { 69 | debug_assert_eq!(tag_out.len(), self.tag_len()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /thrussh-keys/src/signature.rs: -------------------------------------------------------------------------------- 1 | use serde::{Serialize, Deserialize, Deserializer, Serializer}; 2 | use serde::de::{SeqAccess, Visitor}; 3 | use serde::ser::SerializeTuple; 4 | use serde; 5 | use std::fmt; 6 | pub struct SignatureBytes(pub [u8;64]); 7 | 8 | 9 | /// The type of a signature, depending on the algorithm used. 10 | #[derive(Serialize, Deserialize)] 11 | pub enum Signature { 12 | /// An Ed25519 signature 13 | Ed25519(SignatureBytes), 14 | /// An RSA signature 15 | RSA(Vec), 16 | } 17 | 18 | impl AsRef<[u8]> for Signature { 19 | fn as_ref(&self) -> &[u8] { 20 | match *self { 21 | Signature::Ed25519(ref signature) => &signature.0, 22 | Signature::RSA(ref signature) => &signature 23 | } 24 | } 25 | } 26 | 27 | impl AsRef<[u8]> for SignatureBytes { 28 | fn as_ref(&self) -> &[u8] { 29 | &self.0 30 | } 31 | } 32 | 33 | impl<'de> Deserialize<'de> for SignatureBytes { 34 | fn deserialize(deserializer: D) -> Result 35 | where D: Deserializer<'de> 36 | { 37 | struct Vis; 38 | impl<'de> Visitor<'de> for Vis { 39 | type Value = SignatureBytes; 40 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 41 | formatter.write_str("64 bytes") 42 | } 43 | fn visit_seq>(self, mut seq: A) -> Result { 44 | let mut result = [0; 64]; 45 | for x in result.iter_mut() { 46 | if let Some(y) = seq.next_element()? { 47 | *x = y 48 | } else { 49 | return Err(serde::de::Error::invalid_length(64, &self)) 50 | } 51 | } 52 | Ok(SignatureBytes(result)) 53 | } 54 | } 55 | deserializer.deserialize_tuple(64, Vis) 56 | } 57 | } 58 | 59 | impl Serialize for SignatureBytes { 60 | fn serialize(&self, serializer: S) -> Result 61 | where S: Serializer 62 | { 63 | let mut tup = serializer.serialize_tuple(64)?; 64 | for byte in self.0.iter() { 65 | tup.serialize_element(byte)?; 66 | } 67 | tup.end() 68 | } 69 | } 70 | 71 | impl fmt::Debug for SignatureBytes { 72 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 73 | write!(fmt, "{:?}", &self.0[..]) 74 | } 75 | } 76 | 77 | impl Clone for SignatureBytes { 78 | fn clone(&self) -> Self { 79 | let mut result = SignatureBytes([0;64]); 80 | result.0.clone_from_slice(&self.0); 81 | result 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /thrussh/src/key.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | use thrussh_keys::encoding::*; 16 | use thrussh_keys::key::*; 17 | use cryptovec::CryptoVec; 18 | 19 | #[doc(hidden)] 20 | pub trait PubKey { 21 | fn push_to(&self, buffer: &mut CryptoVec); 22 | } 23 | 24 | impl PubKey for PublicKey { 25 | fn push_to(&self, buffer: &mut CryptoVec) { 26 | match self { 27 | &PublicKey::Ed25519(ref public) => { 28 | 29 | buffer.push_u32_be((ED25519.0.len() + public.key.len() + 8) as u32); 30 | buffer.extend_ssh_string(ED25519.0.as_bytes()); 31 | buffer.extend_ssh_string(&public.key); 32 | } 33 | &PublicKey::RSA { ref key, .. } => { 34 | let rsa = key.0.rsa().unwrap(); 35 | let e = rsa.e().to_vec(); 36 | let n = rsa.n().to_vec(); 37 | buffer.push_u32_be( 38 | (4 + SSH_RSA.len() + mpint_len(&n) + mpint_len(&e)) as u32, 39 | ); 40 | buffer.extend_ssh_string(SSH_RSA.as_bytes()); 41 | buffer.extend_ssh_mpint(&e); 42 | buffer.extend_ssh_mpint(&n); 43 | } 44 | } 45 | } 46 | } 47 | 48 | impl PubKey for KeyPair { 49 | fn push_to(&self, buffer: &mut CryptoVec) { 50 | match self { 51 | &KeyPair::Ed25519(ref key) => { 52 | let public = &key.key[32..]; 53 | buffer.push_u32_be((ED25519.0.len() + public.len() + 8) as u32); 54 | buffer.extend_ssh_string(ED25519.0.as_bytes()); 55 | buffer.extend_ssh_string(public); 56 | } 57 | &KeyPair::RSA { ref key, ..} => { 58 | let e = key.e().to_vec(); 59 | let n = key.n().to_vec(); 60 | buffer.push_u32_be( 61 | (4 + SSH_RSA.len() + mpint_len(&n) + mpint_len(&e)) as u32, 62 | ); 63 | buffer.extend_ssh_string(SSH_RSA.as_bytes()); 64 | buffer.extend_ssh_mpint(&e); 65 | buffer.extend_ssh_mpint(&n); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /thrussh/src/read_exact_from.rs: -------------------------------------------------------------------------------- 1 | use tokio::io::AsyncRead; 2 | use futures::{Future, Poll, Async}; 3 | use std::io; 4 | use std::mem; 5 | use std::io::ErrorKind; 6 | 7 | #[derive(Debug)] 8 | pub struct ReadExact { 9 | state: State, 10 | } 11 | 12 | #[derive(Debug)] 13 | enum State { 14 | Reading { 15 | a: A, 16 | buf: T, 17 | pos: usize, 18 | initial_pos: usize, 19 | }, 20 | Empty, 21 | } 22 | 23 | pub fn read_exact_from(a: A, buf: T, pos: usize) -> ReadExact 24 | where 25 | A: AsyncRead, 26 | T: AsMut<[u8]>, 27 | { 28 | ReadExact { 29 | state: State::Reading { 30 | a, 31 | buf, 32 | pos, 33 | initial_pos: pos, 34 | }, 35 | } 36 | } 37 | 38 | fn eof() -> io::Error { 39 | io::Error::new(io::ErrorKind::UnexpectedEof, "early eof") 40 | } 41 | 42 | impl Future for ReadExact 43 | where 44 | A: AsyncRead, 45 | T: AsMut<[u8]>, 46 | { 47 | type Item = (A, T); 48 | type Error = io::Error; 49 | 50 | fn poll(&mut self) -> Poll<(A, T), io::Error> { 51 | match self.state { 52 | State::Reading { 53 | ref mut a, 54 | ref mut buf, 55 | ref mut pos, 56 | .. 57 | } => { 58 | let buf = buf.as_mut(); 59 | while *pos < buf.len() { 60 | let n = match a.read(&mut buf[*pos..]) { 61 | Ok(n) => n, 62 | Err(ref e) if e.kind() == ErrorKind::WouldBlock => return Ok(Async::NotReady), 63 | Err(e) => return Err(e) 64 | }; 65 | *pos += n; 66 | if n == 0 { 67 | return Err(eof()); 68 | } 69 | } 70 | } 71 | State::Empty => panic!("poll a ReadExact after it's done"), 72 | } 73 | 74 | match mem::replace(&mut self.state, State::Empty) { 75 | State::Reading { a, buf, .. } => Ok((a, buf).into()), 76 | State::Empty => panic!(), 77 | } 78 | } 79 | } 80 | 81 | impl ReadExact { 82 | pub fn try_abort(&mut self) -> Option<(A, T)> { 83 | if let State::Reading { 84 | a, 85 | buf, 86 | pos, 87 | initial_pos, 88 | } = mem::replace(&mut self.state, State::Empty) 89 | { 90 | if pos == initial_pos { 91 | Some((a, buf)) 92 | } else { 93 | None 94 | } 95 | } else { 96 | None 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /thrussh-keys/src/bcrypt_pbkdf.rs: -------------------------------------------------------------------------------- 1 | // From the rust-crypto project. 2 | 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use byteorder::{BigEndian, LittleEndian, ByteOrder}; 10 | use openssl::sha::Sha512; 11 | use blowfish::*; 12 | 13 | fn bcrypt_hash(hpass: &[u8], hsalt: &[u8], output: &mut [u8; 32]) { 14 | let mut bf = Blowfish::init_state(); 15 | bf.salted_expand_key(hsalt, hpass); 16 | 17 | for _ in 0..64 { 18 | bf.expand_key(hsalt); 19 | bf.expand_key(hpass); 20 | } 21 | 22 | // b"OxychromaticBlowfishSwatDynamite" 23 | let mut buf:[u32; 8] = [1333295459, 1752330093, 1635019107, 1114402679, 1718186856, 1400332660, 1148808801, 1835627621]; 24 | let mut i = 0; 25 | while i < 8 { 26 | for _ in 0..64 { 27 | let (l, r) = bf.encrypt(buf[i], buf[i+1]); 28 | buf[i] = l; 29 | buf[i+1] = r; 30 | } 31 | i += 2 32 | } 33 | 34 | for i in 0..8 { 35 | LittleEndian::write_u32(&mut output[i*4..(i+1)*4], buf[i]); 36 | } 37 | } 38 | 39 | pub fn bcrypt_pbkdf(password: &[u8], salt: &[u8], rounds: u32, output: &mut [u8]) { 40 | 41 | assert!(password.len() > 0); 42 | assert!(salt.len() > 0); 43 | assert!(rounds > 0); 44 | assert!(output.len() > 0); 45 | assert!(output.len() <= 1024); 46 | 47 | let nblocks = (output.len() + 31) / 32; 48 | 49 | let hpass = { 50 | let mut hasher = Sha512::new(); 51 | hasher.update(password); 52 | hasher.finish() 53 | }; 54 | 55 | for block in 1..(nblocks+1) { 56 | let mut count = [0u8; 4]; 57 | let mut out = [0u8; 32]; 58 | BigEndian::write_u32(&mut count, block as u32); 59 | 60 | let mut hasher = Sha512::new(); 61 | hasher.update(salt); 62 | hasher.update(&count); 63 | let hsalt = hasher.finish(); 64 | 65 | bcrypt_hash(hpass.as_ref(), hsalt.as_ref(), &mut out); 66 | let mut tmp = out; 67 | 68 | for _ in 1..rounds { 69 | 70 | let mut hasher = Sha512::new(); 71 | hasher.update(&tmp); 72 | let hsalt = hasher.finish(); 73 | 74 | bcrypt_hash(hpass.as_ref(), hsalt.as_ref(), &mut tmp); 75 | for i in 0..out.len() { 76 | out[i] ^= tmp[i]; 77 | } 78 | 79 | for i in 0..out.len() { 80 | let idx = i * nblocks + (block-1); 81 | if idx < output.len() { 82 | output[idx] = out[i]; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /thrussh-agent/src/thrussh-add.rs: -------------------------------------------------------------------------------- 1 | extern crate thrussh_keys; 2 | extern crate futures; 3 | extern crate tokio_uds; 4 | extern crate tokio_core; 5 | extern crate clap; 6 | extern crate termion; 7 | 8 | use futures::{Future, Stream}; 9 | use futures::future::Either; 10 | use thrussh_keys::agent; 11 | use std::path::Path; 12 | use std::io::Read; 13 | use std::fs::File; 14 | 15 | fn main() { 16 | let matches = clap::App::new("Thrussh-add") 17 | .version("0.1") 18 | .author("Pierre-Étienne Meunier ") 19 | .arg(clap::Arg::with_name("address") 20 | .short("a") 21 | .takes_value(true) 22 | .help("Bind the agent to that address")) 23 | .arg(clap::Arg::with_name("file") 24 | .takes_value(true) 25 | .multiple(true) 26 | .help("Secret key files to add")) 27 | .get_matches(); 28 | 29 | let agent_path = { 30 | if let Some(addr) = matches.value_of("address") { 31 | std::path::Path::new(addr).to_path_buf() 32 | } else if let Ok(tmp) = std::env::var("SSH_AUTH_SOCK") { 33 | Path::new(&tmp).to_path_buf() 34 | } else { 35 | eprintln!("No $TMPDIR, and no address was given"); 36 | std::process::exit(1) 37 | } 38 | }; 39 | 40 | if let Some(files) = matches.values_of("file") { 41 | let mut core = tokio_core::reactor::Core::new().unwrap(); 42 | let h = core.handle(); 43 | let stream = tokio_uds::UnixStream::connect(&agent_path, &h).unwrap(); 44 | let client = agent::client::AgentClient::connect(stream); 45 | 46 | core.run( 47 | futures::stream::iter_ok::<_, thrussh_keys::Error>(files) 48 | .fold(client, |client, s| { 49 | let mut f = File::open(s).unwrap(); 50 | let mut key = String::new(); 51 | f.read_to_string(&mut key).unwrap(); 52 | let key = match thrussh_keys::decode_secret_key(&key, None) { 53 | Ok(key) => Ok(key), 54 | Err(_) => { 55 | let password = password().unwrap(); 56 | thrussh_keys::decode_secret_key(&key, Some(password.as_bytes())) 57 | } 58 | }; 59 | match key { 60 | Ok(key) => Either::A(client.add_identity(&key, &[]).map(move |(client, success)| { 61 | if !success { 62 | eprintln!("failed to add {:?}", s); 63 | } 64 | client 65 | })), 66 | Err(e) => { 67 | eprintln!("Could not open key file: {:?}", e); 68 | Either::B(futures::finished(client)) 69 | } 70 | } 71 | }) 72 | ).unwrap(); 73 | } 74 | } 75 | 76 | fn password() -> Result { 77 | print!("Password: "); 78 | use std::io::{stdin, stdout}; 79 | use termion::input::TermRead; 80 | let stdout = stdout(); 81 | let mut stdout = stdout.lock(); 82 | let stdin = stdin(); 83 | let mut stdin = stdin.lock(); 84 | if let Some(pass) = stdin.read_passwd(&mut stdout)? { 85 | return Ok(pass) 86 | } else { 87 | return Ok(String::new()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /thrussh/src/client/authenticate.rs: -------------------------------------------------------------------------------- 1 | use super::connection::Connection; 2 | use tokio::io::{AsyncRead, AsyncWrite}; 3 | use super::Handler; 4 | use {HandlerError, Status, AtomicPoll}; 5 | use futures::{Poll, Async, Future}; 6 | use thrussh_keys::key; 7 | use std::sync::Arc; 8 | use tcp::Tcp; 9 | use auth; 10 | 11 | impl Connection { 12 | 13 | /// Try to authenticate this client using a password. 14 | pub fn authenticate_password(mut self, user: &str, password: String) -> Authenticate { 15 | let is_waiting = if let Some(ref mut s) = self.session { 16 | let meth = auth::Method::Password { password }; 17 | s.write_auth_request_if_needed(user, meth) 18 | } else { false }; 19 | if is_waiting { 20 | self.abort_read().unwrap_or(()); 21 | } 22 | Authenticate(Some(self)) 23 | } 24 | 25 | /// Try to authenticate this client using a key pair. 26 | pub fn authenticate_key(mut self, user: &str, key: Arc) -> Authenticate { 27 | let is_waiting = if let Some(ref mut s) = self.session { 28 | let meth = auth::Method::PublicKey { key }; 29 | s.write_auth_request_if_needed(user, meth) 30 | } else { false }; 31 | if is_waiting { 32 | self.abort_read().unwrap_or(()); 33 | } 34 | Authenticate(Some(self)) 35 | } 36 | 37 | /// Try to authenticate this client using a key pair. 38 | pub fn authenticate_key_future( 39 | mut self, 40 | user: &str, 41 | key: key::PublicKey, 42 | ) -> Authenticate 43 | { 44 | let is_waiting = if let Some(ref mut s) = self.session { 45 | let meth = auth::Method::FuturePublicKey { key }; 46 | s.write_auth_request_if_needed(user, meth) 47 | } else { false }; 48 | if is_waiting { 49 | self.abort_read().unwrap_or(()); 50 | } 51 | Authenticate(Some(self)) 52 | } 53 | } 54 | 55 | /// An authenticating future, ultimately resolving into an authenticated connection. 56 | pub struct Authenticate(Option>); 57 | 58 | impl Future for Authenticate { 59 | type Item = Connection; 60 | type Error = HandlerError; 61 | 62 | fn poll(&mut self) -> Poll { 63 | loop { 64 | debug!("authenticated loop"); 65 | let done = if let Some(ref c) = self.0 { 66 | c.is_reading() && { 67 | if let Some(ref session) = c.session { 68 | session.is_authenticated() || session.0.auth_method.is_none() 69 | } else { 70 | false 71 | } 72 | } 73 | } else { 74 | false 75 | }; 76 | if done { 77 | return Ok(Async::Ready(self.0.take().unwrap())) 78 | } 79 | let status = if let Some(ref mut c) = self.0 { 80 | debug!("atomic poll"); 81 | try_ready!(c.atomic_poll()) 82 | } else { 83 | unreachable!() 84 | }; 85 | debug!("/atomic poll"); 86 | 87 | if let Status::Disconnect = status { 88 | debug!("disconnect"); 89 | return Ok(Async::Ready(self.0.take().unwrap())); 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /thrussh/src/pty.rs: -------------------------------------------------------------------------------- 1 | #[allow(non_camel_case_types, missing_docs)] 2 | #[derive(Debug, Copy, Clone, PartialEq)] 3 | /// Standard pseudo-terminal codes. 4 | pub enum Pty { 5 | TTY_OP_END = 0, 6 | VINTR = 1, 7 | VQUIT = 2, 8 | VERASE = 3, 9 | VKILL = 4, 10 | VEOF = 5, 11 | VEOL = 6, 12 | VEOL2 = 7, 13 | VSTART = 8, 14 | VSTOP = 9, 15 | VSUSP = 10, 16 | VDSUSP = 11, 17 | 18 | VREPRINT = 12, 19 | VWERASE = 13, 20 | VLNEXT = 14, 21 | VFLUSH = 15, 22 | VSWTCH = 16, 23 | VSTATUS = 17, 24 | VDISCARD = 18, 25 | IGNPAR = 30, 26 | PARMRK = 31, 27 | INPCK = 32, 28 | ISTRIP = 33, 29 | INLCR = 34, 30 | IGNCR = 35, 31 | ICRNL = 36, 32 | IUCLC = 37, 33 | IXON = 38, 34 | IXANY = 39, 35 | IXOFF = 40, 36 | IMAXBEL = 41, 37 | ISIG = 50, 38 | ICANON = 51, 39 | XCASE = 52, 40 | ECHO = 53, 41 | ECHOE = 54, 42 | ECHOK = 55, 43 | ECHONL = 56, 44 | NOFLSH = 57, 45 | TOSTOP = 58, 46 | IEXTEN = 59, 47 | ECHOCTL = 60, 48 | ECHOKE = 61, 49 | PENDIN = 62, 50 | OPOST = 70, 51 | OLCUC = 71, 52 | ONLCR = 72, 53 | OCRNL = 73, 54 | ONOCR = 74, 55 | ONLRET = 75, 56 | 57 | CS7 = 90, 58 | CS8 = 91, 59 | PARENB = 92, 60 | PARODD = 93, 61 | 62 | TTY_OP_ISPEED = 128, 63 | TTY_OP_OSPEED = 129, 64 | } 65 | 66 | impl Pty { 67 | #[doc(hidden)] 68 | pub fn from_u8(x: u8) -> Option { 69 | match x { 70 | 0 => None, 71 | 1 => Some(Pty::VINTR), 72 | 2 => Some(Pty::VQUIT), 73 | 3 => Some(Pty::VERASE), 74 | 4 => Some(Pty::VKILL), 75 | 5 => Some(Pty::VEOF), 76 | 6 => Some(Pty::VEOL), 77 | 7 => Some(Pty::VEOL2), 78 | 8 => Some(Pty::VSTART), 79 | 9 => Some(Pty::VSTOP), 80 | 10 => Some(Pty::VSUSP), 81 | 11 => Some(Pty::VDSUSP), 82 | 83 | 12 => Some(Pty::VREPRINT), 84 | 13 => Some(Pty::VWERASE), 85 | 14 => Some(Pty::VLNEXT), 86 | 15 => Some(Pty::VFLUSH), 87 | 16 => Some(Pty::VSWTCH), 88 | 17 => Some(Pty::VSTATUS), 89 | 18 => Some(Pty::VDISCARD), 90 | 30 => Some(Pty::IGNPAR), 91 | 31 => Some(Pty::PARMRK), 92 | 32 => Some(Pty::INPCK), 93 | 33 => Some(Pty::ISTRIP), 94 | 34 => Some(Pty::INLCR), 95 | 35 => Some(Pty::IGNCR), 96 | 36 => Some(Pty::ICRNL), 97 | 37 => Some(Pty::IUCLC), 98 | 38 => Some(Pty::IXON), 99 | 39 => Some(Pty::IXANY), 100 | 40 => Some(Pty::IXOFF), 101 | 41 => Some(Pty::IMAXBEL), 102 | 50 => Some(Pty::ISIG), 103 | 51 => Some(Pty::ICANON), 104 | 52 => Some(Pty::XCASE), 105 | 53 => Some(Pty::ECHO), 106 | 54 => Some(Pty::ECHOE), 107 | 55 => Some(Pty::ECHOK), 108 | 56 => Some(Pty::ECHONL), 109 | 57 => Some(Pty::NOFLSH), 110 | 58 => Some(Pty::TOSTOP), 111 | 59 => Some(Pty::IEXTEN), 112 | 60 => Some(Pty::ECHOCTL), 113 | 61 => Some(Pty::ECHOKE), 114 | 62 => Some(Pty::PENDIN), 115 | 70 => Some(Pty::OPOST), 116 | 71 => Some(Pty::OLCUC), 117 | 72 => Some(Pty::ONLCR), 118 | 73 => Some(Pty::OCRNL), 119 | 74 => Some(Pty::ONOCR), 120 | 75 => Some(Pty::ONLRET), 121 | 122 | 90 => Some(Pty::CS7), 123 | 91 => Some(Pty::CS8), 124 | 92 => Some(Pty::PARENB), 125 | 93 => Some(Pty::PARODD), 126 | 127 | 128 => Some(Pty::TTY_OP_ISPEED), 128 | 129 => Some(Pty::TTY_OP_OSPEED), 129 | _ => None, 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /thrussh/src/auth.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | use thrussh_keys::encoding; 17 | use cryptovec::CryptoVec; 18 | use thrussh_keys::key; 19 | use std::sync::Arc; 20 | 21 | bitflags! { 22 | /// Set of methods, represented by bit flags. 23 | pub struct MethodSet: u32 { 24 | /// The SSH `none` method (no authentication). 25 | const NONE = 1; 26 | /// The SSH `password` method (plaintext passwords). 27 | const PASSWORD = 2; 28 | /// The SSH `publickey` method (sign a challenge sent by the 29 | /// server). 30 | const PUBLICKEY = 4; 31 | /// The SSH `hostbased` method (certain hostnames are allowed 32 | /// by the server). 33 | const HOSTBASED = 8; 34 | /// The SSH `keyboard-interactive` method (answer to a 35 | /// challenge, where the "challenge" can be a password prompt, 36 | /// a bytestring to sign with a smartcard, or something else). 37 | const KEYBOARD_INTERACTIVE = 16; 38 | } 39 | } 40 | 41 | macro_rules! iter { 42 | ( $y:expr, $x:expr ) => { 43 | { 44 | if $y.contains($x) { 45 | $y.remove($x); 46 | return Some($x) 47 | } 48 | } 49 | }; 50 | } 51 | 52 | 53 | impl Iterator for MethodSet { 54 | type Item = MethodSet; 55 | fn next(&mut self) -> Option { 56 | iter!(self, MethodSet::NONE); 57 | iter!(self, MethodSet::PASSWORD); 58 | iter!(self, MethodSet::PUBLICKEY); 59 | iter!(self, MethodSet::HOSTBASED); 60 | iter!(self, MethodSet::KEYBOARD_INTERACTIVE); 61 | None 62 | } 63 | } 64 | 65 | pub enum Method { 66 | // None, 67 | Password { password: String }, 68 | PublicKey { key: Arc }, 69 | FuturePublicKey { key: key::PublicKey }, 70 | // Hostbased, 71 | } 72 | 73 | impl encoding::Bytes for MethodSet { 74 | fn bytes(&self) -> &'static [u8] { 75 | match *self { 76 | MethodSet::NONE => b"none", 77 | MethodSet::PASSWORD => b"password", 78 | MethodSet::PUBLICKEY => b"publickey", 79 | MethodSet::HOSTBASED => b"hostbased", 80 | MethodSet::KEYBOARD_INTERACTIVE => b"keyboard-interactive", 81 | _ => b"", 82 | } 83 | } 84 | } 85 | 86 | impl MethodSet { 87 | pub(crate) fn from_bytes(b: &[u8]) -> Option { 88 | match b { 89 | b"none" => Some(MethodSet::NONE), 90 | b"password" => Some(MethodSet::PASSWORD), 91 | b"publickey" => Some(MethodSet::PUBLICKEY), 92 | b"hostbased" => Some(MethodSet::HOSTBASED), 93 | b"keyboard-interactive" => Some(MethodSet::KEYBOARD_INTERACTIVE), 94 | _ => None, 95 | } 96 | } 97 | } 98 | 99 | #[doc(hidden)] 100 | #[derive(Debug)] 101 | pub struct AuthRequest { 102 | pub methods: MethodSet, 103 | pub partial_success: bool, 104 | pub current: Option, 105 | pub rejection_count: usize, 106 | } 107 | 108 | #[doc(hidden)] 109 | #[derive(Debug)] 110 | pub enum CurrentRequest { 111 | PublicKey { 112 | key: CryptoVec, 113 | algo: CryptoVec, 114 | sent_pk_ok: bool, 115 | }, 116 | KeyboardInteractive { submethods: String }, 117 | } 118 | -------------------------------------------------------------------------------- /thrussh/src/client/wait.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use tokio::io::{AsyncRead, AsyncWrite}; 3 | use tcp::Tcp; 4 | 5 | impl Connection { 6 | /// Wait until a condition is met on the connection. 7 | pub fn wait) -> bool>(self, f: F) -> Wait { 8 | Wait { 9 | connection: Some(self), 10 | condition: f, 11 | first_round: true, 12 | } 13 | } 14 | 15 | /// Flush the session, sending any pending message. 16 | pub fn wait_flush(self) -> WaitFlush { 17 | WaitFlush { 18 | connection: Some(self), 19 | first_round: true, 20 | } 21 | } 22 | } 23 | 24 | /// A future waiting for a channel to be closed. 25 | pub struct Wait { 26 | connection: Option>, 27 | condition: F, 28 | first_round: bool, 29 | } 30 | 31 | impl) -> bool> Future 32 | for Wait { 33 | type Item = Connection; 34 | type Error = HandlerError; 35 | 36 | fn poll(&mut self) -> Poll { 37 | if self.first_round { 38 | if let Some(ref mut c) = self.connection { 39 | c.abort_read()? 40 | } 41 | self.first_round = false 42 | } 43 | 44 | loop { 45 | debug!("wait loop"); 46 | if let Some(mut connection) = self.connection.take() { 47 | if connection.handler.is_some() && (self.condition)(&connection) && 48 | connection.is_reading() 49 | { 50 | return Ok(Async::Ready(connection)); 51 | } else { 52 | match try!(connection.atomic_poll()) { 53 | Async::Ready(Status::Ok) => { 54 | self.connection = Some(connection); 55 | } 56 | Async::Ready(Status::Disconnect) => return Ok(Async::Ready(connection)), 57 | Async::NotReady => { 58 | self.connection = Some(connection); 59 | return Ok(Async::NotReady); 60 | } 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | 69 | /// A future waiting for a flush request to complete. 70 | pub struct WaitFlush { 71 | connection: Option>, 72 | first_round: bool, 73 | } 74 | 75 | 76 | impl Future for WaitFlush { 77 | type Item = Connection; 78 | type Error = HandlerError; 79 | 80 | fn poll(&mut self) -> Poll { 81 | if self.first_round { 82 | if let Some(ref mut c) = self.connection { 83 | c.abort_read()? 84 | } 85 | self.first_round = false 86 | } 87 | loop { 88 | debug!("WaitFlush loop"); 89 | if let Some(mut c) = self.connection.take() { 90 | match try!(c.atomic_poll()) { 91 | Async::Ready(Status::Disconnect) => return Ok(Async::Ready(c)), 92 | Async::NotReady => { 93 | self.connection = Some(c); 94 | return Ok(Async::NotReady); 95 | } 96 | Async::Ready(Status::Ok) => { 97 | match c.state { 98 | Some(ConnectionState::Read(_)) => return Ok(Async::Ready(c)), 99 | _ => self.connection = Some(c), 100 | } 101 | } 102 | } 103 | } else { 104 | unreachable!() 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /thrussh-keys/src/format/openssh.rs: -------------------------------------------------------------------------------- 1 | use {Error, ErrorKind, KEYTYPE_ED25519}; 2 | use encoding::Reader; 3 | use key; 4 | use cryptovec::CryptoVec; 5 | use openssl::symm::{Cipher, Mode, Crypter}; 6 | use bcrypt_pbkdf; 7 | 8 | /// Decode a secret key given in the OpenSSH format, deciphering it if 9 | /// needed using the supplied password. 10 | pub fn decode_openssh( 11 | secret: &[u8], 12 | password: Option<&[u8]>, 13 | ) -> Result { 14 | if &secret[0..15] == b"openssh-key-v1\0" { 15 | let mut position = secret.reader(15); 16 | 17 | let ciphername = position.read_string()?; 18 | let kdfname = position.read_string()?; 19 | let kdfoptions = position.read_string()?; 20 | 21 | let nkeys = position.read_u32()?; 22 | let secret_ = position.read_string()?; 23 | let secret = decrypt_secret_key( 24 | ciphername, 25 | kdfname, 26 | kdfoptions, 27 | password, 28 | secret_, 29 | )?; 30 | let mut position = secret.reader(0); 31 | let _check0 = position.read_u32()?; 32 | let _check1 = position.read_u32()?; 33 | for _ in 0..nkeys { 34 | 35 | let key_type = position.read_string()?; 36 | let pubkey = position.read_string()?; 37 | let seckey = position.read_string()?; 38 | let _comment = position.read_string()?; 39 | 40 | if key_type == KEYTYPE_ED25519 { 41 | assert_eq!(pubkey, &seckey[32..]); 42 | use key::ed25519::*; 43 | let mut secret = SecretKey::new_zeroed(); 44 | secret.key.clone_from_slice(seckey); 45 | return Ok(key::KeyPair::Ed25519(secret)) 46 | } else { 47 | return Err(ErrorKind::UnsupportedKeyType(key_type.to_vec()).into()) 48 | } 49 | } 50 | Err(ErrorKind::CouldNotReadKey.into()) 51 | 52 | } else { 53 | Err(ErrorKind::CouldNotReadKey.into()) 54 | } 55 | } 56 | 57 | 58 | fn decrypt_secret_key( 59 | ciphername: &[u8], 60 | kdfname: &[u8], 61 | kdfoptions: &[u8], 62 | password: Option<&[u8]>, 63 | secret_key: &[u8], 64 | ) -> Result, Error> { 65 | 66 | if kdfname == b"none" { 67 | if password.is_none() { 68 | Ok(secret_key.to_vec()) 69 | } else { 70 | Err(ErrorKind::CouldNotReadKey.into()) 71 | } 72 | } else if let Some(password) = password { 73 | let mut key = CryptoVec::new(); 74 | let cipher = match ciphername { 75 | b"aes128-cbc" => { key.resize(16 + 16); Cipher::aes_128_cbc() }, 76 | b"aes128-ctr" => { key.resize(16 + 16); Cipher::aes_128_ctr()}, 77 | b"aes256-cbc" => { key.resize(16 + 32); Cipher::aes_256_cbc()}, 78 | b"aes256-ctr" => { key.resize(16 + 32); Cipher::aes_256_ctr()}, 79 | _ => return Err(ErrorKind::CouldNotReadKey.into()), 80 | }; 81 | 82 | match kdfname { 83 | b"bcrypt" => { 84 | let mut kdfopts = kdfoptions.reader(0); 85 | let salt = kdfopts.read_string()?; 86 | let rounds = kdfopts.read_u32()?; 87 | bcrypt_pbkdf::bcrypt_pbkdf(password, salt, rounds, &mut key); 88 | } 89 | _kdfname => { 90 | return Err(ErrorKind::CouldNotReadKey.into()); 91 | } 92 | }; 93 | let iv = &key[32..]; 94 | let key = &key[..32]; 95 | let mut c = Crypter::new( 96 | cipher, 97 | Mode::Decrypt, 98 | &key, 99 | Some(&iv) 100 | )?; 101 | c.pad(false); 102 | let mut dec = vec![0; secret_key.len() + 32]; 103 | let n = c.update(&secret_key, &mut dec)?; 104 | let n = n + c.finalize(&mut dec[n..])?; 105 | dec.truncate(n); 106 | Ok(dec) 107 | } else { 108 | Err(ErrorKind::KeyIsEncrypted.into()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /thrussh/src/client/channel_open.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use tokio::io::{AsyncRead, AsyncWrite}; 3 | use tcp::Tcp; 4 | 5 | impl Connection { 6 | /// Ask the server to open a session channel. 7 | pub fn channel_open_session(mut self) -> ChannelOpen { 8 | let num = if let Some(ref mut s) = self.session { 9 | s.channel_open_session().unwrap() 10 | } else { 11 | unreachable!() 12 | }; 13 | ChannelOpen { 14 | connection: Some(self), 15 | channel: num, 16 | channel_type: PhantomData, 17 | first_round: true, 18 | } 19 | } 20 | 21 | /// Ask the server to open an X11 forwarding channel. 22 | pub fn channel_open_x11( 23 | mut self, 24 | originator_address: &str, 25 | originator_port: u32, 26 | ) -> ChannelOpen { 27 | let num = if let Some(ref mut s) = self.session { 28 | s.channel_open_x11(originator_address, originator_port) 29 | .unwrap() 30 | } else { 31 | unreachable!() 32 | }; 33 | ChannelOpen { 34 | connection: Some(self), 35 | channel: num, 36 | channel_type: PhantomData, 37 | first_round: true, 38 | } 39 | } 40 | 41 | /// Ask the server to open a direct TCP/IP forwarding channel. 42 | pub fn channel_open_direct_tcpip( 43 | mut self, 44 | host_to_connect: &str, 45 | port_to_connect: u32, 46 | originator_address: &str, 47 | originator_port: u32, 48 | ) -> ChannelOpen { 49 | let num = if let Some(ref mut s) = self.session { 50 | s.channel_open_direct_tcpip( 51 | host_to_connect, 52 | port_to_connect, 53 | originator_address, 54 | originator_port, 55 | ).unwrap() 56 | } else { 57 | unreachable!() 58 | }; 59 | ChannelOpen { 60 | connection: Some(self), 61 | channel: num, 62 | channel_type: PhantomData, 63 | first_round: true, 64 | } 65 | } 66 | } 67 | use std::marker::PhantomData; 68 | 69 | #[doc(hidden)] 70 | pub enum X11Channel {} 71 | #[doc(hidden)] 72 | pub enum SessionChannel {} 73 | #[doc(hidden)] 74 | pub enum DirectTcpIpChannel {} 75 | 76 | /// A future resolving into an open channel number of type 77 | /// `ChannelType`, which can be either `SessionChannel`, `X11Channel` 78 | /// or `DirectTcpIdChannel`. 79 | pub struct ChannelOpen { 80 | connection: Option>, 81 | channel: ChannelId, 82 | channel_type: PhantomData, 83 | first_round: bool, 84 | } 85 | 86 | impl Future 87 | for ChannelOpen { 88 | type Item = (Connection, ChannelId); 89 | type Error = HandlerError; 90 | 91 | fn poll(&mut self) -> Poll { 92 | 93 | if self.first_round { 94 | if let Some(ref mut c) = self.connection { 95 | c.abort_read()?; 96 | } 97 | self.first_round = false 98 | } 99 | loop { 100 | debug!("channelopen loop"); 101 | let is_open = if let Some(ref c) = self.connection { 102 | if let Some(ref s) = c.session { 103 | s.channel_is_open(self.channel) && c.is_reading() 104 | } else { 105 | false 106 | } 107 | } else { 108 | false 109 | }; 110 | if is_open { 111 | return Ok(Async::Ready( 112 | (self.connection.take().unwrap(), self.channel), 113 | )); 114 | } 115 | 116 | let status = if let Some(ref mut c) = self.connection { 117 | try_ready!(c.atomic_poll()) 118 | } else { 119 | unreachable!() 120 | }; 121 | 122 | if let Status::Disconnect = status { 123 | return Err(HandlerError::Error(Error::Disconnect)); 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /thrussh-keys/src/format/mod.rs: -------------------------------------------------------------------------------- 1 | use {Error, ErrorKind}; 2 | use key; 3 | use super::is_base64_char; 4 | use hex::FromHex; 5 | use base64::{decode_config, encode_config, MIME}; 6 | use openssl::rsa::Rsa; 7 | use std::io::Write; 8 | 9 | mod openssh; 10 | pub use self::openssh::*; 11 | 12 | mod pkcs5; 13 | pub use self::pkcs5::*; 14 | 15 | mod pkcs8; 16 | 17 | const AES_128_CBC: &'static str = "DEK-Info: AES-128-CBC,"; 18 | 19 | #[derive(Clone, Copy, Debug)] 20 | /// AES encryption key. 21 | pub enum Encryption { 22 | /// Key for AES128 23 | Aes128Cbc([u8; 16]), 24 | /// Key for AES256 25 | Aes256Cbc([u8; 16]), 26 | } 27 | 28 | #[derive(Clone, Debug)] 29 | enum Format { 30 | Rsa, 31 | Openssh, 32 | Pkcs5Encrypted(Encryption), 33 | Pkcs8Encrypted, 34 | Pkcs8, 35 | } 36 | 37 | /// Decode a secret key, possibly deciphering it with the supplied 38 | /// password. 39 | pub fn decode_secret_key( 40 | secret: &str, 41 | password: Option<&[u8]>, 42 | ) -> Result { 43 | 44 | let mut format = None; 45 | let secret = { 46 | let mut started = false; 47 | let mut sec = String::new(); 48 | for l in secret.lines() { 49 | if started == true { 50 | if l.chars().all(is_base64_char) { 51 | sec.push_str(l) 52 | } else if l.starts_with(AES_128_CBC) { 53 | let iv_: Vec = FromHex::from_hex(l.split_at(AES_128_CBC.len()).1)?; 54 | if iv_.len() != 16 { 55 | return Err(ErrorKind::CouldNotReadKey.into()); 56 | } 57 | let mut iv = [0; 16]; 58 | iv.clone_from_slice(&iv_); 59 | format = Some(Format::Pkcs5Encrypted(Encryption::Aes128Cbc(iv))) 60 | } 61 | } 62 | if l == "-----BEGIN OPENSSH PRIVATE KEY-----" { 63 | started = true; 64 | format = Some(Format::Openssh); 65 | } else if l == "-----BEGIN RSA PRIVATE KEY-----" { 66 | started = true; 67 | format = Some(Format::Rsa); 68 | } else if l == "-----BEGIN ENCRYPTED PRIVATE KEY-----" { 69 | started = true; 70 | format = Some(Format::Pkcs8Encrypted); 71 | } else if l == "-----BEGIN PRIVATE KEY-----" { 72 | started = true; 73 | format = Some(Format::Pkcs8); 74 | } else if l.starts_with("-----END ") { 75 | break; 76 | } 77 | } 78 | sec 79 | }; 80 | 81 | // debug!("secret = {:?}", secret); 82 | let secret = decode_config(&secret, MIME)?; 83 | match format { 84 | Some(Format::Openssh) => decode_openssh(&secret, password), 85 | Some(Format::Rsa) => decode_rsa(&secret), 86 | Some(Format::Pkcs5Encrypted(enc)) => decode_pkcs5(&secret, password, enc), 87 | Some(Format::Pkcs8Encrypted) | 88 | Some(Format::Pkcs8) => self::pkcs8::decode_pkcs8(&secret, password), 89 | None => Err(ErrorKind::CouldNotReadKey.into()), 90 | } 91 | } 92 | 93 | pub fn encode_pkcs8_pem(key: &key::KeyPair, mut w: W) -> Result<(), Error> { 94 | let x = self::pkcs8::encode_pkcs8(key); 95 | w.write_all(b"-----BEGIN PRIVATE KEY-----\n")?; 96 | w.write_all(encode_config(&x, MIME).as_bytes())?; 97 | w.write_all(b"\n-----END PRIVATE KEY-----\n")?; 98 | Ok(()) 99 | } 100 | 101 | pub fn encode_pkcs8_pem_encrypted(key: &key::KeyPair, pass: &[u8], rounds: u32, mut w: W) -> Result<(), Error> { 102 | let x = self::pkcs8::encode_pkcs8_encrypted(pass, rounds, key)?; 103 | w.write_all(b"-----BEGIN ENCRYPTED PRIVATE KEY-----\n")?; 104 | w.write_all(encode_config(&x, MIME).as_bytes())?; 105 | w.write_all(b"\n-----END ENCRYPTED PRIVATE KEY-----\n")?; 106 | Ok(()) 107 | } 108 | 109 | 110 | fn decode_rsa(secret: &[u8]) -> Result { 111 | Ok(key::KeyPair::RSA { 112 | key: Rsa::private_key_from_der(secret)?, 113 | hash: key::SignatureHash::SHA2_256 114 | }) 115 | } 116 | 117 | fn pkcs_unpad(dec: &mut Vec) { 118 | let len = dec.len(); 119 | if len > 0 { 120 | let padding_len = dec[len-1]; 121 | if dec[(len - padding_len as usize)..].iter().all(|&x| x == padding_len) { 122 | dec.truncate(len - padding_len as usize) 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /thrussh/src/client/data.rs: -------------------------------------------------------------------------------- 1 | use super::connection::{Connection, ConnectionState}; 2 | use tokio::io::{AsyncRead, AsyncWrite}; 3 | use super::Handler; 4 | use {ChannelId, HandlerError, Error, Status, AtomicPoll}; 5 | use futures::{Poll, Async, Future}; 6 | use tcp::Tcp; 7 | 8 | impl Connection { 9 | /// Send data to a channel. On session channels, `extended` can be 10 | /// used to encode standard error by passing `Some(1)`, and stdout 11 | /// by passing `None`. 12 | pub fn data>( 13 | self, 14 | channel: ChannelId, 15 | extended: Option, 16 | data: T, 17 | ) -> Data { 18 | 19 | debug!("data: {:?}", data.as_ref().len()); 20 | Data { 21 | connection: Some(self), 22 | channel: channel, 23 | extended: extended, 24 | data: Some(data), 25 | position: 0, 26 | first_round: true, 27 | } 28 | } 29 | } 30 | 31 | /// Future for sending data. 32 | pub struct Data> { 33 | connection: Option>, 34 | data: Option, 35 | extended: Option, 36 | channel: ChannelId, 37 | position: usize, 38 | first_round: bool, 39 | } 40 | 41 | // We are careful here, to leave the connection in the Read state (the 42 | // only cancellable one) before returning Async::Ready. 43 | impl> Future for Data { 44 | type Item = (Connection, T); 45 | type Error = HandlerError; 46 | fn poll(&mut self) -> Poll { 47 | 48 | let mut connection = self.connection.take().unwrap(); 49 | if self.first_round { 50 | connection.abort_read()?; 51 | self.first_round = false 52 | } 53 | let data = self.data.take().unwrap(); 54 | 55 | loop { 56 | debug!("Data loop"); 57 | // Do everything we can do. 58 | let status = connection.atomic_poll()?; 59 | let mut not_ready = false; 60 | match status { 61 | Async::Ready(Status::Disconnect) => return Err(From::from(Error::Disconnect)), 62 | Async::Ready(Status::Ok) if connection.is_reading() => {} 63 | Async::Ready(Status::Ok) => continue, 64 | Async::NotReady if connection.is_reading() => not_ready = true, 65 | Async::NotReady => { 66 | self.connection = Some(connection); 67 | self.data = Some(data); 68 | return Ok(Async::NotReady); 69 | } 70 | } 71 | 72 | let mut session = connection.session.take().unwrap(); 73 | { 74 | let data_ = data.as_ref(); 75 | let enc = session.0.encrypted.as_mut().unwrap(); 76 | self.position += enc.data(self.channel, self.extended, &data_[self.position..]); 77 | } 78 | session.flush()?; 79 | if !session.0.write_buffer.buffer.is_empty() { 80 | if let Some(ConnectionState::Read(mut read)) = connection.state { 81 | if let Some((stream, read_buffer)) = read.try_abort() { 82 | connection.read_buffer = Some(read_buffer); 83 | connection.state = Some(ConnectionState::Write( 84 | session.0.write_buffer.write_all(stream), 85 | )); 86 | connection.session = Some(session); 87 | } else { 88 | connection.state = Some(ConnectionState::Read(read)); 89 | connection.session = Some(session); 90 | } 91 | } else { 92 | connection.session = Some(session); 93 | } 94 | } else if self.position < data.as_ref().len() { 95 | connection.session = Some(session); 96 | if not_ready { 97 | self.connection = Some(connection); 98 | self.data = Some(data); 99 | return Ok(Async::NotReady); 100 | } 101 | } else { 102 | connection.session = Some(session); 103 | return Ok(Async::Ready((connection, data))); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /thrussh/src/server/kex.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use negotiation::Select; 3 | use msg; 4 | use cipher::CipherPair; 5 | use negotiation; 6 | use key::PubKey; 7 | use thrussh_keys::encoding::{Encoding, Reader}; 8 | 9 | use kex; 10 | 11 | impl KexInit { 12 | pub fn server_parse( 13 | mut self, 14 | config: &Config, 15 | cipher: &CipherPair, 16 | buf: &[u8], 17 | write_buffer: &mut SSHBuffer, 18 | ) -> Result { 19 | 20 | if buf[0] == msg::KEXINIT { 21 | debug!("server parse"); 22 | let algo = if self.algo.is_none() { 23 | // read algorithms from packet. 24 | self.exchange.client_kex_init.extend(buf); 25 | super::negotiation::Server::read_kex(buf, &config.preferred)? 26 | } else { 27 | return Err(Error::Kex); 28 | }; 29 | if !self.sent { 30 | self.server_write(config, cipher, write_buffer)? 31 | } 32 | let mut key = 0; 33 | debug!("config {:?} algo {:?}", config.keys, algo.key); 34 | while key < config.keys.len() && config.keys[key].name() != algo.key.as_ref() { 35 | key += 1 36 | } 37 | let next_kex = if key < config.keys.len() { 38 | Kex::KexDh(KexDh { 39 | exchange: self.exchange, 40 | key: key, 41 | names: algo, 42 | session_id: self.session_id, 43 | }) 44 | } else { 45 | return Err(Error::UnknownKey); 46 | }; 47 | 48 | Ok(next_kex) 49 | } else { 50 | Ok(Kex::KexInit(self)) 51 | } 52 | } 53 | 54 | pub fn server_write( 55 | &mut self, 56 | config: &Config, 57 | cipher: &CipherPair, 58 | write_buffer: &mut SSHBuffer, 59 | ) -> Result<(), Error> { 60 | self.exchange.server_kex_init.clear(); 61 | negotiation::write_kex(&config.preferred, &mut self.exchange.server_kex_init)?; 62 | self.sent = true; 63 | cipher.write(&self.exchange.server_kex_init, write_buffer); 64 | Ok(()) 65 | } 66 | } 67 | 68 | impl KexDh { 69 | pub fn parse( 70 | mut self, 71 | config: &Config, 72 | buffer: &mut CryptoVec, 73 | buffer2: &mut CryptoVec, 74 | cipher: &CipherPair, 75 | buf: &[u8], 76 | write_buffer: &mut SSHBuffer, 77 | ) -> Result { 78 | debug!("KexDh: parse {:?}", self.names.ignore_guessed); 79 | if self.names.ignore_guessed { 80 | // If we need to ignore this packet. 81 | self.names.ignore_guessed = false; 82 | Ok(Kex::KexDh(self)) 83 | } else { 84 | // Else, process it. 85 | debug!("buf = {:?}", buf); 86 | assert!(buf[0] == msg::KEX_ECDH_INIT); 87 | let mut r = buf.reader(1); 88 | self.exchange.client_ephemeral.extend(r.read_string()?); 89 | let kex = try!(kex::Algorithm::server_dh( 90 | self.names.kex, 91 | &mut self.exchange, 92 | buf, 93 | )); 94 | // Then, we fill the write buffer right away, so that we 95 | // can output it immediately when the time comes. 96 | let kexdhdone = KexDhDone { 97 | exchange: self.exchange, 98 | kex: kex, 99 | key: self.key, 100 | names: self.names, 101 | session_id: self.session_id, 102 | }; 103 | 104 | let hash = try!(kexdhdone.kex.compute_exchange_hash( 105 | &config.keys[kexdhdone.key], 106 | &kexdhdone.exchange, 107 | buffer, 108 | )); 109 | debug!("exchange hash: {:?}", hash); 110 | buffer.clear(); 111 | buffer.push(msg::KEX_ECDH_REPLY); 112 | config.keys[kexdhdone.key].push_to(buffer); 113 | // Server ephemeral 114 | buffer.extend_ssh_string(&kexdhdone.exchange.server_ephemeral); 115 | // Hash signature 116 | debug!(" >>>>>>>>>>>>>>> signing with key {:?}", kexdhdone.key); 117 | debug!("hash: {:?}", hash); 118 | debug!("key: {:?}", config.keys[kexdhdone.key]); 119 | config.keys[kexdhdone.key].add_signature(buffer, &hash)?; 120 | cipher.write(&buffer, write_buffer); 121 | 122 | cipher.write(&[msg::NEWKEYS], write_buffer); 123 | 124 | Ok(Kex::NewKeys( 125 | try!(kexdhdone.compute_keys(hash, buffer, buffer2, true)), 126 | )) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /thrussh/src/ssh_read.rs: -------------------------------------------------------------------------------- 1 | use std; 2 | use Error; 3 | use cryptovec::CryptoVec; 4 | use futures::{Async, Poll}; 5 | use tokio::io::{AsyncRead, AsyncWrite}; 6 | use tcp::Tcp; 7 | use std::io::ErrorKind; 8 | 9 | /// The buffer to read the identification string (first line in the 10 | /// protocol). 11 | struct ReadSshIdBuffer { 12 | pub buf: CryptoVec, 13 | pub total: usize, 14 | pub bytes_read: usize, 15 | pub sshid_len: usize, 16 | } 17 | 18 | impl ReadSshIdBuffer { 19 | pub fn id(&self) -> &[u8] { 20 | &self.buf[..self.sshid_len] 21 | } 22 | 23 | pub fn new() -> ReadSshIdBuffer { 24 | let mut buf = CryptoVec::new(); 25 | buf.resize(256); 26 | ReadSshIdBuffer { 27 | buf: buf, 28 | sshid_len: 0, 29 | bytes_read: 0, 30 | total: 0, 31 | } 32 | } 33 | } 34 | 35 | impl std::fmt::Debug for ReadSshIdBuffer { 36 | fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { 37 | write!(fmt, "ReadSshId {:?}", self.id()) 38 | } 39 | } 40 | 41 | /// SshRead is the same as R, plus a small buffer in the beginning to 42 | /// read the identification string. After the first line in the 43 | /// connection, the `id` parameter is never used again. 44 | pub struct SshRead { 45 | id: Option, 46 | r: R, 47 | } 48 | 49 | impl std::io::Read for SshRead { 50 | fn read(&mut self, buf: &mut [u8]) -> Result { 51 | if let Some(mut id) = self.id.take() { 52 | debug!("id {:?} {:?}", id.total, id.bytes_read); 53 | if id.total > id.bytes_read { 54 | let result = { 55 | let mut readable = &id.buf[id.bytes_read..id.total]; 56 | readable.read(buf).unwrap() 57 | }; 58 | debug!("read {:?} bytes from id.buf", result); 59 | id.bytes_read += result; 60 | self.id = Some(id); 61 | return Ok(result); 62 | } 63 | } 64 | self.r.read(buf) 65 | } 66 | } 67 | 68 | impl AsyncRead for SshRead {} 69 | 70 | impl std::io::Write for SshRead { 71 | fn write(&mut self, buf: &[u8]) -> Result { 72 | self.r.write(buf) 73 | } 74 | fn flush(&mut self) -> Result<(), std::io::Error> { 75 | self.r.flush() 76 | } 77 | } 78 | 79 | impl AsyncWrite for SshRead { 80 | fn shutdown(&mut self) -> Poll<(), std::io::Error> { 81 | self.r.shutdown() 82 | } 83 | } 84 | 85 | 86 | impl Tcp for SshRead { 87 | fn tcp_shutdown(&mut self) -> Result<(), std::io::Error> { 88 | self.r.tcp_shutdown() 89 | } 90 | } 91 | 92 | 93 | 94 | impl SshRead { 95 | pub fn new(r: R) -> Self { 96 | SshRead { 97 | id: Some(ReadSshIdBuffer::new()), 98 | r: r, 99 | } 100 | } 101 | 102 | pub fn read_ssh_id(&mut self) -> Poll<&[u8], Error> { 103 | let ssh_id = self.id.as_mut().unwrap(); 104 | loop { 105 | let mut i = 0; 106 | debug!("read_ssh_id: reading"); 107 | let n = match self.r.read(&mut ssh_id.buf[ssh_id.total..]) { 108 | Ok(n) => n, 109 | Err(ref e) if e.kind() == ErrorKind::WouldBlock => return Ok(Async::NotReady), 110 | Err(e) => return Err(e.into()) 111 | }; 112 | debug!("read {:?}", n); 113 | 114 | // let buf = try_nb!(stream.fill_buf()); 115 | ssh_id.total += n; 116 | debug!("{:?}", std::str::from_utf8(&ssh_id.buf[..ssh_id.total])); 117 | if n == 0 { 118 | return Err(Error::Disconnect); 119 | } 120 | loop { 121 | if i >= ssh_id.total - 1 { 122 | break; 123 | } 124 | if ssh_id.buf[i] == b'\r' && ssh_id.buf[i + 1] == b'\n' { 125 | ssh_id.bytes_read = i + 2; 126 | break; 127 | } else if ssh_id.buf[i + 1] == b'\n' { 128 | // This is really wrong, but OpenSSH 7.4 uses 129 | // it. 130 | ssh_id.bytes_read = i + 2; 131 | i += 1; 132 | break; 133 | } else { 134 | i += 1; 135 | } 136 | } 137 | 138 | if ssh_id.bytes_read > 0 { 139 | // If we have a full line, handle it. 140 | if i >= 8 { 141 | if &ssh_id.buf[0..8] == b"SSH-2.0-" { 142 | // Either the line starts with "SSH-2.0-" 143 | ssh_id.sshid_len = i; 144 | return Ok(Async::Ready(&ssh_id.buf[..ssh_id.sshid_len])); 145 | } 146 | } 147 | // Else, it is a "preliminary" (see 148 | // https://tools.ietf.org/html/rfc4253#section-4.2), 149 | // and we can discard it and read the next one. 150 | ssh_id.total = 0; 151 | ssh_id.bytes_read = 0; 152 | } 153 | debug!("bytes_read: {:?}", ssh_id.bytes_read); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /thrussh/src/cipher/chacha20poly1305.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | // http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD 17 | 18 | use super::super::Error; 19 | use sodium::Sodium; 20 | use sodium::chacha20::{KEY_BYTES, NONCE_BYTES, Nonce, Key}; 21 | use byteorder::{ByteOrder, BigEndian}; 22 | 23 | pub struct OpeningKey { k1: Key, k2: Key, sodium: Sodium } 24 | pub struct SealingKey { k1: Key, k2: Key, sodium: Sodium } 25 | 26 | const TAG_LEN: usize = 16; 27 | 28 | pub static CIPHER: super::Cipher = super::Cipher { 29 | name: NAME, 30 | key_len: 64, 31 | make_sealing_cipher, 32 | make_opening_cipher, 33 | }; 34 | 35 | pub const NAME: super::Name = super::Name("chacha20-poly1305@openssh.com"); 36 | 37 | fn make_sealing_cipher(k: &[u8]) -> super::SealingCipher { 38 | let mut k1 = Key([0; KEY_BYTES]); 39 | let mut k2 = Key([0; KEY_BYTES]); 40 | k1.0.clone_from_slice(&k[KEY_BYTES..]); 41 | k2.0.clone_from_slice(&k[..KEY_BYTES]); 42 | super::SealingCipher::Chacha20Poly1305(SealingKey { k1, k2, sodium: Sodium::new() }) 43 | } 44 | 45 | fn make_opening_cipher(k: &[u8]) -> super::OpeningCipher { 46 | let mut k1 = Key([0; KEY_BYTES]); 47 | let mut k2 = Key([0; KEY_BYTES]); 48 | k1.0.clone_from_slice(&k[KEY_BYTES..]); 49 | k2.0.clone_from_slice(&k[..KEY_BYTES]); 50 | super::OpeningCipher::Chacha20Poly1305(OpeningKey { k1, k2, sodium: Sodium::new() }) 51 | } 52 | 53 | fn make_counter(sequence_number: u32) -> Nonce { 54 | let mut nonce = Nonce([0; NONCE_BYTES]); 55 | let i0 = NONCE_BYTES-4; 56 | BigEndian::write_u32(&mut nonce.0[i0..], sequence_number); 57 | nonce 58 | } 59 | 60 | impl super::OpeningKey for OpeningKey { 61 | 62 | fn decrypt_packet_length( 63 | &self, 64 | sequence_number: u32, 65 | mut encrypted_packet_length: [u8; 4], 66 | ) -> [u8; 4] { 67 | let nonce = make_counter(sequence_number); 68 | self.sodium.chacha20_xor(&mut encrypted_packet_length, &nonce, &self.k1); 69 | encrypted_packet_length 70 | } 71 | 72 | fn tag_len(&self) -> usize { 73 | TAG_LEN 74 | } 75 | 76 | fn open<'a>( 77 | &self, 78 | sequence_number: u32, 79 | ciphertext_in_plaintext_out: &'a mut [u8], 80 | tag: &[u8], 81 | ) -> Result<&'a [u8], Error> { 82 | 83 | let nonce = make_counter(sequence_number); 84 | { 85 | use sodium::poly1305::Key; 86 | let mut poly_key = Key([0; 32]); 87 | self.sodium.chacha20_xor(&mut poly_key.0, &nonce, &self.k2); 88 | // let mut tag_ = Tag([0; 16]); 89 | // tag_.0.clone_from_slice(tag); 90 | if !self.sodium.poly1305_verify(&tag, ciphertext_in_plaintext_out, &poly_key) { 91 | return Err(Error::PacketAuth) 92 | } 93 | } 94 | self.sodium.chacha20_xor_ic(&mut ciphertext_in_plaintext_out[4..], &nonce, 1, &self.k2); 95 | Ok(&ciphertext_in_plaintext_out[4..]) 96 | } 97 | } 98 | 99 | impl super::SealingKey for SealingKey { 100 | 101 | fn padding_length(&self, payload: &[u8]) -> usize { 102 | let block_size = 8; 103 | let extra_len = super::PACKET_LENGTH_LEN + super::PADDING_LENGTH_LEN; 104 | let padding_len = if payload.len() + extra_len <= super::MINIMUM_PACKET_LEN { 105 | super::MINIMUM_PACKET_LEN - payload.len() - super::PADDING_LENGTH_LEN 106 | } else { 107 | (block_size - ((super::PADDING_LENGTH_LEN + payload.len()) % block_size)) 108 | }; 109 | if padding_len < super::PACKET_LENGTH_LEN { 110 | padding_len + block_size 111 | } else { 112 | padding_len 113 | } 114 | } 115 | 116 | // As explained in "SSH via CTR mode with stateful decryption" in 117 | // https://openvpn.net/papers/ssh-security.pdf, the padding doesn't need to 118 | // be random because we're doing stateful counter-mode encryption. Use 119 | // fixed padding to avoid PRNG overhead. 120 | fn fill_padding(&self, padding_out: &mut [u8]) { 121 | for padding_byte in padding_out { 122 | *padding_byte = 0; 123 | } 124 | } 125 | 126 | fn tag_len(&self) -> usize { 127 | TAG_LEN 128 | } 129 | 130 | /// Append an encrypted packet with contents `packet_content` at the end of `buffer`. 131 | fn seal( 132 | &self, 133 | sequence_number: u32, 134 | plaintext_in_ciphertext_out: &mut [u8], 135 | tag_out: &mut [u8], 136 | ) { 137 | let mut nonce = make_counter(sequence_number); 138 | { 139 | let (a, b) = plaintext_in_ciphertext_out.split_at_mut(4); 140 | self.sodium.chacha20_xor(a, &nonce, &self.k1); 141 | self.sodium.chacha20_xor_ic(b, &nonce, 1, &self.k2); 142 | } 143 | nonce.0[0] = 0; 144 | use sodium::poly1305::Key; 145 | let mut poly_key = Key([0; 32]); 146 | self.sodium.chacha20_xor(&mut poly_key.0, &nonce, &self.k2); 147 | let tag = self.sodium.poly1305_auth(plaintext_in_ciphertext_out, &poly_key); 148 | tag_out.clone_from_slice(&tag.0); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /thrussh/src/encoding.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | use byteorder::{ByteOrder, BigEndian}; 17 | use Error; 18 | use cryptovec::CryptoVec; 19 | 20 | #[doc(hidden)] 21 | pub trait Bytes { 22 | fn bytes(&self) -> &[u8]; 23 | } 24 | 25 | impl> Bytes for A { 26 | fn bytes(&self) -> &[u8] { 27 | self.as_ref().as_bytes() 28 | } 29 | } 30 | 31 | /// Encode in the SSH format. 32 | pub trait Encoding { 33 | /// Push an SSH-encoded string to `self`. 34 | fn extend_ssh_string(&mut self, s: &[u8]); 35 | /// Push an SSH-encoded blank string of length `s` to `self`. 36 | fn extend_ssh_string_blank(&mut self, s: usize) -> &mut [u8]; 37 | /// Push an SSH-encoded multiple-precision integer. 38 | fn extend_ssh_mpint(&mut self, s: &[u8]); 39 | /// Push an SSH-encoded list. 40 | fn extend_list>(&mut self, list: I); 41 | /// Push an SSH-encoded empty list. 42 | fn write_empty_list(&mut self); 43 | } 44 | 45 | /// Encoding length of the given mpint. 46 | pub fn mpint_len(s: &[u8]) -> usize { 47 | let mut i = 0; 48 | while i < s.len() && s[i] == 0 { 49 | i += 1 50 | } 51 | (if s[i] & 0x80 != 0 { 5 } else { 4 }) + s.len() - i 52 | } 53 | 54 | 55 | impl Encoding for CryptoVec { 56 | fn extend_ssh_string(&mut self, s: &[u8]) { 57 | self.push_u32_be(s.len() as u32); 58 | self.extend(s); 59 | } 60 | fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] { 61 | self.push_u32_be(len as u32); 62 | let current = self.len(); 63 | self.resize(current + len); 64 | &mut self[current..] 65 | } 66 | fn extend_ssh_mpint(&mut self, s: &[u8]) { 67 | // Skip initial 0s. 68 | let mut i = 0; 69 | while i < s.len() && s[i] == 0 { 70 | i += 1 71 | } 72 | // If the first non-zero is >= 128, write its length (u32, BE), followed by 0. 73 | if s[i] & 0x80 != 0 { 74 | 75 | self.push_u32_be((s.len() - i + 1) as u32); 76 | self.push(0) 77 | 78 | } else { 79 | 80 | self.push_u32_be((s.len() - i) as u32); 81 | 82 | } 83 | self.extend(&s[i..]); 84 | } 85 | 86 | 87 | fn extend_list>(&mut self, list: I) { 88 | let len0 = self.len(); 89 | self.extend(&[0, 0, 0, 0]); 90 | let mut first = true; 91 | for i in list { 92 | if !first { 93 | self.push(b',') 94 | } else { 95 | first = false; 96 | } 97 | self.extend(i.bytes()) 98 | } 99 | let len = (self.len() - len0 - 4) as u32; 100 | 101 | BigEndian::write_u32(&mut self[len0..], len); 102 | } 103 | 104 | fn write_empty_list(&mut self) { 105 | self.extend(&[0, 0, 0, 0]); 106 | } 107 | } 108 | 109 | /// A cursor-like trait to read SSH-encoded things. 110 | pub trait Reader { 111 | /// Create an SSH reader for `self`. 112 | fn reader<'a>(&'a self, starting_at: usize) -> Position<'a>; 113 | } 114 | 115 | impl Reader for CryptoVec { 116 | fn reader<'a>(&'a self, starting_at: usize) -> Position<'a> { 117 | Position { 118 | s: &self, 119 | position: starting_at, 120 | } 121 | } 122 | } 123 | 124 | impl Reader for [u8] { 125 | fn reader<'a>(&'a self, starting_at: usize) -> Position<'a> { 126 | Position { 127 | s: self, 128 | position: starting_at, 129 | } 130 | } 131 | } 132 | 133 | /// A cursor-like type to read SSH-encoded values. 134 | #[derive(Debug)] 135 | pub struct Position<'a> { 136 | s: &'a [u8], 137 | #[doc(hidden)] 138 | pub position: usize, 139 | } 140 | impl<'a> Position<'a> { 141 | /// Read one string from this reader. 142 | pub fn read_string(&mut self) -> Result<&'a [u8], Error> { 143 | let len = self.read_u32()? as usize; 144 | if self.position + len <= self.s.len() { 145 | let result = &self.s[self.position..(self.position + len)]; 146 | self.position += len; 147 | Ok(result) 148 | } else { 149 | Err(Error::IndexOutOfBounds) 150 | } 151 | } 152 | /// Read a `u32` from this reader. 153 | pub fn read_u32(&mut self) -> Result { 154 | if self.position + 4 <= self.s.len() { 155 | let u = BigEndian::read_u32(&self.s[self.position..]); 156 | self.position += 4; 157 | Ok(u) 158 | } else { 159 | Err(Error::IndexOutOfBounds) 160 | } 161 | } 162 | /// Read one byte from this reader. 163 | pub fn read_byte(&mut self) -> Result { 164 | if self.position + 1 <= self.s.len() { 165 | let u = self.s[self.position]; 166 | self.position += 1; 167 | Ok(u) 168 | } else { 169 | Err(Error::IndexOutOfBounds) 170 | } 171 | } 172 | 173 | /// Read one byte from this reader. 174 | pub fn read_mpint(&mut self) -> Result<&'a [u8], Error> { 175 | let len = self.read_u32()? as usize; 176 | if self.position + len <= self.s.len() { 177 | let result = &self.s[self.position..(self.position + len)]; 178 | self.position += len; 179 | Ok(result) 180 | } else { 181 | Err(Error::IndexOutOfBounds) 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /thrussh-libsodium/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | use libc::{c_int, c_ulonglong}; 3 | use std::marker::PhantomData; 4 | 5 | extern "C" { 6 | 7 | fn sodium_init() -> c_int; 8 | 9 | // Signatures 10 | fn crypto_sign_keypair(pk: *mut u8, sk: *mut u8) -> c_int; 11 | fn crypto_sign_detached(sig: *mut u8, siglen: *mut c_ulonglong, m: *const u8, mlen: c_ulonglong, sk: *const u8) -> c_int; 12 | fn crypto_sign_verify_detached(sig: *const u8, m: *const u8, mlen: c_ulonglong, pk: *const u8) -> c_int; 13 | 14 | // Diffie-Hellman 15 | fn crypto_scalarmult_curve25519_base(q: *mut u8, n: *const u8) -> c_int; 16 | fn crypto_scalarmult_curve25519(q: *mut u8, n: *const u8, p: *const u8) -> c_int; 17 | 18 | // Chacha20 19 | fn crypto_stream_chacha20_xor(c: *mut u8, m: *const u8, mlen: c_ulonglong, n: *const u8, k: *const u8) -> c_int; 20 | fn crypto_stream_chacha20_xor_ic(c: *mut u8, m: *const u8, mlen: c_ulonglong, n: *const u8, ic: u64, k: *const u8) -> c_int; 21 | 22 | // Poly1305 23 | fn crypto_onetimeauth(out: *mut u8, in_: *const u8, inlen: c_ulonglong, k: *const u8) -> c_int; 24 | fn crypto_onetimeauth_verify(h: *const u8, in_: *const u8, inlen: c_ulonglong, k: *const u8) -> c_int; 25 | } 26 | 27 | /// "Proof" of initialisation of the library 28 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 29 | pub struct Sodium(PhantomData<()>); 30 | unsafe impl Send for Sodium{} 31 | unsafe impl Sync for Sodium{} 32 | 33 | impl Sodium { 34 | pub fn new() -> Self { 35 | unsafe { sodium_init(); } 36 | Sodium(PhantomData) 37 | } 38 | } 39 | 40 | pub mod chacha20 { 41 | use super::*; 42 | pub const NONCE_BYTES: usize = 8; 43 | pub const KEY_BYTES: usize = 32; 44 | pub struct Nonce(pub [u8; NONCE_BYTES]); 45 | pub struct Key(pub [u8; KEY_BYTES]); 46 | 47 | impl Sodium { 48 | pub fn chacha20_xor(&self, c: &mut [u8], n: &Nonce, k: &Key) { 49 | unsafe { 50 | crypto_stream_chacha20_xor(c.as_mut_ptr(), c.as_ptr(), c.len() as c_ulonglong, n.0.as_ptr(), k.0.as_ptr()); 51 | } 52 | } 53 | 54 | pub fn chacha20_xor_ic(&self, c: &mut [u8], n: &Nonce, ic: u64, k: &Key) { 55 | unsafe { 56 | crypto_stream_chacha20_xor_ic(c.as_mut_ptr(), c.as_ptr(), c.len() as c_ulonglong, n.0.as_ptr(), ic, k.0.as_ptr()); 57 | } 58 | } 59 | } 60 | } 61 | 62 | pub mod poly1305 { 63 | use super::*; 64 | pub const KEY_BYTES: usize = 32; 65 | pub const TAG_BYTES: usize = 16; 66 | pub struct Key(pub [u8; KEY_BYTES]); 67 | pub struct Tag(pub [u8; TAG_BYTES]); 68 | impl Sodium { 69 | pub fn poly1305_auth(&self, m: &[u8], key: &Key) -> Tag { 70 | let mut tag = Tag([0; TAG_BYTES]); 71 | unsafe { 72 | crypto_onetimeauth(tag.0.as_mut_ptr(), m.as_ptr(), m.len() as c_ulonglong, key.0.as_ptr()); 73 | } 74 | tag 75 | } 76 | pub fn poly1305_verify(&self, tag: &[u8], m: &[u8], key: &Key) -> bool { 77 | if tag.len() != TAG_BYTES { 78 | false 79 | } else { 80 | unsafe { 81 | crypto_onetimeauth_verify(tag.as_ptr(), m.as_ptr(), m.len() as c_ulonglong, key.0.as_ptr()) == 0 82 | } 83 | } 84 | } 85 | } 86 | } 87 | 88 | pub mod ed25519 { 89 | use super::*; 90 | pub const PUBLICKEY_BYTES: usize = 32; 91 | pub const SECRETKEY_BYTES: usize = 64; 92 | pub const SIGNATURE_BYTES: usize = 64; 93 | 94 | /// Ed25519 public key. 95 | #[derive(Debug, PartialEq, Eq, Clone)] 96 | pub struct PublicKey { 97 | /// Actual key 98 | pub key: [u8; PUBLICKEY_BYTES], 99 | /// "Proof" of initialisation of the library 100 | pub sodium: Sodium, 101 | } 102 | 103 | impl PublicKey { 104 | pub fn new_zeroed() -> Self { 105 | PublicKey { key: [0; PUBLICKEY_BYTES], sodium: Sodium::new() } 106 | } 107 | } 108 | 109 | /// Ed25519 secret key. 110 | #[derive(Clone)] 111 | pub struct SecretKey { 112 | /// Actual key 113 | pub key: [u8; SECRETKEY_BYTES], 114 | /// "Proof" of initialisation of the library 115 | pub sodium: Sodium, 116 | } 117 | 118 | impl SecretKey { 119 | pub fn new_zeroed() -> Self { 120 | SecretKey { key: [0; SECRETKEY_BYTES], sodium: Sodium::new() } 121 | } 122 | } 123 | 124 | pub struct Signature(pub [u8; SIGNATURE_BYTES]); 125 | 126 | /// Generate a key pair. 127 | pub fn keypair() -> (PublicKey, SecretKey) { 128 | unsafe { 129 | let sodium = Sodium::new(); 130 | let mut pk = PublicKey { key: [0; PUBLICKEY_BYTES], sodium }; 131 | let mut sk = SecretKey { key: [0; SECRETKEY_BYTES], sodium }; 132 | crypto_sign_keypair(pk.key.as_mut_ptr(), sk.key.as_mut_ptr()); 133 | (pk, sk) 134 | } 135 | } 136 | 137 | /// Verify a signature, `sig` could as well be a `Signature`. 138 | pub fn verify_detached(sig: &[u8], m: &[u8], pk: &PublicKey) -> bool { 139 | if sig.len() == SIGNATURE_BYTES { 140 | unsafe { 141 | crypto_sign_verify_detached(sig.as_ptr(), m.as_ptr(), m.len() as c_ulonglong, pk.key.as_ptr()) == 0 142 | } 143 | } else { 144 | false 145 | } 146 | } 147 | 148 | /// Sign a message with a secret key. 149 | pub fn sign_detached(m: &[u8], sk: &SecretKey) -> Signature { 150 | let mut sig = Signature([0; SIGNATURE_BYTES]); 151 | let mut sig_len = 0; 152 | unsafe { 153 | crypto_sign_detached( 154 | sig.0.as_mut_ptr(), &mut sig_len, 155 | m.as_ptr(), m.len() as c_ulonglong, 156 | sk.key.as_ptr() 157 | ); 158 | } 159 | sig 160 | } 161 | } 162 | 163 | pub mod scalarmult { 164 | use super::*; 165 | pub const BYTES: usize = 32; 166 | 167 | #[derive(Debug)] 168 | pub struct Scalar(pub [u8; BYTES]); 169 | #[derive(Debug)] 170 | pub struct GroupElement(pub [u8; BYTES]); 171 | 172 | impl Sodium { 173 | 174 | pub fn scalarmult_base(&self, n: &Scalar) -> GroupElement{ 175 | let mut q = GroupElement([0; BYTES]); 176 | unsafe { 177 | crypto_scalarmult_curve25519_base(q.0.as_mut_ptr(), n.0.as_ptr()); 178 | } 179 | q 180 | } 181 | 182 | pub fn scalarmult(&self, n: &Scalar, p: &GroupElement) -> GroupElement { 183 | let mut q = GroupElement([0; BYTES]); 184 | unsafe { 185 | crypto_scalarmult_curve25519(q.0.as_mut_ptr(), n.0.as_ptr(), p.0.as_ptr()); 186 | } 187 | q 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /thrussh-keys/src/encoding.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | use byteorder::{ByteOrder, BigEndian, WriteBytesExt}; 17 | use {ErrorKind, Error}; 18 | use cryptovec::CryptoVec; 19 | 20 | #[doc(hidden)] 21 | pub trait Bytes { 22 | fn bytes(&self) -> &[u8]; 23 | } 24 | 25 | impl> Bytes for A { 26 | fn bytes(&self) -> &[u8] { 27 | self.as_ref().as_bytes() 28 | } 29 | } 30 | 31 | /// Encode in the SSH format. 32 | pub trait Encoding { 33 | /// Push an SSH-encoded string to `self`. 34 | fn extend_ssh_string(&mut self, s: &[u8]); 35 | /// Push an SSH-encoded blank string of length `s` to `self`. 36 | fn extend_ssh_string_blank(&mut self, s: usize) -> &mut [u8]; 37 | /// Push an SSH-encoded multiple-precision integer. 38 | fn extend_ssh_mpint(&mut self, s: &[u8]); 39 | /// Push an SSH-encoded list. 40 | fn extend_list>(&mut self, list: I); 41 | /// Push an SSH-encoded empty list. 42 | fn write_empty_list(&mut self); 43 | } 44 | 45 | /// Encoding length of the given mpint. 46 | pub fn mpint_len(s: &[u8]) -> usize { 47 | let mut i = 0; 48 | while i < s.len() && s[i] == 0 { 49 | i += 1 50 | } 51 | (if s[i] & 0x80 != 0 { 5 } else { 4 }) + s.len() - i 52 | } 53 | 54 | 55 | impl Encoding for Vec { 56 | fn extend_ssh_string(&mut self, s: &[u8]) { 57 | self.write_u32::(s.len() as u32).unwrap(); 58 | self.extend(s); 59 | } 60 | fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] { 61 | self.write_u32::(len as u32).unwrap(); 62 | let current = self.len(); 63 | self.resize(current + len, 0u8); 64 | &mut self[current..] 65 | } 66 | fn extend_ssh_mpint(&mut self, s: &[u8]) { 67 | // Skip initial 0s. 68 | let mut i = 0; 69 | while i < s.len() && s[i] == 0 { 70 | i += 1 71 | } 72 | // If the first non-zero is >= 128, write its length (u32, BE), followed by 0. 73 | if s[i] & 0x80 != 0 { 74 | 75 | self.write_u32::((s.len() - i + 1) as u32).unwrap(); 76 | self.push(0) 77 | 78 | } else { 79 | 80 | self.write_u32::((s.len() - i) as u32).unwrap(); 81 | 82 | } 83 | self.extend(&s[i..]); 84 | } 85 | 86 | 87 | fn extend_list>(&mut self, list: I) { 88 | let len0 = self.len(); 89 | self.extend(&[0, 0, 0, 0]); 90 | let mut first = true; 91 | for i in list { 92 | if !first { 93 | self.push(b',') 94 | } else { 95 | first = false; 96 | } 97 | self.extend(i.bytes()) 98 | } 99 | let len = (self.len() - len0 - 4) as u32; 100 | 101 | BigEndian::write_u32(&mut self[len0..], len); 102 | } 103 | 104 | fn write_empty_list(&mut self) { 105 | self.extend(&[0, 0, 0, 0]); 106 | } 107 | } 108 | 109 | impl Encoding for CryptoVec { 110 | fn extend_ssh_string(&mut self, s: &[u8]) { 111 | self.push_u32_be(s.len() as u32); 112 | self.extend(s); 113 | } 114 | fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] { 115 | self.push_u32_be(len as u32); 116 | let current = self.len(); 117 | self.resize(current + len); 118 | &mut self[current..] 119 | } 120 | fn extend_ssh_mpint(&mut self, s: &[u8]) { 121 | // Skip initial 0s. 122 | let mut i = 0; 123 | while i < s.len() && s[i] == 0 { 124 | i += 1 125 | } 126 | // If the first non-zero is >= 128, write its length (u32, BE), followed by 0. 127 | if s[i] & 0x80 != 0 { 128 | 129 | self.push_u32_be((s.len() - i + 1) as u32); 130 | self.push(0) 131 | 132 | } else { 133 | 134 | self.push_u32_be((s.len() - i) as u32); 135 | 136 | } 137 | self.extend(&s[i..]); 138 | } 139 | 140 | 141 | fn extend_list>(&mut self, list: I) { 142 | let len0 = self.len(); 143 | self.extend(&[0, 0, 0, 0]); 144 | let mut first = true; 145 | for i in list { 146 | if !first { 147 | self.push(b',') 148 | } else { 149 | first = false; 150 | } 151 | self.extend(i.bytes()) 152 | } 153 | let len = (self.len() - len0 - 4) as u32; 154 | 155 | BigEndian::write_u32(&mut self[len0..], len); 156 | } 157 | 158 | fn write_empty_list(&mut self) { 159 | self.extend(&[0, 0, 0, 0]); 160 | } 161 | } 162 | 163 | /// A cursor-like trait to read SSH-encoded things. 164 | pub trait Reader { 165 | /// Create an SSH reader for `self`. 166 | fn reader<'a>(&'a self, starting_at: usize) -> Position<'a>; 167 | } 168 | 169 | impl Reader for CryptoVec { 170 | fn reader<'a>(&'a self, starting_at: usize) -> Position<'a> { 171 | Position { 172 | s: &self, 173 | position: starting_at, 174 | } 175 | } 176 | } 177 | 178 | impl Reader for [u8] { 179 | fn reader<'a>(&'a self, starting_at: usize) -> Position<'a> { 180 | Position { 181 | s: self, 182 | position: starting_at, 183 | } 184 | } 185 | } 186 | 187 | /// A cursor-like type to read SSH-encoded values. 188 | #[derive(Debug)] 189 | pub struct Position<'a> { 190 | s: &'a [u8], 191 | #[doc(hidden)] 192 | pub position: usize, 193 | } 194 | impl<'a> Position<'a> { 195 | /// Read one string from this reader. 196 | pub fn read_string(&mut self) -> Result<&'a [u8], Error> { 197 | let len = self.read_u32()? as usize; 198 | if self.position + len <= self.s.len() { 199 | let result = &self.s[self.position..(self.position + len)]; 200 | self.position += len; 201 | Ok(result) 202 | } else { 203 | Err(ErrorKind::IndexOutOfBounds.into()) 204 | } 205 | } 206 | /// Read a `u32` from this reader. 207 | pub fn read_u32(&mut self) -> Result { 208 | if self.position + 4 <= self.s.len() { 209 | let u = BigEndian::read_u32(&self.s[self.position..]); 210 | self.position += 4; 211 | Ok(u) 212 | } else { 213 | Err(ErrorKind::IndexOutOfBounds.into()) 214 | } 215 | } 216 | /// Read one byte from this reader. 217 | pub fn read_byte(&mut self) -> Result { 218 | if self.position + 1 <= self.s.len() { 219 | let u = self.s[self.position]; 220 | self.position += 1; 221 | Ok(u) 222 | } else { 223 | Err(ErrorKind::IndexOutOfBounds.into()) 224 | } 225 | } 226 | 227 | /// Read one byte from this reader. 228 | pub fn read_mpint(&mut self) -> Result<&'a [u8], Error> { 229 | let len = self.read_u32()? as usize; 230 | if self.position + len <= self.s.len() { 231 | let result = &self.s[self.position..(self.position + len)]; 232 | self.position += len; 233 | Ok(result) 234 | } else { 235 | Err(ErrorKind::IndexOutOfBounds.into()) 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /thrussh/src/negotiation.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | use Error; 16 | use thrussh_keys::key; 17 | use kex; 18 | use cipher; 19 | use msg; 20 | use std::str::from_utf8; 21 | // use super::mac; // unimplemented 22 | // use super::compression; // unimplemented 23 | use cryptovec::CryptoVec; 24 | use thrussh_keys::encoding::{Encoding, Reader}; 25 | use thrussh_keys::key::{PublicKey, KeyPair}; 26 | use openssl::rand; 27 | 28 | #[derive(Debug)] 29 | pub struct Names { 30 | pub kex: kex::Name, 31 | pub key: key::Name, 32 | pub cipher: cipher::Name, 33 | pub mac: Option<&'static str>, 34 | pub ignore_guessed: bool, 35 | } 36 | 37 | /// Lists of preferred algorithms. This is normally hard-coded into implementations. 38 | #[derive(Debug)] 39 | pub struct Preferred { 40 | /// Preferred key exchange algorithms. 41 | pub kex: &'static [kex::Name], 42 | /// Preferred public key algorithms. 43 | pub key: &'static [key::Name], 44 | /// Preferred symmetric ciphers. 45 | pub cipher: &'static [cipher::Name], 46 | /// Preferred MAC algorithms. 47 | pub mac: &'static [&'static str], 48 | /// Preferred compression algorithms. 49 | pub compression: &'static [&'static str], 50 | } 51 | 52 | pub const DEFAULT: Preferred = Preferred { 53 | kex: &[kex::CURVE25519], 54 | key: &[key::ED25519, key::RSA_SHA2_256, key::RSA_SHA2_512], 55 | cipher: &[cipher::chacha20poly1305::NAME], 56 | mac: &["none"], 57 | compression: &["none"], 58 | }; 59 | 60 | impl Default for Preferred { 61 | fn default() -> Preferred { 62 | DEFAULT 63 | } 64 | } 65 | 66 | /// Named algorithms. 67 | pub trait Named { 68 | /// The name of this algorithm. 69 | fn name(&self) -> &'static str; 70 | } 71 | 72 | impl Named for () { 73 | fn name(&self) -> &'static str { 74 | "" 75 | } 76 | } 77 | 78 | use thrussh_keys::key::{ED25519, SSH_RSA}; 79 | 80 | impl Named for PublicKey { 81 | fn name(&self) -> &'static str { 82 | match self { 83 | &PublicKey::Ed25519(_) => ED25519.0, 84 | &PublicKey::RSA { .. } => SSH_RSA, 85 | } 86 | } 87 | } 88 | 89 | impl Named for KeyPair { 90 | fn name(&self) -> &'static str { 91 | match self { 92 | &KeyPair::Ed25519 { .. } => ED25519.0, 93 | &KeyPair::RSA { ref hash, .. } => hash.name().0, 94 | } 95 | } 96 | } 97 | 98 | pub trait Select { 99 | fn select + Copy>(a: &[S], b: &[u8]) -> Option<(bool, S)>; 100 | 101 | fn read_kex(buffer: &[u8], pref: &Preferred) -> Result { 102 | let mut r = buffer.reader(17); 103 | let kex_string = try!(r.read_string()); 104 | let (kex_both_first, kex_algorithm) = if let Some(x) = Self::select(pref.kex, kex_string) { 105 | x 106 | } else { 107 | debug!( 108 | "Could not find common kex algorithm, other side only supports {:?}, we only support {:?}", 109 | from_utf8(kex_string), 110 | pref.kex 111 | ); 112 | return Err(Error::NoCommonKexAlgo); 113 | }; 114 | 115 | let key_string = try!(r.read_string()); 116 | let (key_both_first, key_algorithm) = if let Some(x) = Self::select(pref.key, key_string) { 117 | x 118 | } else { 119 | debug!( 120 | "Could not find common key algorithm, other side only supports {:?}, we only support {:?}", 121 | from_utf8(key_string), 122 | pref.key 123 | ); 124 | return Err(Error::NoCommonKeyAlgo); 125 | }; 126 | 127 | let cipher_string = try!(r.read_string()); 128 | let cipher = Self::select(pref.cipher, cipher_string); 129 | if cipher.is_none() { 130 | debug!( 131 | "Could not find common cipher, other side only supports {:?}, we only support {:?}", 132 | from_utf8(cipher_string), 133 | pref.cipher 134 | ); 135 | return Err(Error::NoCommonCipher); 136 | } 137 | try!(r.read_string()); // SERVER_TO_CLIENT 138 | let mac = Self::select(pref.mac, try!(r.read_string())); 139 | let mac = mac.and_then(|(_, x)| Some(x)); 140 | try!(r.read_string()); // SERVER_TO_CLIENT 141 | try!(r.read_string()); // 142 | try!(r.read_string()); // 143 | try!(r.read_string()); // 144 | 145 | let follows = try!(r.read_byte()) != 0; 146 | match (cipher, mac, follows) { 147 | (Some((_, cip)), mac, fol) => { 148 | Ok(Names { 149 | kex: kex_algorithm, 150 | key: key_algorithm, 151 | cipher: cip, 152 | mac: mac, 153 | // Ignore the next packet if (1) it follows and (2) it's not the correct guess. 154 | ignore_guessed: fol && !(kex_both_first && key_both_first), 155 | }) 156 | } 157 | _ => Err(Error::KexInit), 158 | } 159 | } 160 | } 161 | 162 | pub struct Server; 163 | pub struct Client; 164 | 165 | impl Select for Server { 166 | fn select + Copy>(server_list: &[S], client_list: &[u8]) -> Option<(bool, S)> { 167 | let mut both_first_choice = true; 168 | for c in client_list.split(|&x| x == b',') { 169 | for &s in server_list { 170 | if c == s.as_ref().as_bytes() { 171 | return Some((both_first_choice, s)); 172 | } 173 | both_first_choice = false 174 | } 175 | } 176 | None 177 | } 178 | } 179 | 180 | impl Select for Client { 181 | fn select + Copy>(client_list: &[S], server_list: &[u8]) -> Option<(bool, S)> { 182 | let mut both_first_choice = true; 183 | for &c in client_list { 184 | for s in server_list.split(|&x| x == b',') { 185 | if s == c.as_ref().as_bytes() { 186 | return Some((both_first_choice, c)); 187 | } 188 | both_first_choice = false 189 | } 190 | } 191 | None 192 | } 193 | } 194 | 195 | 196 | pub fn write_kex(prefs: &Preferred, buf: &mut CryptoVec) -> Result<(), Error> { 197 | 198 | // buf.clear(); 199 | buf.push(msg::KEXINIT); 200 | 201 | let mut cookie = [0; 16]; 202 | rand::rand_bytes(&mut cookie)?; 203 | 204 | buf.extend(&cookie); // cookie 205 | buf.extend_list(prefs.kex.iter()); // kex algo 206 | 207 | buf.extend_list(prefs.key.iter()); 208 | 209 | buf.extend_list(prefs.cipher.iter()); // cipher client to server 210 | buf.extend_list(prefs.cipher.iter()); // cipher server to client 211 | 212 | buf.extend_list(prefs.mac.iter()); // mac client to server 213 | buf.extend_list(prefs.mac.iter()); // mac server to client 214 | buf.extend_list(prefs.compression.iter()); // compress client to server 215 | buf.extend_list(prefs.compression.iter()); // compress server to client 216 | 217 | buf.write_empty_list(); // languages client to server 218 | buf.write_empty_list(); // languagesserver to client 219 | 220 | buf.push(0); // doesn't follow 221 | buf.extend(&[0, 0, 0, 0]); // reserved 222 | Ok(()) 223 | } 224 | -------------------------------------------------------------------------------- /thrussh/src/kex.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | use byteorder::{ByteOrder, BigEndian}; 16 | use std; 17 | use Error; 18 | use msg; 19 | 20 | use cryptovec::CryptoVec; 21 | use session::Exchange; 22 | use key; 23 | use cipher; 24 | use thrussh_keys::encoding::Encoding; 25 | use openssl; 26 | use sodium; 27 | 28 | #[doc(hidden)] 29 | pub struct Algorithm { 30 | local_secret: Option, 31 | shared_secret: Option, 32 | } 33 | 34 | impl std::fmt::Debug for Algorithm { 35 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 36 | write!( 37 | f, 38 | "Algorithm {{ local_secret: [hidden], shared_secret: [hidden] }}", 39 | ) 40 | } 41 | } 42 | 43 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 44 | pub struct Name(&'static str); 45 | impl AsRef for Name { 46 | fn as_ref(&self) -> &str { 47 | self.0 48 | } 49 | } 50 | pub const CURVE25519: Name = Name("curve25519-sha256@libssh.org"); 51 | 52 | // We used to support curve "NIST P-256" here, but the security of 53 | // that curve is controversial, see 54 | // http://safecurves.cr.yp.to/rigid.html 55 | 56 | impl Algorithm { 57 | #[doc(hidden)] 58 | pub fn server_dh( 59 | _name: Name, 60 | exchange: &mut Exchange, 61 | payload: &[u8], 62 | ) -> Result { 63 | debug!("server_dh"); 64 | 65 | assert_eq!(payload[0], msg::KEX_ECDH_INIT); 66 | let mut client_pubkey = GroupElement([0; 32]); 67 | { 68 | let pubkey_len = BigEndian::read_u32(&payload[1..]) as usize; 69 | client_pubkey.0.clone_from_slice( 70 | &payload[5..(5 + pubkey_len)], 71 | ) 72 | }; 73 | debug!("client_pubkey: {:?}", client_pubkey); 74 | use sodium::scalarmult::*; 75 | use openssl::rand::*; 76 | let mut server_secret = Scalar([0; 32]); 77 | rand_bytes(&mut server_secret.0)?; 78 | let sodium = sodium::Sodium::new(); 79 | let server_pubkey = sodium.scalarmult_base(&server_secret); 80 | 81 | // fill exchange. 82 | exchange.server_ephemeral.clear(); 83 | exchange.server_ephemeral.extend(&server_pubkey.0); 84 | let shared = sodium.scalarmult(&server_secret, &client_pubkey); 85 | Ok(Algorithm { 86 | local_secret: None, 87 | shared_secret: Some(shared), 88 | }) 89 | } 90 | 91 | #[doc(hidden)] 92 | pub fn client_dh( 93 | _name: Name, 94 | client_ephemeral: &mut CryptoVec, 95 | buf: &mut CryptoVec, 96 | ) -> Result { 97 | 98 | use sodium::scalarmult::*; 99 | use openssl::rand::*; 100 | let mut client_secret = Scalar([0; 32]); 101 | rand_bytes(&mut client_secret.0)?; 102 | let sodium = sodium::Sodium::new(); 103 | let client_pubkey = sodium.scalarmult_base(&client_secret); 104 | 105 | // fill exchange. 106 | client_ephemeral.clear(); 107 | client_ephemeral.extend(&client_pubkey.0); 108 | 109 | buf.push(msg::KEX_ECDH_INIT); 110 | buf.extend_ssh_string(&client_pubkey.0); 111 | 112 | 113 | Ok(Algorithm { 114 | local_secret: Some(client_secret), 115 | shared_secret: None, 116 | }) 117 | } 118 | 119 | pub fn compute_shared_secret(&mut self, remote_pubkey_: &[u8]) -> Result<(), Error> { 120 | let local_secret = std::mem::replace(&mut self.local_secret, None).unwrap(); 121 | 122 | use sodium::scalarmult::*; 123 | let mut remote_pubkey = GroupElement([0; 32]); 124 | remote_pubkey.0.clone_from_slice(remote_pubkey_); 125 | let sodium = sodium::Sodium::new(); 126 | let shared = sodium.scalarmult(&local_secret, &remote_pubkey); 127 | self.shared_secret = Some(shared); 128 | Ok(()) 129 | } 130 | 131 | pub fn compute_exchange_hash( 132 | &self, 133 | key: &K, 134 | exchange: &Exchange, 135 | buffer: &mut CryptoVec, 136 | ) -> Result { 137 | // Computing the exchange hash, see page 7 of RFC 5656. 138 | buffer.clear(); 139 | buffer.extend_ssh_string(&exchange.client_id); 140 | buffer.extend_ssh_string(&exchange.server_id); 141 | buffer.extend_ssh_string(&exchange.client_kex_init); 142 | buffer.extend_ssh_string(&exchange.server_kex_init); 143 | 144 | 145 | key.push_to(buffer); 146 | buffer.extend_ssh_string(&exchange.client_ephemeral); 147 | buffer.extend_ssh_string(&exchange.server_ephemeral); 148 | 149 | if let Some(ref shared) = self.shared_secret { 150 | buffer.extend_ssh_mpint(&shared.0); 151 | } 152 | use openssl::hash::*; 153 | let hash = { 154 | let mut hasher = Hasher::new(MessageDigest::sha256())?; 155 | hasher.update(&buffer)?; 156 | hasher.finish()? 157 | }; 158 | Ok(hash) 159 | } 160 | 161 | 162 | pub fn compute_keys( 163 | &self, 164 | session_id: &openssl::hash::DigestBytes, 165 | exchange_hash: &openssl::hash::DigestBytes, 166 | buffer: &mut CryptoVec, 167 | key: &mut CryptoVec, 168 | cipher: cipher::Name, 169 | is_server: bool, 170 | ) -> Result { 171 | let cipher = match cipher { 172 | super::cipher::chacha20poly1305::NAME => &super::cipher::chacha20poly1305::CIPHER, 173 | _ => unreachable!(), 174 | }; 175 | 176 | // https://tools.ietf.org/html/rfc4253#section-7.2 177 | let mut compute_key = |c, key: &mut CryptoVec, len| -> Result<(), Error> { 178 | buffer.clear(); 179 | key.clear(); 180 | 181 | if let Some(ref shared) = self.shared_secret { 182 | buffer.extend_ssh_mpint(&shared.0); 183 | } 184 | 185 | buffer.extend(exchange_hash.as_ref()); 186 | buffer.push(c); 187 | buffer.extend(session_id.as_ref()); 188 | use openssl::hash::*; 189 | let hash = { 190 | let mut hasher = Hasher::new(MessageDigest::sha256())?; 191 | hasher.update(&buffer)?; 192 | hasher.finish()? 193 | }; 194 | key.extend(hash.as_ref()); 195 | 196 | while key.len() < len { 197 | // extend. 198 | buffer.clear(); 199 | if let Some(ref shared) = self.shared_secret { 200 | buffer.extend_ssh_mpint(&shared.0); 201 | } 202 | buffer.extend(exchange_hash.as_ref()); 203 | buffer.extend(key); 204 | let hash = { 205 | let mut hasher = Hasher::new(MessageDigest::sha256())?; 206 | hasher.update(&buffer)?; 207 | hasher.finish()? 208 | }; 209 | key.extend(&hash.as_ref()); 210 | } 211 | Ok(()) 212 | }; 213 | 214 | let (local_to_remote, remote_to_local) = if is_server { 215 | (b'D', b'C') 216 | } else { 217 | (b'C', b'D') 218 | }; 219 | 220 | compute_key(local_to_remote, key, cipher.key_len)?; 221 | let local_to_remote = (cipher.make_sealing_cipher)(key); 222 | 223 | compute_key(remote_to_local, key, cipher.key_len)?; 224 | let remote_to_local = (cipher.make_opening_cipher)(key); 225 | 226 | Ok(super::cipher::CipherPair { 227 | local_to_remote: local_to_remote, 228 | remote_to_local: remote_to_local, 229 | }) 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /thrussh/src/cipher/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | use byteorder::{ByteOrder, BigEndian}; 16 | use Error; 17 | use std; 18 | use sshbuffer::SSHBuffer; 19 | use std::num::Wrapping; 20 | use tokio::io::AsyncRead; 21 | use read_exact_from::*; 22 | use futures::{Future, Async, Poll}; 23 | use std::sync::Arc; 24 | pub mod chacha20poly1305; 25 | pub mod clear; 26 | use cryptovec::CryptoVec; 27 | 28 | 29 | pub struct Cipher { 30 | pub name: Name, 31 | pub key_len: usize, 32 | pub make_opening_cipher: fn(key: &[u8]) -> OpeningCipher, 33 | pub make_sealing_cipher: fn(key: &[u8]) -> SealingCipher, 34 | } 35 | 36 | pub enum OpeningCipher { 37 | Clear(clear::Key), 38 | Chacha20Poly1305(chacha20poly1305::OpeningKey), 39 | } 40 | 41 | impl<'a> OpeningCipher { 42 | fn as_opening_key(&self) -> &OpeningKey { 43 | match *self { 44 | OpeningCipher::Clear(ref key) => key, 45 | OpeningCipher::Chacha20Poly1305(ref key) => key, 46 | } 47 | } 48 | } 49 | 50 | pub enum SealingCipher { 51 | Clear(clear::Key), 52 | Chacha20Poly1305(chacha20poly1305::SealingKey), 53 | } 54 | 55 | impl<'a> SealingCipher { 56 | fn as_sealing_key(&'a self) -> &'a SealingKey { 57 | match *self { 58 | SealingCipher::Clear(ref key) => key, 59 | SealingCipher::Chacha20Poly1305(ref key) => key, 60 | } 61 | } 62 | } 63 | 64 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 65 | pub struct Name(&'static str); 66 | impl AsRef for Name { 67 | fn as_ref(&self) -> &str { 68 | self.0 69 | } 70 | } 71 | 72 | pub struct CipherPair { 73 | pub local_to_remote: SealingCipher, 74 | pub remote_to_local: OpeningCipher, 75 | } 76 | 77 | impl std::fmt::Debug for CipherPair { 78 | fn fmt(&self, _: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { 79 | Ok(()) // TODO? 80 | } 81 | } 82 | 83 | pub const CLEAR_PAIR: CipherPair = CipherPair { 84 | local_to_remote: SealingCipher::Clear(clear::Key), 85 | remote_to_local: OpeningCipher::Clear(clear::Key), 86 | }; 87 | 88 | pub trait OpeningKey { 89 | 90 | fn decrypt_packet_length(&self, seqn: u32, encrypted_packet_length: [u8; 4]) -> [u8; 4]; 91 | 92 | fn tag_len(&self) -> usize; 93 | 94 | fn open<'a>( 95 | &self, 96 | seqn: u32, 97 | ciphertext_in_plaintext_out: &'a mut [u8], 98 | tag: &[u8], 99 | ) -> Result<&'a [u8], Error>; 100 | } 101 | 102 | pub trait SealingKey { 103 | 104 | fn padding_length(&self, plaintext: &[u8]) -> usize; 105 | 106 | fn fill_padding(&self, padding_out: &mut [u8]); 107 | 108 | fn tag_len(&self) -> usize; 109 | 110 | fn seal(&self, seqn: u32, plaintext_in_ciphertext_out: &mut [u8], tag_out: &mut [u8]); 111 | } 112 | 113 | enum CipherReadState { 114 | Len { 115 | len: ReadExact, 116 | buffer: SSHBuffer, 117 | pair: Arc, 118 | }, 119 | Body { 120 | body: ReadExact, 121 | buffer: SSHBuffer, 122 | pair: Arc, 123 | }, 124 | } 125 | 126 | pub struct CipherRead(Option>); 127 | 128 | impl CipherRead { 129 | pub fn try_abort(&mut self) -> Option<(R, SSHBuffer)> { 130 | if let Some(CipherReadState::Len { 131 | mut len, 132 | buffer, 133 | pair, 134 | }) = self.0.take() 135 | { 136 | // Aborting, and abandoning the 4 bytes of buffer. 137 | if let Some((r, _)) = len.try_abort() { 138 | return Some((r, buffer)); 139 | } else { 140 | self.0 = Some(CipherReadState::Len { len, buffer, pair }) 141 | } 142 | } 143 | None 144 | } 145 | } 146 | 147 | impl Future for CipherRead { 148 | type Item = (R, SSHBuffer, usize); 149 | type Error = Error; 150 | fn poll(&mut self) -> Poll { 151 | loop { 152 | debug!("cipherread poll"); 153 | match self.0.take() { 154 | None => panic!("future is over"), 155 | Some(CipherReadState::Len { 156 | mut len, 157 | mut buffer, 158 | pair, 159 | }) => { 160 | if let Async::Ready((stream, len_)) = len.poll()? { 161 | { 162 | let key = pair.remote_to_local.as_opening_key(); 163 | let seqn = buffer.seqn.0; 164 | buffer.buffer.clear(); 165 | buffer.buffer.extend(&len_); 166 | let len = key.decrypt_packet_length(seqn, len_); 167 | let len = BigEndian::read_u32(&len) as usize + key.tag_len(); 168 | buffer.buffer.resize(len + 4); 169 | } 170 | self.0 = Some(CipherReadState::Body { 171 | body: read_exact_from( 172 | stream, 173 | std::mem::replace(&mut buffer.buffer, CryptoVec::new()), 174 | 4, 175 | ), 176 | buffer, 177 | pair, 178 | }) 179 | } else { 180 | self.0 = Some(CipherReadState::Len { len, buffer, pair }); 181 | return Ok(Async::NotReady); 182 | } 183 | } 184 | Some(CipherReadState::Body { 185 | mut body, 186 | mut buffer, 187 | pair, 188 | }) => { 189 | if let Async::Ready((stream, body)) = body.poll()? { 190 | let plaintext_end = { 191 | buffer.buffer = body; 192 | let key = pair.remote_to_local.as_opening_key(); 193 | let seqn = buffer.seqn.0; 194 | let ciphertext_len = buffer.buffer.len() - key.tag_len(); 195 | let (ciphertext, tag) = buffer.buffer.split_at_mut(ciphertext_len); 196 | let plaintext = key.open(seqn, ciphertext, tag)?; 197 | 198 | let padding_length = plaintext[0] as usize; 199 | let plaintext_end = plaintext.len().checked_sub(padding_length).ok_or( 200 | Error::IndexOutOfBounds, 201 | )?; 202 | 203 | // Sequence numbers are on 32 bits and wrap. 204 | // https://tools.ietf.org/html/rfc4253#section-6.4 205 | buffer.seqn += Wrapping(1); 206 | buffer.len = 0; 207 | 208 | plaintext_end 209 | }; 210 | return Ok(Async::Ready((stream, buffer, plaintext_end + 4))); 211 | } else { 212 | self.0 = Some(CipherReadState::Body { body, buffer, pair }); 213 | return Ok(Async::NotReady); 214 | } 215 | } 216 | } 217 | } 218 | } 219 | } 220 | 221 | pub fn read(stream: R, buffer: SSHBuffer, pair: Arc) -> CipherRead { 222 | CipherRead(Some(CipherReadState::Len { 223 | len: read_exact_from(stream, [0; 4], 0), 224 | buffer, 225 | pair, 226 | })) 227 | } 228 | 229 | impl CipherPair { 230 | pub fn write(&self, payload: &[u8], buffer: &mut SSHBuffer) { 231 | // https://tools.ietf.org/html/rfc4253#section-6 232 | // 233 | // The variables `payload`, `packet_length` and `padding_length` refer 234 | // to the protocol fields of the same names. 235 | 236 | let key = self.local_to_remote.as_sealing_key(); 237 | 238 | let padding_length = key.padding_length(payload); 239 | let packet_length = PADDING_LENGTH_LEN + payload.len() + padding_length; 240 | let offset = buffer.buffer.len(); 241 | 242 | // Maximum packet length: 243 | // https://tools.ietf.org/html/rfc4253#section-6.1 244 | assert!(packet_length <= std::u32::MAX as usize); 245 | buffer.buffer.push_u32_be(packet_length as u32); 246 | 247 | assert!(padding_length <= std::u8::MAX as usize); 248 | buffer.buffer.push(padding_length as u8); 249 | buffer.buffer.extend(payload); 250 | key.fill_padding(buffer.buffer.resize_mut(padding_length)); 251 | buffer.buffer.resize_mut(key.tag_len()); 252 | 253 | let (plaintext, tag) = 254 | buffer.buffer[offset..].split_at_mut(PACKET_LENGTH_LEN + packet_length); 255 | 256 | key.seal(buffer.seqn.0, plaintext, tag); 257 | 258 | // Sequence numbers are on 32 bits and wrap. 259 | // https://tools.ietf.org/html/rfc4253#section-6.4 260 | buffer.seqn += Wrapping(1); 261 | } 262 | } 263 | 264 | 265 | pub const PACKET_LENGTH_LEN: usize = 4; 266 | 267 | const MINIMUM_PACKET_LEN: usize = 16; 268 | 269 | const PADDING_LENGTH_LEN: usize = 1; 270 | -------------------------------------------------------------------------------- /thrussh/src/server/session.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use msg; 3 | use thrussh_keys::encoding::Encoding; 4 | use std::sync::Arc; 5 | 6 | /// A connected server session. This type is unique to a client. 7 | pub struct Session(pub(crate) CommonSession>); 8 | 9 | 10 | impl Session { 11 | /// Flush the session, i.e. encrypt the pending buffer. 12 | pub fn flush(&mut self) -> Result<(), Error> { 13 | if let Some(ref mut enc) = self.0.encrypted { 14 | if enc.flush( 15 | &self.0.config.as_ref().limits, 16 | &self.0.cipher, 17 | &mut self.0.write_buffer, 18 | ) 19 | { 20 | if let Some(exchange) = enc.exchange.take() { 21 | let mut kexinit = KexInit::initiate_rekey(exchange, &enc.session_id); 22 | kexinit.server_write( 23 | &self.0.config.as_ref(), 24 | &mut self.0.cipher, 25 | &mut self.0.write_buffer, 26 | )?; 27 | enc.rekey = Some(Kex::KexInit(kexinit)) 28 | } 29 | } 30 | } 31 | Ok(()) 32 | } 33 | 34 | /// Retrieves the configuration of this session. 35 | pub fn config(&self) -> &Config { 36 | &self.0.config 37 | } 38 | 39 | /// Sends a disconnect message. 40 | pub fn disconnect(&mut self, reason: Disconnect, description: &str, language_tag: &str) { 41 | self.0.disconnect(reason, description, language_tag); 42 | } 43 | 44 | /// Send a "success" reply to a /global/ request (requests without 45 | /// a channel number, such as TCP/IP forwarding or 46 | /// cancelling). Always call this function if the request was 47 | /// successful (it checks whether the client expects an answer). 48 | pub fn request_success(&mut self) { 49 | if self.0.wants_reply { 50 | if let Some(ref mut enc) = self.0.encrypted { 51 | self.0.wants_reply = false; 52 | push_packet!(enc.write, enc.write.push(msg::REQUEST_SUCCESS)) 53 | } 54 | } 55 | } 56 | 57 | /// Send a "failure" reply to a global request. 58 | pub fn request_failure(&mut self) { 59 | if let Some(ref mut enc) = self.0.encrypted { 60 | self.0.wants_reply = false; 61 | push_packet!(enc.write, enc.write.push(msg::REQUEST_FAILURE)) 62 | } 63 | } 64 | 65 | /// Send a "success" reply to a channel request. Always call this 66 | /// function if the request was successful (it checks whether the 67 | /// client expects an answer). 68 | pub fn channel_success(&mut self, channel: ChannelId) { 69 | if let Some(ref mut enc) = self.0.encrypted { 70 | if let Some(channel) = enc.channels.get_mut(&channel) { 71 | assert!(channel.confirmed); 72 | if channel.wants_reply { 73 | channel.wants_reply = false; 74 | push_packet!(enc.write, { 75 | enc.write.push(msg::CHANNEL_SUCCESS); 76 | enc.write.push_u32_be(channel.recipient_channel); 77 | }) 78 | } 79 | } 80 | } 81 | } 82 | 83 | /// Send a "failure" reply to a global request. 84 | pub fn channel_failure(&mut self, channel: ChannelId) { 85 | if let Some(ref mut enc) = self.0.encrypted { 86 | if let Some(channel) = enc.channels.get_mut(&channel) { 87 | assert!(channel.confirmed); 88 | if channel.wants_reply { 89 | channel.wants_reply = false; 90 | push_packet!(enc.write, { 91 | enc.write.push(msg::CHANNEL_FAILURE); 92 | enc.write.push_u32_be(channel.recipient_channel); 93 | }) 94 | } 95 | } 96 | } 97 | } 98 | 99 | /// Send a "failure" reply to a request to open a channel open. 100 | pub fn channel_open_failure( 101 | &mut self, 102 | channel: ChannelId, 103 | reason: ChannelOpenFailure, 104 | description: &str, 105 | language: &str, 106 | ) { 107 | if let Some(ref mut enc) = self.0.encrypted { 108 | push_packet!(enc.write, { 109 | enc.write.push(msg::CHANNEL_OPEN_FAILURE); 110 | enc.write.push_u32_be(channel.0); 111 | enc.write.push_u32_be(reason as u32); 112 | enc.write.extend_ssh_string(description.as_bytes()); 113 | enc.write.extend_ssh_string(language.as_bytes()); 114 | }) 115 | } 116 | } 117 | 118 | 119 | /// Close a channel. 120 | pub fn close(&mut self, channel: ChannelId) { 121 | self.0.byte(channel, msg::CHANNEL_CLOSE); 122 | } 123 | 124 | /// Send EOF to a channel 125 | pub fn eof(&mut self, channel: ChannelId) { 126 | self.0.byte(channel, msg::CHANNEL_EOF); 127 | } 128 | 129 | /// Send data to a channel. On session channels, `extended` can be 130 | /// used to encode standard error by passing `Some(1)`, and stdout 131 | /// by passing `None`. 132 | /// 133 | 134 | /// The number of bytes added to the "sending pipeline" (to be 135 | /// processed by the event loop) is returned. 136 | pub fn data(&mut self, channel: ChannelId, extended: Option, data: &[u8]) -> usize { 137 | if let Some(ref mut enc) = self.0.encrypted { 138 | enc.data(channel, extended, data) 139 | } else { 140 | unreachable!() 141 | } 142 | } 143 | 144 | /// Inform the client of whether they may perform 145 | /// control-S/control-Q flow control. See 146 | /// [RFC4254](https://tools.ietf.org/html/rfc4254#section-6.8). 147 | pub fn xon_xoff_request(&mut self, channel: ChannelId, client_can_do: bool) { 148 | if let Some(ref mut enc) = self.0.encrypted { 149 | if let Some(channel) = enc.channels.get(&channel) { 150 | assert!(channel.confirmed); 151 | push_packet!(enc.write, { 152 | enc.write.push(msg::CHANNEL_REQUEST); 153 | 154 | enc.write.push_u32_be(channel.recipient_channel); 155 | enc.write.extend_ssh_string(b"xon-xoff"); 156 | enc.write.push(0); 157 | enc.write.push(if client_can_do { 1 } else { 0 }); 158 | }) 159 | } 160 | } 161 | } 162 | 163 | /// Send the exit status of a program. 164 | pub fn exit_status_request(&mut self, channel: ChannelId, exit_status: u32) { 165 | if let Some(ref mut enc) = self.0.encrypted { 166 | if let Some(channel) = enc.channels.get(&channel) { 167 | assert!(channel.confirmed); 168 | push_packet!(enc.write, { 169 | enc.write.push(msg::CHANNEL_REQUEST); 170 | 171 | enc.write.push_u32_be(channel.recipient_channel); 172 | enc.write.extend_ssh_string(b"exit-status"); 173 | enc.write.push(0); 174 | enc.write.push_u32_be(exit_status) 175 | }) 176 | } 177 | } 178 | } 179 | 180 | /// If the program was killed by a signal, send the details about the signal to the client. 181 | pub fn exit_signal_request( 182 | &mut self, 183 | channel: ChannelId, 184 | signal: Sig, 185 | core_dumped: bool, 186 | error_message: &str, 187 | language_tag: &str, 188 | ) { 189 | if let Some(ref mut enc) = self.0.encrypted { 190 | if let Some(channel) = enc.channels.get(&channel) { 191 | assert!(channel.confirmed); 192 | push_packet!(enc.write, { 193 | enc.write.push(msg::CHANNEL_REQUEST); 194 | 195 | enc.write.push_u32_be(channel.recipient_channel); 196 | enc.write.extend_ssh_string(b"exit-signal"); 197 | enc.write.push(0); 198 | enc.write.extend_ssh_string(signal.name().as_bytes()); 199 | enc.write.push(if core_dumped { 1 } else { 0 }); 200 | enc.write.extend_ssh_string(error_message.as_bytes()); 201 | enc.write.extend_ssh_string(language_tag.as_bytes()); 202 | }) 203 | } 204 | } 205 | } 206 | 207 | /// Open a TCP/IP forwarding channel, when a connection comes to a 208 | /// local port for which forwarding has been requested. See 209 | /// [RFC4254](https://tools.ietf.org/html/rfc4254#section-7). The 210 | /// TCP/IP packets can then be tunneled through the channel using 211 | /// `.data()`. 212 | pub fn channel_open_forwarded_tcpip( 213 | &mut self, 214 | connected_address: &str, 215 | connected_port: u32, 216 | originator_address: &str, 217 | originator_port: u32, 218 | ) -> Result { 219 | let result = if let Some(ref mut enc) = self.0.encrypted { 220 | match enc.state { 221 | Some(EncryptedState::Authenticated) => { 222 | debug!("sending open request"); 223 | 224 | let sender_channel = enc.new_channel( 225 | self.0.config.window_size, 226 | self.0.config.maximum_packet_size, 227 | ); 228 | push_packet!(enc.write, { 229 | enc.write.push(msg::CHANNEL_OPEN); 230 | enc.write.extend_ssh_string(b"forwarded-tcpip"); 231 | 232 | // sender channel id. 233 | enc.write.push_u32_be(sender_channel.0); 234 | 235 | // window. 236 | enc.write.push_u32_be(self.0.config.as_ref().window_size); 237 | 238 | // max packet size. 239 | enc.write.push_u32_be( 240 | self.0.config.as_ref().maximum_packet_size, 241 | ); 242 | 243 | enc.write.extend_ssh_string(connected_address.as_bytes()); 244 | enc.write.push_u32_be(connected_port); // sender channel id. 245 | enc.write.extend_ssh_string(originator_address.as_bytes()); 246 | enc.write.push_u32_be(originator_port); // sender channel id. 247 | }); 248 | sender_channel 249 | } 250 | _ => return Err(Error::Inconsistent), 251 | } 252 | } else { 253 | return Err(Error::Inconsistent); 254 | }; 255 | Ok(result) 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /thrussh-keys/src/pem.rs: -------------------------------------------------------------------------------- 1 | use {Error, ErrorKind, KEYTYPE_ED25519, KEYTYPE_RSA, rsa_key_from_components}; 2 | use std; 3 | use thrussh::encoding::Reader; 4 | use thrussh::key; 5 | use super::is_base64_char; 6 | use hex::FromHex; 7 | use base64::{decode_config, MIME}; 8 | use ring::signature; 9 | use untrusted; 10 | use yasna; 11 | use ring; 12 | 13 | use openssl::symm::{encrypt, decrypt, Cipher, Mode, Crypter}; 14 | use openssl::hash::{MessageDigest, Hasher}; 15 | use bcrypt_pbkdf; 16 | 17 | 18 | const PBES2: &'static [u64] = &[1, 2, 840, 113549, 1, 5, 13]; 19 | const PBKDF2: &'static [u64] = &[1, 2, 840, 113549, 1, 5, 12]; 20 | const HMAC_SHA256: &'static [u64] = &[1, 2, 840, 113549, 2, 9]; 21 | const AES256CBC: &'static [u64] = &[2, 16, 840, 1, 101, 3, 4, 1, 42]; 22 | const ED25519: &'static [u64] = &[1, 3, 101, 112]; 23 | const RSA: &'static [u64] = &[1, 2, 840, 113549, 1, 1, 1]; 24 | 25 | // https://tools.ietf.org/html/rfc5208 26 | fn decode_pkcs8( 27 | secret: &[u8], 28 | password: Option<&[u8]>, 29 | ) -> Result<(key::Algorithm, super::KeyPairComponents), Error> { 30 | if let Some(pass) = password { 31 | // let mut sec = Vec::new(); 32 | let secret = yasna::parse_der(&secret, |reader| { 33 | reader.read_sequence(|reader| { 34 | // Encryption parameters 35 | let parameters = reader.next().read_sequence(|reader| { 36 | let oid = reader.next().read_oid()?; 37 | debug!("oid = {:?} {:?}", oid, oid.components().as_slice() == PBES2); 38 | if oid.components().as_slice() == PBES2 { 39 | asn1_read_pbes2(reader) 40 | } else { 41 | Ok(Err(ErrorKind::UnknownAlgorithm(oid).into())) 42 | } 43 | })?; 44 | // Ciphertext 45 | let ciphertext = reader.next().read_bytes()?; 46 | Ok(parameters.map(|p| p.decrypt(pass, &ciphertext))) 47 | }) 48 | })???; 49 | debug!("secret {:?}", secret); 50 | 51 | let mut oid = None; 52 | yasna::parse_der(&secret, |reader| { 53 | reader.read_sequence(|reader| { 54 | let version = reader.next().read_u64()?; 55 | debug!("version = {:?}", version); 56 | reader.next().read_sequence(|reader| { 57 | oid = Some(reader.next().read_oid()?); 58 | Ok(()) 59 | }).unwrap_or(()); 60 | Ok(()) 61 | }) 62 | }).unwrap_or(()); 63 | 64 | debug!("pkcs8 oid {:?}", oid); 65 | let oid = if let Some(oid) = oid { 66 | oid 67 | } else { 68 | return Err(ErrorKind::CouldNotReadKey.into()) 69 | }; 70 | if oid.components().as_slice() == ED25519 { 71 | let components = signature::primitive::Ed25519KeyPairComponents::from_pkcs8( 72 | untrusted::Input::from(&secret), 73 | )?; 74 | debug!("components!"); 75 | let keypair = signature::Ed25519KeyPair::from_pkcs8(untrusted::Input::from(&secret))?; 76 | debug!("keypair!"); 77 | Ok((key::Algorithm::Ed25519(keypair), 78 | super::KeyPairComponents::Ed25519(components))) 79 | 80 | } else if oid.components().as_slice() == RSA { 81 | let components = signature::primitive::RSAKeyPairComponents::from_pkcs8( 82 | untrusted::Input::from(&secret), 83 | )?; 84 | 85 | let keypair = signature::RSAKeyPair::from_pkcs8(untrusted::Input::from(&secret))?; 86 | Ok(( 87 | key::Algorithm::RSA( 88 | std::sync::Arc::new(keypair), 89 | key::RSAPublicKey { 90 | n: components.n.as_slice_less_safe().to_vec(), 91 | e: components.e.as_slice_less_safe().to_vec(), 92 | hash: key::SignatureHash::SHA2_512, 93 | }, 94 | ), 95 | super::KeyPairComponents::RSA( 96 | super::RSAKeyPairComponents::from_components(&components), 97 | ), 98 | )) 99 | } else { 100 | Err(ErrorKind::CouldNotReadKey.into()) 101 | } 102 | } else { 103 | Err(ErrorKind::KeyIsEncrypted.into()) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | use env_logger; 109 | 110 | #[test] 111 | fn test_read_write_pkcs8() { 112 | env_logger::init().unwrap_or(()); 113 | let r = ring::rand::SystemRandom::new(); 114 | let key = ring::signature::Ed25519KeyPair::generate_pkcs8(&r).unwrap(); 115 | let password = b"blabla"; 116 | let ciphertext = encode_pkcs8(&r, &key, Some(password), 100).unwrap(); 117 | let (_, comp) = decode_pkcs8(&ciphertext, Some(password)).unwrap(); 118 | use super::KeyPairComponents; 119 | match comp { 120 | KeyPairComponents::Ed25519(_) => debug!("Ed25519"), 121 | KeyPairComponents::RSA(_) => debug!("RSA"), 122 | } 123 | } 124 | 125 | 126 | use yasna::models::ObjectIdentifier; 127 | pub fn encode_pkcs8( 128 | rand: &R, 129 | plaintext: &[u8], 130 | password: Option<&[u8]>, 131 | rounds: u32 132 | ) -> Result, Error> { 133 | if let Some(pass) = password { 134 | 135 | let mut salt = [0; 64]; 136 | rand.fill(&mut salt)?; 137 | let mut iv = [0; 16]; 138 | rand.fill(&mut iv)?; 139 | let mut key = [0; 32]; // AES256-CBC 140 | ring::pbkdf2::derive(&ring::digest::SHA256, rounds, &salt, pass, &mut key[..]); 141 | debug!("key = {:?}", key); 142 | 143 | let mut plaintext = plaintext.to_vec(); 144 | let padding_len = 32 - (plaintext.len() % 32); 145 | plaintext.extend(std::iter::repeat(padding_len as u8).take(padding_len)); 146 | 147 | debug!("plaintext {:?}", plaintext); 148 | let ciphertext = encrypt(Cipher::aes_256_cbc(), &key, Some(&iv), &plaintext)?; 149 | 150 | let v = yasna::construct_der(|writer| { 151 | writer.write_sequence(|writer| { 152 | // Encryption parameters 153 | writer.next().write_sequence(|writer| { 154 | writer.next().write_oid(&ObjectIdentifier::from_slice(PBES2)); 155 | asn1_write_pbes2(writer.next(), rounds as u64, &salt, &iv) 156 | }); 157 | // Ciphertext 158 | writer.next().write_bytes(&ciphertext[..]) 159 | }) 160 | }); 161 | Ok(v) 162 | } else { 163 | Err(ErrorKind::KeyIsEncrypted.into()) 164 | } 165 | } 166 | 167 | fn asn1_write_pbes2(writer: yasna::DERWriter, rounds: u64, salt: &[u8], iv: &[u8]) { 168 | writer.write_sequence(|writer| { 169 | // 1. Key generation algorithm 170 | writer.next().write_sequence(|writer| { 171 | writer.next().write_oid(&ObjectIdentifier::from_slice(PBKDF2)); 172 | asn1_write_pbkdf2(writer.next(), rounds, salt) 173 | }); 174 | // 2. Encryption algorithm. 175 | writer.next().write_sequence(|writer| { 176 | writer.next().write_oid(&ObjectIdentifier::from_slice(AES256CBC)); 177 | writer.next().write_bytes(iv) 178 | }); 179 | }) 180 | } 181 | 182 | fn asn1_write_pbkdf2(writer: yasna::DERWriter, rounds: u64, salt: &[u8]) { 183 | writer.write_sequence(|writer| { 184 | writer.next().write_bytes(salt); 185 | writer.next().write_u64(rounds); 186 | writer.next().write_sequence(|writer| { 187 | writer.next().write_oid(&ObjectIdentifier::from_slice(HMAC_SHA256)); 188 | writer.next().write_null() 189 | }) 190 | }) 191 | } 192 | 193 | enum Algorithms { 194 | Pbes2(KeyDerivation, Encryption), 195 | } 196 | 197 | impl Algorithms { 198 | fn decrypt(&self, password: &[u8], cipher: &[u8]) -> Result, Error> { 199 | match *self { 200 | Algorithms::Pbes2(ref der, ref enc) => { 201 | let mut key = enc.key(); 202 | der.derive(password, &mut key); 203 | let out = enc.decrypt(&key, cipher)?; 204 | Ok(out) 205 | } 206 | } 207 | } 208 | } 209 | 210 | impl KeyDerivation { 211 | fn derive(&self, password: &[u8], key: &mut [u8]) { 212 | match *self { 213 | KeyDerivation::Pbkdf2 { 214 | ref salt, 215 | rounds, 216 | digest, 217 | } => ring::pbkdf2::derive(digest, rounds as u32, salt, password, key), 218 | } 219 | } 220 | } 221 | 222 | enum Key { 223 | K128([u8; 16]), 224 | K256([u8; 32]), 225 | } 226 | 227 | impl std::ops::Deref for Key { 228 | type Target = [u8]; 229 | fn deref(&self) -> &[u8] { 230 | match *self { 231 | Key::K128(ref k) => k, 232 | Key::K256(ref k) => k, 233 | } 234 | } 235 | } 236 | 237 | impl std::ops::DerefMut for Key { 238 | fn deref_mut(&mut self) -> &mut [u8] { 239 | match *self { 240 | Key::K128(ref mut k) => k, 241 | Key::K256(ref mut k) => k, 242 | } 243 | } 244 | } 245 | 246 | impl Encryption { 247 | fn key(&self) -> Key { 248 | match *self { 249 | Encryption::Aes128Cbc(_) => Key::K128([0; 16]), 250 | Encryption::Aes256Cbc(_) => Key::K256([0; 32]), 251 | } 252 | } 253 | 254 | fn decrypt(&self, key: &[u8], ciphertext: &[u8]) -> Result, Error> { 255 | let (cipher, iv) = match *self { 256 | Encryption::Aes128Cbc(ref iv) => (Cipher::aes_128_cbc(), iv), 257 | Encryption::Aes256Cbc(ref iv) => (Cipher::aes_256_cbc(), iv), 258 | }; 259 | let mut dec = decrypt( 260 | cipher, 261 | &key, 262 | Some(&iv[..]), 263 | ciphertext 264 | )?; 265 | pkcs_unpad(&mut dec); 266 | Ok(dec) 267 | } 268 | } 269 | 270 | enum KeyDerivation { 271 | Pbkdf2 { 272 | salt: Vec, 273 | rounds: u64, 274 | digest: &'static ring::digest::Algorithm, 275 | }, 276 | } 277 | 278 | fn asn1_read_pbes2( 279 | reader: &mut yasna::BERReaderSeq, 280 | ) -> Result, yasna::ASN1Error> { 281 | reader.next().read_sequence(|reader| { 282 | // PBES2 has two components. 283 | // 1. Key generation algorithm 284 | let keygen = reader.next().read_sequence(|reader| { 285 | let oid = reader.next().read_oid()?; 286 | if oid.components().as_slice() == PBKDF2 { 287 | asn1_read_pbkdf2(reader) 288 | } else { 289 | Ok(Err(ErrorKind::UnknownAlgorithm(oid).into())) 290 | } 291 | })?; 292 | // 2. Encryption algorithm. 293 | let algorithm = reader.next().read_sequence(|reader| { 294 | let oid = reader.next().read_oid()?; 295 | if oid.components().as_slice() == AES256CBC { 296 | asn1_read_aes256cbc(reader) 297 | } else { 298 | Ok(Err(ErrorKind::UnknownAlgorithm(oid).into())) 299 | } 300 | })?; 301 | Ok(keygen.and_then(|keygen| { 302 | algorithm.map(|algo| Algorithms::Pbes2(keygen, algo)) 303 | })) 304 | }) 305 | } 306 | 307 | fn asn1_read_pbkdf2( 308 | reader: &mut yasna::BERReaderSeq, 309 | ) -> Result, yasna::ASN1Error> { 310 | reader.next().read_sequence(|reader| { 311 | let salt = reader.next().read_bytes()?; 312 | let rounds = reader.next().read_u64()?; 313 | let digest = reader.next().read_sequence(|reader| { 314 | let oid = reader.next().read_oid()?; 315 | if oid.components().as_slice() == HMAC_SHA256 { 316 | reader.next().read_null()?; 317 | Ok(Ok(&ring::digest::SHA256)) 318 | } else { 319 | Ok(Err(ErrorKind::UnknownAlgorithm(oid).into())) 320 | } 321 | })?; 322 | Ok(digest.map(|digest| { 323 | KeyDerivation::Pbkdf2 { 324 | salt, 325 | rounds, 326 | digest, 327 | } 328 | })) 329 | }) 330 | } 331 | 332 | fn asn1_read_aes256cbc( 333 | reader: &mut yasna::BERReaderSeq, 334 | ) -> Result, yasna::ASN1Error> { 335 | let iv = reader.next().read_bytes()?; 336 | let mut i = [0; 16]; 337 | i.clone_from_slice(&iv); 338 | Ok(Ok(Encryption::Aes256Cbc(i))) 339 | 340 | } 341 | -------------------------------------------------------------------------------- /thrussh/src/client/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | use std::sync::Arc; 17 | use std; 18 | use futures::{Poll, Async}; 19 | use futures::future::Future; 20 | use tokio_timer::Delay; 21 | use tokio::net::TcpStream; 22 | use tokio::io::{WriteAll, Flush, flush}; 23 | use tokio; 24 | use std::net::ToSocketAddrs; 25 | use {Disconnect, Error, Limits, Sig, ChannelOpenFailure, ChannelId, 26 | FromFinished, HandlerError, Status, AtomicPoll}; 27 | use thrussh_keys::key::parse_public_key; 28 | use thrussh_keys::key; 29 | use auth; 30 | use negotiation; 31 | use cryptovec::CryptoVec; 32 | use session::*; 33 | use sshbuffer::*; 34 | use pty::Pty; 35 | 36 | mod encrypted; 37 | 38 | mod session; 39 | pub use self::session::*; 40 | mod connection; 41 | pub use self::connection::*; 42 | mod data; 43 | mod authenticate; 44 | mod kex; 45 | mod channel_open; 46 | mod wait; 47 | 48 | pub use self::data::*; 49 | pub use self::authenticate::*; 50 | pub use self::kex::*; 51 | pub use self::channel_open::*; 52 | pub use self::wait::*; 53 | 54 | /// The configuration of clients. 55 | #[derive(Debug)] 56 | pub struct Config { 57 | /// The client ID string sent at the beginning of the protocol. 58 | pub client_id: String, 59 | /// The bytes and time limits before key re-exchange. 60 | pub limits: Limits, 61 | /// The initial size of a channel (used for flow control). 62 | pub window_size: u32, 63 | /// The maximal size of a single packet. 64 | pub maximum_packet_size: u32, 65 | /// Lists of preferred algorithms. 66 | pub preferred: negotiation::Preferred, 67 | /// Time after which the connection is garbage-collected. 68 | pub connection_timeout: Option, 69 | } 70 | 71 | impl Default for Config { 72 | fn default() -> Config { 73 | Config { 74 | client_id: format!( 75 | "SSH-2.0-{}_{}", 76 | env!("CARGO_PKG_NAME"), 77 | env!("CARGO_PKG_VERSION") 78 | ), 79 | limits: Limits::default(), 80 | window_size: 200000, 81 | maximum_packet_size: 200000, 82 | preferred: Default::default(), 83 | connection_timeout: None, 84 | } 85 | } 86 | } 87 | 88 | 89 | /// A client handler. Note that messages can be received from the 90 | /// server at any time during a session. 91 | pub trait Handler: Sized { 92 | /// Error type returned by the futures. 93 | type Error: std::fmt::Debug; 94 | 95 | /// A future ultimately resolving into a boolean, which can be 96 | /// returned by some parts of this handler. 97 | type FutureBool: Future + FromFinished<(Self, bool), Self::Error>; 98 | 99 | /// A future ultimately resolving into a boolean, which can be 100 | /// returned by some parts of this handler. 101 | type FutureUnit: Future + FromFinished; 102 | 103 | /// A future that computes the signature of a `CryptoVec`, appends 104 | /// that signature to that `CryptoVec`, and resolves to the 105 | /// `CryptoVec`. Useful for instance to implement SSH agent 106 | /// clients. 107 | type FutureSign: Future + FromFinished<(Self, CryptoVec), Self::Error>; 108 | 109 | /// A future ultimately resolving into unit, which can be returned 110 | /// by some parts of this handler. 111 | type SessionUnit: Future + FromFinished<(Self, Session), Self::Error>; 112 | 113 | 114 | /// Called when the server sends us an authentication banner. This 115 | /// is usually meant to be shown to the user, see 116 | /// [RFC4252](https://tools.ietf.org/html/rfc4252#section-5.4) for 117 | /// more details. 118 | #[allow(unused_variables)] 119 | fn auth_banner(self, banner: &str) -> Self::FutureUnit { 120 | Self::FutureUnit::finished(self) 121 | } 122 | 123 | /// Called when using the `FuturePublicKey` method, used for 124 | /// instance to implement SSH agent. This can be used for instance 125 | /// to implement an interface to SSH agents. The default 126 | /// implementation returns the supplied `CryptoVec` without 127 | /// touching it. 128 | #[allow(unused_variables)] 129 | fn auth_publickey_sign(self, key: &key::PublicKey, to_sign: CryptoVec) -> Self::FutureSign { 130 | Self::FutureSign::finished((self, to_sign)) 131 | } 132 | 133 | /// Called to check the server's public key. This is a very important 134 | /// step to help prevent man-in-the-middle attacks. The default 135 | /// implementation rejects all keys. 136 | #[allow(unused_variables)] 137 | fn check_server_key(self, server_public_key: &key::PublicKey) -> Self::FutureBool { 138 | Self::FutureBool::finished((self, false)) 139 | } 140 | 141 | /// Called when the server confirmed our request to open a 142 | /// channel. A channel can only be written to after receiving this 143 | /// message (this library panics otherwise). 144 | #[allow(unused_variables)] 145 | fn channel_open_confirmation(self, channel: ChannelId, session: Session) -> Self::SessionUnit { 146 | Self::SessionUnit::finished((self, session)) 147 | } 148 | 149 | /// Called when the server closes a channel. 150 | #[allow(unused_variables)] 151 | fn channel_close(self, channel: ChannelId, session: Session) -> Self::SessionUnit { 152 | Self::SessionUnit::finished((self, session)) 153 | } 154 | 155 | /// Called when the server sends EOF to a channel. 156 | #[allow(unused_variables)] 157 | fn channel_eof(self, channel: ChannelId, session: Session) -> Self::SessionUnit { 158 | Self::SessionUnit::finished((self, session)) 159 | } 160 | 161 | /// Called when the server rejected our request to open a channel. 162 | #[allow(unused_variables)] 163 | fn channel_open_failure( 164 | self, 165 | channel: ChannelId, 166 | reason: ChannelOpenFailure, 167 | description: &str, 168 | language: &str, 169 | session: Session, 170 | ) -> Self::SessionUnit { 171 | Self::SessionUnit::finished((self, session)) 172 | } 173 | 174 | /// Called when a new channel is created. 175 | #[allow(unused_variables)] 176 | fn channel_open_forwarded_tcpip( 177 | self, 178 | channel: ChannelId, 179 | connected_address: &str, 180 | connected_port: u32, 181 | originator_address: &str, 182 | originator_port: u32, 183 | session: Session, 184 | ) -> Self::SessionUnit { 185 | Self::SessionUnit::finished((self, session)) 186 | } 187 | 188 | /// Called when the server sends us data. The `extended_code` 189 | /// parameter is a stream identifier, `None` is usually the 190 | /// standard output, and `Some(1)` is the standard error. See 191 | /// [RFC4254](https://tools.ietf.org/html/rfc4254#section-5.2). 192 | #[allow(unused_variables)] 193 | fn data( 194 | self, 195 | channel: ChannelId, 196 | extended_code: Option, 197 | data: &[u8], 198 | session: Session, 199 | ) -> Self::SessionUnit { 200 | Self::SessionUnit::finished((self, session)) 201 | } 202 | 203 | /// The server informs this client of whether the client may 204 | /// perform control-S/control-Q flow control. See 205 | /// [RFC4254](https://tools.ietf.org/html/rfc4254#section-6.8). 206 | #[allow(unused_variables)] 207 | fn xon_xoff( 208 | self, 209 | channel: ChannelId, 210 | client_can_do: bool, 211 | session: Session, 212 | ) -> Self::SessionUnit { 213 | Self::SessionUnit::finished((self, session)) 214 | } 215 | 216 | /// The remote process has exited, with the given exit status. 217 | #[allow(unused_variables)] 218 | fn exit_status( 219 | self, 220 | channel: ChannelId, 221 | exit_status: u32, 222 | session: Session, 223 | ) -> Self::SessionUnit { 224 | Self::SessionUnit::finished((self, session)) 225 | } 226 | 227 | /// The remote process exited upon receiving a signal. 228 | #[allow(unused_variables)] 229 | fn exit_signal( 230 | self, 231 | channel: ChannelId, 232 | signal_name: Sig, 233 | core_dumped: bool, 234 | error_message: &str, 235 | lang_tag: &str, 236 | session: Session, 237 | ) -> Self::SessionUnit { 238 | Self::SessionUnit::finished((self, session)) 239 | } 240 | 241 | /// Called when the network window is adjusted, meaning that we 242 | /// can send more bytes. This is useful if this client wants to 243 | /// send huge amounts of data, for instance if we have called 244 | /// `Session::data` before, and it returned less than the 245 | /// full amount of data. 246 | #[allow(unused_variables)] 247 | fn window_adjusted( 248 | self, 249 | channel: ChannelId, 250 | new_window_size: usize, 251 | session: Session, 252 | ) -> Self::SessionUnit { 253 | Self::SessionUnit::finished((self, session)) 254 | } 255 | } 256 | 257 | /* 258 | use tokio::reactor::Reactor; 259 | /// Create a new client connection to the given address. 260 | pub fn connect< 261 | Addr: ToSocketAddrs, 262 | H: Handler, 263 | I, 264 | E, 265 | X: Future, 266 | F: FnOnce(Connection) -> X, 267 | >( 268 | addr: Addr, 269 | config: Arc, 270 | timeout: Option, 271 | handler: H, 272 | f: F, 273 | ) -> Result> { 274 | 275 | let mut l = Reactor::new()?; 276 | let cur = tokio::executor::current_thread::CurrentThread::new(); 277 | cur.block_on(connect_future(addr, config, timeout, handler, f)?) 278 | } 279 | */ 280 | 281 | /// Create a new client connection to the given address. 282 | pub fn connect_future< 283 | Addr: ToSocketAddrs, 284 | H: Handler, 285 | I, 286 | E, 287 | X: Future, 288 | F: FnOnce(Connection) -> X, 289 | >( 290 | addr: Addr, 291 | config: Arc, 292 | timeout: Option, 293 | handler: H, 294 | f: F, 295 | ) -> Result, Error> { 296 | 297 | Ok(ConnectFuture { 298 | state: Some(ConnectFutureState::TcpConnect( 299 | TcpStream::connect(&addr.to_socket_addrs()?.next().unwrap()) 300 | )), 301 | handler: Some(handler), 302 | config, 303 | timeout, 304 | f: Some(f), 305 | }) 306 | } 307 | 308 | /// Future returned by `connect_future`. 309 | pub struct ConnectFuture, F: FnOnce(Connection) -> X> { 310 | handler: Option, 311 | config: Arc, 312 | state: Option>, 313 | timeout: Option, 314 | f: Option 315 | } 316 | 317 | enum ConnectFutureState { 318 | TcpConnect(tokio::net::ConnectFuture), 319 | Connect(X) 320 | } 321 | 322 | impl, F: FnOnce(Connection) -> X> Future for ConnectFuture { 323 | type Item = I; 324 | type Error = HandlerError; 325 | fn poll(&mut self) -> Poll { 326 | loop { 327 | match self.state.take() { 328 | Some(ConnectFutureState::TcpConnect(mut connect)) => { 329 | if let Async::Ready(socket) = connect.poll()? { 330 | 331 | self.state = Some(ConnectFutureState::Connect( 332 | (self.f.take().unwrap())(Connection::new( 333 | self.config.clone(), 334 | socket, 335 | self.handler.take().unwrap(), 336 | self.timeout.take(), 337 | )?) 338 | )) 339 | 340 | } else { 341 | self.state = Some(ConnectFutureState::TcpConnect(connect)); 342 | return Ok(Async::NotReady) 343 | } 344 | } 345 | Some(ConnectFutureState::Connect(mut connect)) => { 346 | match connect.poll() { 347 | Ok(Async::Ready(f)) => return Ok(Async::Ready(f)), 348 | Ok(Async::NotReady) => { 349 | self.state = Some(ConnectFutureState::Connect(connect)); 350 | return Ok(Async::NotReady) 351 | } 352 | Err(e) => return Err(HandlerError::Handler(e)) 353 | } 354 | } 355 | None => panic!("Future polled after completion") 356 | } 357 | } 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /thrussh/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | use std; 17 | use std::net::ToSocketAddrs; 18 | use std::sync::Arc; 19 | 20 | use futures::stream::Stream; 21 | use futures::{Poll, Async}; 22 | use futures::future::Future; 23 | use tokio::net::TcpListener; 24 | use tokio::io::{AsyncRead, AsyncWrite}; 25 | use tokio::io::{flush, Flush, WriteAll}; 26 | use thrussh_keys::key; 27 | 28 | use super::*; 29 | use sshbuffer::*; 30 | use negotiation; 31 | 32 | use session::*; 33 | use auth; 34 | 35 | mod encrypted; 36 | mod connection; 37 | mod kex; 38 | mod session; 39 | pub use self::connection::*; 40 | pub use self::kex::*; 41 | pub use self::session::*; 42 | 43 | #[derive(Debug)] 44 | /// Configuration of a server. 45 | pub struct Config { 46 | /// The server ID string sent at the beginning of the protocol. 47 | pub server_id: String, 48 | /// Authentication methods proposed to the client. 49 | pub methods: auth::MethodSet, 50 | /// The authentication banner, usually a warning message shown to the client. 51 | pub auth_banner: Option<&'static str>, 52 | /// Authentication rejections must happen in constant time for 53 | /// security reasons. Thrussh does not handle this by default. 54 | pub auth_rejection_time: std::time::Duration, 55 | /// The server's keys. The first key pair in the client's preference order will be chosen. 56 | pub keys: Vec, 57 | /// The bytes and time limits before key re-exchange. 58 | pub limits: Limits, 59 | /// The initial size of a channel (used for flow control). 60 | pub window_size: u32, 61 | /// The maximal size of a single packet. 62 | pub maximum_packet_size: u32, 63 | /// Lists of preferred algorithms. 64 | pub preferred: Preferred, 65 | /// Maximal number of allowed authentication attempts. 66 | pub max_auth_attempts: usize, 67 | /// Time after which the connection is garbage-collected. 68 | pub connection_timeout: Option, 69 | } 70 | 71 | impl Default for Config { 72 | fn default() -> Config { 73 | Config { 74 | server_id: format!( 75 | "SSH-2.0-{}_{}", 76 | env!("CARGO_PKG_NAME"), 77 | env!("CARGO_PKG_VERSION") 78 | ), 79 | methods: auth::MethodSet::all(), 80 | auth_banner: None, 81 | auth_rejection_time: std::time::Duration::from_secs(1), 82 | keys: Vec::new(), 83 | window_size: 200000, 84 | maximum_packet_size: 200000, 85 | limits: Limits::default(), 86 | preferred: Default::default(), 87 | max_auth_attempts: 10, 88 | connection_timeout: Some(std::time::Duration::from_secs(600)), 89 | } 90 | } 91 | } 92 | 93 | /// A client's response in a challenge-response authentication. 94 | #[derive(Debug)] 95 | pub struct Response<'a> { 96 | pos: thrussh_keys::encoding::Position<'a>, 97 | n: u32, 98 | } 99 | 100 | impl<'a> Iterator for Response<'a> { 101 | type Item = &'a [u8]; 102 | fn next(&mut self) -> Option { 103 | if self.n == 0 { 104 | None 105 | } else { 106 | self.n -= 1; 107 | self.pos.read_string().ok() 108 | } 109 | } 110 | } 111 | 112 | use std::borrow::Cow; 113 | /// An authentication result, in a challenge-response authentication. 114 | #[derive(Debug, PartialEq, Eq)] 115 | pub enum Auth { 116 | /// Reject the authentication request. 117 | Reject, 118 | /// Accept the authentication request. 119 | Accept, 120 | 121 | /// Method was not accepted, but no other check was performed. 122 | UnsupportedMethod, 123 | 124 | /// Partially accept the challenge-response authentication 125 | /// request, providing more instructions for the client to follow. 126 | Partial { 127 | /// Name of this challenge. 128 | name: Cow<'static, str>, 129 | /// Instructions for this challenge. 130 | instructions: Cow<'static, str>, 131 | /// A number of prompts to the user. Each prompt has a `bool` 132 | /// indicating whether the terminal must echo the characters 133 | /// typed by the user. 134 | prompts: Cow<'static, [(Cow<'static, str>, bool)]>, 135 | }, 136 | } 137 | 138 | /// Server handler. Each client will have their own handler. 139 | pub trait Handler: Sized { 140 | /// The type of errors returned by the futures. 141 | type Error: std::error::Error + Send + Sync; 142 | 143 | /// The type of authentications, which can be a future ultimately 144 | /// resolving to 145 | type FutureAuth: Future + Send; 146 | 147 | /// The type of units returned by some parts of this handler. 148 | type FutureUnit: Future + Send; 149 | 150 | /// The type of future bools returned by some parts of this handler. 151 | type FutureBool: Future + Send; 152 | 153 | /// Convert an `Auth` to `Self::FutureAuth`. This is used to 154 | /// produce the default handlers. 155 | fn finished_auth(self, auth: Auth) -> Self::FutureAuth; 156 | 157 | /// Convert a `bool` to `Self::FutureBool`. This is used to 158 | /// produce the default handlers. 159 | fn finished_bool(self, session: Session, b: bool) -> Self::FutureBool; 160 | 161 | /// Produce a `Self::FutureUnit`. This is used to produce the 162 | /// default handlers. 163 | fn finished(self, session: Session) -> Self::FutureUnit; 164 | 165 | /// Check authentication using the "none" method. Thrussh makes 166 | /// sure rejection happens in time `config.auth_rejection_time`, 167 | /// except if this method takes more than that. 168 | #[allow(unused_variables)] 169 | fn auth_none(self, user: &str) -> Self::FutureAuth { 170 | self.finished_auth(Auth::Reject) 171 | } 172 | 173 | /// Check authentication using the "password" method. Thrussh 174 | /// makes sure rejection happens in time 175 | /// `config.auth_rejection_time`, except if this method takes more 176 | /// than that. 177 | #[allow(unused_variables)] 178 | fn auth_password(self, user: &str, password: &str) -> Self::FutureAuth { 179 | self.finished_auth(Auth::Reject) 180 | } 181 | 182 | /// Check authentication using the "publickey" method. This method 183 | /// should just check whether the public key matches the 184 | /// authorized ones. Thrussh then checks the signature. If the key 185 | /// is unknown, or the signature is invalid, Thrussh guarantees 186 | /// that rejection happens in constant time 187 | /// `config.auth_rejection_time`, except if this method takes more 188 | /// time than that. 189 | #[allow(unused_variables)] 190 | fn auth_publickey(self, user: &str, public_key: &key::PublicKey) -> Self::FutureAuth { 191 | self.finished_auth(Auth::Reject) 192 | } 193 | 194 | /// Check authentication using the "keyboard-interactive" 195 | /// method. Thrussh makes sure rejection happens in time 196 | /// `config.auth_rejection_time`, except if this method takes more 197 | /// than that. 198 | #[allow(unused_variables)] 199 | fn auth_keyboard_interactive( 200 | self, 201 | user: &str, 202 | submethods: &str, 203 | response: Option, 204 | ) -> Self::FutureAuth { 205 | self.finished_auth(Auth::Reject) 206 | } 207 | 208 | /// Called when the client closes a channel. 209 | #[allow(unused_variables)] 210 | fn channel_close(self, channel: ChannelId, session: Session) -> Self::FutureUnit { 211 | self.finished(session) 212 | } 213 | 214 | /// Called when the client sends EOF to a channel. 215 | #[allow(unused_variables)] 216 | fn channel_eof(self, channel: ChannelId, session: Session) -> Self::FutureUnit { 217 | self.finished(session) 218 | } 219 | 220 | /// Called when a new session channel is created. 221 | #[allow(unused_variables)] 222 | fn channel_open_session(self, channel: ChannelId, session: Session) -> Self::FutureUnit { 223 | self.finished(session) 224 | } 225 | 226 | /// Called when a new X11 channel is created. 227 | #[allow(unused_variables)] 228 | fn channel_open_x11( 229 | self, 230 | channel: ChannelId, 231 | originator_address: &str, 232 | originator_port: u32, 233 | session: Session, 234 | ) -> Self::FutureUnit { 235 | self.finished(session) 236 | } 237 | 238 | /// Called when a new channel is created. 239 | #[allow(unused_variables)] 240 | fn channel_open_direct_tcpip( 241 | self, 242 | channel: ChannelId, 243 | host_to_connect: &str, 244 | port_to_connect: u32, 245 | originator_address: &str, 246 | originator_port: u32, 247 | session: Session, 248 | ) -> Self::FutureUnit { 249 | self.finished(session) 250 | } 251 | 252 | /// Called when a data packet is received. A response can be 253 | /// written to the `response` argument. 254 | #[allow(unused_variables)] 255 | fn data(self, channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit { 256 | self.finished(session) 257 | } 258 | 259 | /// Called when an extended data packet is received. Code 1 means 260 | /// that this packet comes from stderr, other codes are not 261 | /// defined (see 262 | /// [RFC4254](https://tools.ietf.org/html/rfc4254#section-5.2)). 263 | #[allow(unused_variables)] 264 | fn extended_data( 265 | self, 266 | channel: ChannelId, 267 | code: u32, 268 | data: &[u8], 269 | session: Session, 270 | ) -> Self::FutureUnit { 271 | self.finished(session) 272 | } 273 | 274 | /// Called when the network window is adjusted, meaning that we 275 | /// can send more bytes. 276 | #[allow(unused_variables)] 277 | fn window_adjusted( 278 | self, 279 | channel: ChannelId, 280 | new_window_size: usize, 281 | session: Session, 282 | ) -> Self::FutureUnit { 283 | self.finished(session) 284 | } 285 | 286 | /// The client requests a pseudo-terminal with the given 287 | /// specifications. 288 | #[allow(unused_variables)] 289 | fn pty_request( 290 | self, 291 | channel: ChannelId, 292 | term: &str, 293 | col_width: u32, 294 | row_height: u32, 295 | pix_width: u32, 296 | pix_height: u32, 297 | modes: &[(Pty, u32)], 298 | session: Session, 299 | ) -> Self::FutureUnit { 300 | self.finished(session) 301 | } 302 | 303 | /// The client requests an X11 connection. 304 | #[allow(unused_variables)] 305 | fn x11_request( 306 | self, 307 | channel: ChannelId, 308 | single_connection: bool, 309 | x11_auth_protocol: &str, 310 | x11_auth_cookie: &str, 311 | x11_screen_number: u32, 312 | session: Session, 313 | ) -> Self::FutureUnit { 314 | self.finished(session) 315 | } 316 | 317 | /// The client wants to set the given environment variable. Check 318 | /// these carefully, as it is dangerous to allow any variable 319 | /// environment to be set. 320 | #[allow(unused_variables)] 321 | fn env_request( 322 | self, 323 | channel: ChannelId, 324 | variable_name: &str, 325 | variable_value: &str, 326 | session: Session, 327 | ) -> Self::FutureUnit { 328 | self.finished(session) 329 | } 330 | 331 | /// The client requests a shell. 332 | #[allow(unused_variables)] 333 | fn shell_request(self, channel: ChannelId, session: Session) -> Self::FutureUnit { 334 | self.finished(session) 335 | } 336 | 337 | /// The client sends a command to execute, to be passed to a 338 | /// shell. Make sure to check the command before doing so. 339 | #[allow(unused_variables)] 340 | fn exec_request(self, channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit { 341 | self.finished(session) 342 | } 343 | 344 | /// The client asks to start the subsystem with the given name 345 | /// (such as sftp). 346 | #[allow(unused_variables)] 347 | fn subsystem_request( 348 | self, 349 | channel: ChannelId, 350 | name: &str, 351 | session: Session, 352 | ) -> Self::FutureUnit { 353 | self.finished(session) 354 | } 355 | 356 | /// The client's pseudo-terminal window size has changed. 357 | #[allow(unused_variables)] 358 | fn window_change_request( 359 | self, 360 | channel: ChannelId, 361 | col_width: u32, 362 | row_height: u32, 363 | pix_width: u32, 364 | pix_height: u32, 365 | session: Session, 366 | ) -> Self::FutureUnit { 367 | self.finished(session) 368 | } 369 | 370 | /// The client is sending a signal (usually to pass to the 371 | /// currently running process). 372 | #[allow(unused_variables)] 373 | fn signal(self, channel: ChannelId, signal_name: Sig, session: Session) -> Self::FutureUnit { 374 | self.finished(session) 375 | } 376 | 377 | /// Used for reverse-forwarding ports, see 378 | /// [RFC4254](https://tools.ietf.org/html/rfc4254#section-7). 379 | #[allow(unused_variables)] 380 | fn tcpip_forward(self, address: &str, port: u32, session: Session) -> Self::FutureBool { 381 | self.finished_bool(session, false) 382 | } 383 | /// Used to stop the reverse-forwarding of a port, see 384 | /// [RFC4254](https://tools.ietf.org/html/rfc4254#section-7). 385 | #[allow(unused_variables)] 386 | fn cancel_tcpip_forward(self, address: &str, port: u32, session: Session) -> Self::FutureBool { 387 | self.finished_bool(session, false) 388 | } 389 | } 390 | 391 | /// Trait used to create new handlers when clients connect. 392 | pub trait Server { 393 | /// The type of handlers. 394 | type Handler: Handler+Send; 395 | /// Called when a new client connects. 396 | fn new(&self) -> Self::Handler; 397 | } 398 | 399 | /// Run this server. 400 | pub fn run(config: Arc, addr: &str, server: H) { 401 | 402 | let addr = addr.to_socket_addrs().unwrap().next().unwrap(); 403 | let socket = TcpListener::bind(&addr).unwrap(); 404 | 405 | let done = socket.incoming().for_each(move |socket| { 406 | let handler = server.new(); 407 | let connection = Connection::new(config.clone(), socket, handler).unwrap(); 408 | use tokio::executor::Executor; 409 | tokio::executor::DefaultExecutor::current().spawn(Box::new(connection.map_err(|err| println!("err {:?}", err)))).unwrap(); 410 | Ok(()) 411 | }).map_err(|_| ()); 412 | tokio::run(done); 413 | } 414 | -------------------------------------------------------------------------------- /thrussh-keys/src/key.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | use cryptovec::CryptoVec; 16 | use {Error, ErrorKind}; 17 | use encoding::{Encoding, Reader}; 18 | use std; 19 | use openssl; 20 | use sodium; 21 | use encoding; 22 | pub use signature::*; 23 | use openssl::pkey::{Public, Private}; 24 | 25 | /// Keys for elliptic curve Ed25519 cryptography. 26 | pub mod ed25519 { 27 | pub use sodium::ed25519::{PublicKey, SecretKey, keypair, verify_detached, sign_detached}; 28 | } 29 | 30 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 31 | /// Name of a public key algorithm. 32 | pub struct Name(pub &'static str); 33 | 34 | impl AsRef for Name { 35 | fn as_ref(&self) -> &str { 36 | self.0 37 | } 38 | } 39 | 40 | /// The name of the Ed25519 algorithm for SSH. 41 | pub const ED25519: Name = Name("ssh-ed25519"); 42 | /// The name of the ssh-sha2-512 algorithm for SSH. 43 | pub const RSA_SHA2_512: Name = Name("rsa-sha2-512"); 44 | /// The name of the ssh-sha2-256 algorithm for SSH. 45 | pub const RSA_SHA2_256: Name = Name("rsa-sha2-256"); 46 | 47 | pub const SSH_RSA: &'static str = "ssh-rsa"; 48 | 49 | impl Name { 50 | /// Base name of the private key file for a key name. 51 | pub fn identity_file(&self) -> &'static str { 52 | match *self { 53 | ED25519 => "id_ed25519", 54 | RSA_SHA2_512 => "id_rsa", 55 | RSA_SHA2_256 => "id_rsa", 56 | _ => unreachable!(), 57 | } 58 | } 59 | } 60 | 61 | #[doc(hidden)] 62 | pub trait Verify { 63 | fn verify_client_auth(&self, buffer: &[u8], sig: &[u8]) -> bool; 64 | fn verify_server_auth(&self, buffer: &[u8], sig: &[u8]) -> bool; 65 | } 66 | 67 | /// The hash function used for hashing buffers. 68 | #[derive(Eq, PartialEq, Clone, Copy, Debug, Hash, Serialize, Deserialize)] 69 | #[allow(non_camel_case_types)] 70 | pub enum SignatureHash { 71 | /// SHA2, 256 bits. 72 | SHA2_256, 73 | /// SHA2, 512 bits. 74 | SHA2_512, 75 | } 76 | 77 | impl SignatureHash { 78 | pub fn name(&self) -> Name { 79 | match *self { 80 | SignatureHash::SHA2_256 => RSA_SHA2_256, 81 | SignatureHash::SHA2_512 => RSA_SHA2_512, 82 | } 83 | } 84 | 85 | fn to_message_digest(&self) -> openssl::hash::MessageDigest { 86 | use openssl::hash::MessageDigest; 87 | match *self { 88 | SignatureHash::SHA2_256 => MessageDigest::sha256(), 89 | SignatureHash::SHA2_512 => MessageDigest::sha512(), 90 | } 91 | } 92 | } 93 | 94 | /// Public key 95 | #[derive(Eq, PartialEq, Debug)] 96 | pub enum PublicKey { 97 | #[doc(hidden)] 98 | Ed25519(sodium::ed25519::PublicKey), 99 | #[doc(hidden)] 100 | RSA { 101 | key: OpenSSLPKey, 102 | hash: SignatureHash, 103 | }, 104 | } 105 | 106 | /// A public key from OpenSSL. 107 | pub struct OpenSSLPKey(pub openssl::pkey::PKey); 108 | 109 | use std::cmp::{Eq, PartialEq}; 110 | impl PartialEq for OpenSSLPKey { 111 | fn eq(&self, b: &OpenSSLPKey) -> bool { 112 | self.0.public_eq(&b.0) 113 | } 114 | } 115 | impl Eq for OpenSSLPKey {} 116 | impl std::fmt::Debug for OpenSSLPKey { 117 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 118 | write!(f, "OpenSSLPKey {{ (hidden) }}") 119 | } 120 | } 121 | 122 | impl PublicKey { 123 | /// Parse a public key in SSH format. 124 | pub fn parse(algo: &[u8], pubkey: &[u8]) -> Result { 125 | match algo { 126 | b"ssh-ed25519" => { 127 | let mut p = pubkey.reader(0); 128 | let key_algo = p.read_string()?; 129 | let key_bytes = p.read_string()?; 130 | if key_algo != b"ssh-ed25519" || key_bytes.len() != sodium::ed25519::PUBLICKEY_BYTES { 131 | return Err(ErrorKind::CouldNotReadKey.into()); 132 | } 133 | let mut p = sodium::ed25519::PublicKey { 134 | key: [0; sodium::ed25519::PUBLICKEY_BYTES], 135 | sodium: sodium::Sodium::new() 136 | }; 137 | p.key.clone_from_slice(key_bytes); 138 | Ok(PublicKey::Ed25519(p)) 139 | } 140 | b"rsa-sha2-256" | b"rsa-sha2-512" => { 141 | let mut p = pubkey.reader(0); 142 | let key_algo = p.read_string()?; 143 | if key_algo != b"rsa-sha2-256" && key_algo != b"rsa-sha2-512" { 144 | return Err(ErrorKind::CouldNotReadKey.into()); 145 | } 146 | let key_e = p.read_string()?; 147 | let key_n = p.read_string()?; 148 | use openssl::rsa::Rsa; 149 | use openssl::pkey::PKey; 150 | use openssl::bn::BigNum; 151 | Ok(PublicKey::RSA { 152 | key: OpenSSLPKey(PKey::from_rsa(Rsa::from_public_components( 153 | BigNum::from_slice(key_n)?, 154 | BigNum::from_slice(key_e)?, 155 | )?)?), 156 | hash: { 157 | if algo == b"rsa-sha2-256" { 158 | SignatureHash::SHA2_256 159 | } else { 160 | SignatureHash::SHA2_512 161 | } 162 | }, 163 | }) 164 | } 165 | _ => Err(ErrorKind::CouldNotReadKey.into()), 166 | } 167 | } 168 | } 169 | 170 | impl PublicKey { 171 | 172 | /// Algorithm name for that key. 173 | pub fn name(&self) -> &'static str { 174 | match *self { 175 | PublicKey::Ed25519(_) => ED25519.0, 176 | PublicKey::RSA { ref hash, .. } => hash.name().0 177 | } 178 | } 179 | 180 | /// Verify a signature. 181 | pub fn verify_detached(&self, buffer: &[u8], sig: &[u8]) -> bool { 182 | match self { 183 | &PublicKey::Ed25519(ref public) => { 184 | sodium::ed25519::verify_detached(&sig, buffer, &public) 185 | } 186 | &PublicKey::RSA { 187 | ref key, 188 | ref hash, 189 | } => { 190 | use openssl::sign::*; 191 | let verify = || { 192 | let mut verifier = Verifier::new( 193 | hash.to_message_digest(), 194 | &key.0, 195 | )?; 196 | verifier.update(buffer)?; 197 | verifier.verify(&sig) 198 | }; 199 | verify().unwrap_or(false) 200 | } 201 | } 202 | } 203 | 204 | } 205 | 206 | 207 | impl Verify for PublicKey { 208 | fn verify_client_auth(&self, buffer: &[u8], sig: &[u8]) -> bool { 209 | self.verify_detached(buffer, sig) 210 | } 211 | fn verify_server_auth(&self, buffer: &[u8], sig: &[u8]) -> bool { 212 | self.verify_detached(buffer, sig) 213 | } 214 | } 215 | 216 | /// Public key exchange algorithms. 217 | pub enum KeyPair { 218 | Ed25519(sodium::ed25519::SecretKey), 219 | RSA { 220 | key: openssl::rsa::Rsa, 221 | hash: SignatureHash, 222 | }, 223 | } 224 | 225 | impl std::fmt::Debug for KeyPair { 226 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 227 | match *self { 228 | KeyPair::Ed25519(ref key) => { 229 | write!(f, "Ed25519 {{ public: {:?}, secret: (hidden) }}", &key.key[32..]) 230 | } 231 | KeyPair::RSA { .. } => { 232 | write!(f, "RSA {{ (hidden) }}") 233 | } 234 | } 235 | } 236 | } 237 | 238 | impl<'b> encoding::Bytes for &'b KeyPair { 239 | fn bytes(&self) -> &[u8] { 240 | self.name().as_bytes() 241 | } 242 | } 243 | 244 | 245 | impl KeyPair { 246 | /* 247 | pub fn public_components(&self) -> PublicKeyComponents { 248 | match self { 249 | &KeyPair::Ed25519(ref key) => { 250 | let mut public = [0; 32]; 251 | public.clone_from_slice(&key.key[32..]); 252 | PublicKeyComponents::Ed25519(public) 253 | }, 254 | &KeyPair::RSA { ref key, hash } => { 255 | let n = key.n().unwrap().to_vec(); 256 | let e = key.e().unwrap().to_vec(); 257 | PublicKeyComponents::RSA { n, e, hash } 258 | } 259 | } 260 | } 261 | */ 262 | 263 | /// Copy the public key of this algorithm. 264 | pub fn clone_public_key(&self) -> PublicKey { 265 | match self { 266 | &KeyPair::Ed25519(ref key) => { 267 | let mut public = sodium::ed25519::PublicKey { 268 | key: [0; 32], 269 | sodium: sodium::Sodium::new(), 270 | }; 271 | public.key.clone_from_slice(&key.key[32..]); 272 | PublicKey::Ed25519(public) 273 | }, 274 | &KeyPair::RSA { ref key, ref hash } => { 275 | use openssl::pkey::PKey; 276 | use openssl::rsa::Rsa; 277 | let key = Rsa::from_public_components( 278 | key.n().to_owned().unwrap(), 279 | key.e().to_owned().unwrap() 280 | ).unwrap(); 281 | PublicKey::RSA { 282 | key: OpenSSLPKey(PKey::from_rsa(key).unwrap()), 283 | hash: hash.clone() 284 | } 285 | } 286 | } 287 | } 288 | 289 | /// Name of this key algorithm. 290 | pub fn name(&self) -> &'static str { 291 | match *self { 292 | KeyPair::Ed25519(_) => ED25519.0, 293 | KeyPair::RSA { ref hash, .. } => hash.name().0, 294 | } 295 | } 296 | 297 | /// Generate a key pair. 298 | pub fn generate(t: Name) -> Option { 299 | match t { 300 | ED25519 => { 301 | let (public, secret) = sodium::ed25519::keypair(); 302 | assert_eq!(&public.key, &secret.key[32..]); 303 | Some(KeyPair::Ed25519(secret)) 304 | } 305 | _ => None, 306 | } 307 | } 308 | 309 | /// Sign a slice using this algorithm. 310 | pub fn sign_detached(&self, to_sign: &[u8]) -> Result { 311 | match self { 312 | &KeyPair::Ed25519(ref secret) => Ok(Signature::Ed25519(SignatureBytes(sodium::ed25519::sign_detached(to_sign.as_ref(), secret).0))), 313 | &KeyPair::RSA { ref key, ref hash } => Ok(Signature::RSA(rsa_signature(hash, key, to_sign.as_ref())?)) 314 | } 315 | } 316 | 317 | #[doc(hidden)] 318 | /// This is used by the server to sign the initial DH kex 319 | /// message. Note: we are not signing the same kind of thing as in 320 | /// the function below, `add_self_signature`. 321 | pub fn add_signature>( 322 | &self, 323 | buffer: &mut CryptoVec, 324 | to_sign: H, 325 | ) -> Result<(), Error> { 326 | match self { 327 | &KeyPair::Ed25519(ref secret) => { 328 | let signature = sodium::ed25519::sign_detached(to_sign.as_ref(), secret); 329 | 330 | buffer.push_u32_be((ED25519.0.len() + signature.0.len() + 8) as u32); 331 | buffer.extend_ssh_string(ED25519.0.as_bytes()); 332 | buffer.extend_ssh_string(&signature.0); 333 | } 334 | &KeyPair::RSA { ref key, ref hash } => { 335 | // https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2 336 | let signature = rsa_signature(hash, key, to_sign.as_ref())?; 337 | let name = hash.name(); 338 | buffer.push_u32_be((name.0.len() + signature.len() + 8) as u32); 339 | buffer.extend_ssh_string(name.0.as_bytes()); 340 | buffer.extend_ssh_string(&signature); 341 | } 342 | } 343 | Ok(()) 344 | } 345 | 346 | #[doc(hidden)] 347 | /// This is used by the client for authentication. Note: we are 348 | /// not signing the same kind of thing as in the above function, 349 | /// `add_signature`. 350 | pub fn add_self_signature(&self, buffer: &mut CryptoVec) -> Result<(), Error> { 351 | match self { 352 | &KeyPair::Ed25519(ref secret) => { 353 | let signature = sodium::ed25519::sign_detached(&buffer, secret); 354 | 355 | buffer.push_u32_be((ED25519.0.len() + signature.0.len() + 8) as u32); 356 | buffer.extend_ssh_string(ED25519.0.as_bytes()); 357 | buffer.extend_ssh_string(&signature.0); 358 | } 359 | &KeyPair::RSA { ref key, ref hash } => { 360 | 361 | // https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2 362 | let signature = rsa_signature(hash, key, buffer)?; 363 | let name = hash.name(); 364 | buffer.push_u32_be((name.0.len() + signature.len() + 8) as u32); 365 | buffer.extend_ssh_string(name.0.as_bytes()); 366 | buffer.extend_ssh_string(&signature); 367 | } 368 | } 369 | Ok(()) 370 | } 371 | } 372 | 373 | fn rsa_signature(hash: &SignatureHash, key: &openssl::rsa::Rsa, b: &[u8]) -> Result, Error> { 374 | use openssl::sign::Signer; 375 | use openssl::pkey::*; 376 | use openssl::rsa::*; 377 | let pkey = PKey::from_rsa(Rsa::from_private_components( 378 | key.n().to_owned()?, 379 | key.e().to_owned()?, 380 | key.d().to_owned()?, 381 | key.p().unwrap().to_owned()?, 382 | key.q().unwrap().to_owned()?, 383 | key.dmp1().unwrap().to_owned()?, 384 | key.dmq1().unwrap().to_owned()?, 385 | key.iqmp().unwrap().to_owned()?, 386 | )?)?; 387 | let mut signer = Signer::new(hash.to_message_digest(), &pkey)?; 388 | signer.update(b)?; 389 | Ok(signer.sign_to_vec()?) 390 | } 391 | 392 | /// Parse a public key from a byte slice. 393 | pub fn parse_public_key(p: &[u8]) -> Result { 394 | let mut pos = p.reader(0); 395 | let t = pos.read_string()?; 396 | if t == b"ssh-ed25519" { 397 | if let Ok(pubkey) = pos.read_string() { 398 | use sodium::ed25519; 399 | let mut p = ed25519::PublicKey { 400 | key: [0; ed25519::PUBLICKEY_BYTES], 401 | sodium: sodium::Sodium::new(), 402 | }; 403 | p.key.clone_from_slice(pubkey); 404 | return Ok(PublicKey::Ed25519(p)); 405 | } 406 | } 407 | if t == b"ssh-rsa" { 408 | let e = pos.read_string()?; 409 | let n = pos.read_string()?; 410 | use openssl::pkey::*; 411 | use openssl::rsa::*; 412 | use openssl::bn::*; 413 | return Ok(PublicKey::RSA { 414 | key: OpenSSLPKey(PKey::from_rsa(Rsa::from_public_components( 415 | BigNum::from_slice(n)?, 416 | BigNum::from_slice(e)?, 417 | )?)?), 418 | hash: SignatureHash::SHA2_256, 419 | }); 420 | } 421 | Err(ErrorKind::CouldNotReadKey.into()) 422 | } 423 | -------------------------------------------------------------------------------- /thrussh/src/session.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Pierre-Étienne Meunier 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | // 15 | 16 | use std; 17 | use auth; 18 | use negotiation; 19 | use kex; 20 | use cipher; 21 | use msg; 22 | use {Error, ChannelId, Channel, Disconnect}; 23 | use cryptovec::CryptoVec; 24 | use std::collections::HashMap; 25 | use Limits; 26 | use sshbuffer::SSHBuffer; 27 | use byteorder::{BigEndian, ByteOrder}; 28 | use openssl::hash; 29 | use thrussh_keys::encoding::Encoding; 30 | use std::num::Wrapping; 31 | use std::sync::Arc; 32 | 33 | #[derive(Debug)] 34 | pub(crate) struct Encrypted { 35 | pub state: Option, 36 | 37 | // It's always Some, except when we std::mem::replace it temporarily. 38 | pub exchange: Option, 39 | pub kex: kex::Algorithm, 40 | pub key: usize, 41 | pub mac: Option<&'static str>, 42 | pub session_id: hash::DigestBytes, 43 | pub rekey: Option, 44 | pub channels: HashMap, 45 | pub last_channel_id: Wrapping, 46 | pub wants_reply: bool, 47 | pub write: CryptoVec, 48 | pub write_cursor: usize, 49 | pub last_rekey: std::time::Instant, 50 | } 51 | 52 | pub(crate) struct CommonSession { 53 | pub auth_user: String, 54 | pub config: Config, 55 | pub encrypted: Option, 56 | pub auth_method: Option, 57 | pub write_buffer: SSHBuffer, 58 | pub kex: Option, 59 | pub cipher: Arc, 60 | pub wants_reply: bool, 61 | pub disconnected: bool, 62 | pub buffer: Option, 63 | } 64 | 65 | impl CommonSession { 66 | pub fn encrypted(&mut self, state: EncryptedState, newkeys: NewKeys) { 67 | if let Some(ref mut enc) = self.encrypted { 68 | enc.exchange = Some(newkeys.exchange); 69 | enc.kex = newkeys.kex; 70 | enc.key = newkeys.key; 71 | enc.mac = newkeys.names.mac; 72 | self.cipher = Arc::new(newkeys.cipher); 73 | } else { 74 | self.encrypted = Some(Encrypted { 75 | exchange: Some(newkeys.exchange), 76 | kex: newkeys.kex, 77 | key: newkeys.key, 78 | mac: newkeys.names.mac, 79 | session_id: newkeys.session_id, 80 | state: Some(state), 81 | rekey: None, 82 | channels: HashMap::new(), 83 | last_channel_id: Wrapping(1), 84 | wants_reply: false, 85 | write: CryptoVec::new(), 86 | write_cursor: 0, 87 | last_rekey: std::time::Instant::now(), 88 | }); 89 | self.cipher = Arc::new(newkeys.cipher); 90 | } 91 | } 92 | 93 | /// Send a disconnect message. 94 | pub fn disconnect(&mut self, reason: Disconnect, description: &str, language_tag: &str) { 95 | let disconnect = |buf: &mut CryptoVec| { 96 | push_packet!(buf, { 97 | buf.push(msg::DISCONNECT); 98 | buf.push_u32_be(reason as u32); 99 | buf.extend_ssh_string(description.as_bytes()); 100 | buf.extend_ssh_string(language_tag.as_bytes()); 101 | }); 102 | }; 103 | if !self.disconnected { 104 | self.disconnected = true; 105 | if let Some(ref mut enc) = self.encrypted { 106 | disconnect(&mut enc.write) 107 | } else { 108 | disconnect(&mut self.write_buffer.buffer) 109 | } 110 | } 111 | } 112 | 113 | /// Send a single byte message onto the channel. 114 | pub fn byte(&mut self, channel: ChannelId, msg: u8) { 115 | if let Some(ref mut enc) = self.encrypted { 116 | if let Some(channel) = enc.channels.get(&channel) { 117 | push_packet!(enc.write, { 118 | enc.write.push(msg); 119 | enc.write.push_u32_be(channel.recipient_channel); 120 | }); 121 | } 122 | } 123 | } 124 | } 125 | 126 | impl Encrypted { 127 | pub fn adjust_window_size(&mut self, channel: ChannelId, data: &[u8], target: u32) { 128 | debug!("adjust_window_size"); 129 | if let Some(ref mut channel) = self.channels.get_mut(&channel) { 130 | debug!("channel {:?}", channel); 131 | // Ignore extra data. 132 | // https://tools.ietf.org/html/rfc4254#section-5.2 133 | if data.len() as u32 <= channel.sender_window_size { 134 | channel.sender_window_size -= data.len() as u32; 135 | } 136 | if channel.sender_window_size < target / 2 { 137 | debug!( 138 | "sender_window_size {:?}, target {:?}", 139 | channel.sender_window_size, 140 | target 141 | ); 142 | push_packet!(self.write, { 143 | self.write.push(msg::CHANNEL_WINDOW_ADJUST); 144 | self.write.push_u32_be(channel.recipient_channel); 145 | self.write.push_u32_be(target - channel.sender_window_size); 146 | }); 147 | channel.sender_window_size = target; 148 | } 149 | } 150 | } 151 | 152 | pub fn data(&mut self, channel: ChannelId, extended: Option, buf: &[u8]) -> usize { 153 | use std::ops::Deref; 154 | if let Some(channel) = self.channels.get_mut(&channel) { 155 | assert!(channel.confirmed); 156 | debug!( 157 | "output {:?} {:?} {:?}", 158 | channel, 159 | buf.len(), 160 | &buf[..std::cmp::min(buf.len(), 100)] 161 | ); 162 | debug!("output {:?}", self.write.deref()); 163 | let mut buf = if buf.len() as u32 > channel.recipient_window_size { 164 | &buf[0..channel.recipient_window_size as usize] 165 | } else { 166 | buf 167 | }; 168 | let buf_len = buf.len(); 169 | 170 | while buf.len() > 0 { 171 | // Compute the length we're allowed to send. 172 | let off = std::cmp::min(buf.len(), channel.recipient_maximum_packet_size as usize); 173 | let off = std::cmp::min(off, channel.recipient_window_size as usize); 174 | push_packet!(self.write, { 175 | if let Some(ext) = extended { 176 | self.write.push(msg::CHANNEL_EXTENDED_DATA); 177 | self.write.push_u32_be(channel.recipient_channel); 178 | self.write.push_u32_be(ext); 179 | } else { 180 | self.write.push(msg::CHANNEL_DATA); 181 | self.write.push_u32_be(channel.recipient_channel); 182 | } 183 | self.write.extend_ssh_string(&buf[..off]); 184 | }); 185 | debug!("buffer: {:?}", self.write.deref().len()); 186 | channel.recipient_window_size -= off as u32; 187 | buf = &buf[off..] 188 | } 189 | debug!("buf.len() = {:?}, buf_len = {:?}", buf.len(), buf_len); 190 | buf_len 191 | } else { 192 | 0 193 | } 194 | } 195 | 196 | pub fn flush( 197 | &mut self, 198 | limits: &Limits, 199 | cipher: &cipher::CipherPair, 200 | write_buffer: &mut SSHBuffer, 201 | ) -> bool { 202 | // If there are pending packets (and we've not started to rekey), flush them. 203 | { 204 | while self.write_cursor < self.write.len() { 205 | 206 | let now = std::time::Instant::now(); 207 | let dur = now.duration_since(self.last_rekey); 208 | 209 | if write_buffer.bytes >= limits.rekey_write_limit || 210 | dur >= limits.rekey_time_limit 211 | { 212 | 213 | // Resetting those now is not strictly correct 214 | // (since we're resetting before the rekeying), 215 | // but since the bytes sent during rekeying will 216 | // be counted, the limits are still an upper bound 217 | // on the size that can be sent. 218 | write_buffer.bytes = 0; 219 | self.last_rekey = now; 220 | return true; 221 | 222 | } else { 223 | // Read a single packet, selfrypt and send it. 224 | let len = BigEndian::read_u32(&self.write[self.write_cursor..]) as usize; 225 | debug!("flushing len {:?}", len); 226 | let packet = &self.write[(self.write_cursor + 4).. 227 | (self.write_cursor + 4 + len)]; 228 | cipher.write(packet, write_buffer); 229 | self.write_cursor += 4 + len 230 | } 231 | } 232 | } 233 | if self.write_cursor >= self.write.len() { 234 | // If all packets have been written, clear. 235 | self.write_cursor = 0; 236 | self.write.clear(); 237 | } 238 | false 239 | } 240 | pub fn new_channel_id(&mut self) -> ChannelId { 241 | self.last_channel_id += Wrapping(1); 242 | while self.channels.contains_key( 243 | &ChannelId(self.last_channel_id.0), 244 | ) 245 | { 246 | self.last_channel_id += Wrapping(1) 247 | } 248 | ChannelId(self.last_channel_id.0) 249 | } 250 | pub fn new_channel(&mut self, window_size: u32, maxpacket: u32) -> ChannelId { 251 | loop { 252 | self.last_channel_id += Wrapping(1); 253 | if let std::collections::hash_map::Entry::Vacant(vacant_entry) = 254 | self.channels.entry(ChannelId(self.last_channel_id.0)) 255 | { 256 | vacant_entry.insert(Channel { 257 | recipient_channel: 0, 258 | sender_channel: ChannelId(self.last_channel_id.0), 259 | sender_window_size: window_size, 260 | recipient_window_size: 0, 261 | sender_maximum_packet_size: maxpacket, 262 | recipient_maximum_packet_size: 0, 263 | confirmed: false, 264 | wants_reply: false, 265 | }); 266 | return ChannelId(self.last_channel_id.0); 267 | } 268 | } 269 | } 270 | } 271 | 272 | 273 | 274 | 275 | #[derive(Debug)] 276 | pub enum EncryptedState { 277 | WaitingServiceRequest, 278 | WaitingAuthRequest(auth::AuthRequest), 279 | Authenticated, 280 | } 281 | 282 | 283 | #[derive(Debug)] 284 | pub struct Exchange { 285 | pub client_id: CryptoVec, 286 | pub server_id: CryptoVec, 287 | pub client_kex_init: CryptoVec, 288 | pub server_kex_init: CryptoVec, 289 | pub client_ephemeral: CryptoVec, 290 | pub server_ephemeral: CryptoVec, 291 | } 292 | 293 | impl Exchange { 294 | pub fn new() -> Self { 295 | Exchange { 296 | client_id: CryptoVec::new(), 297 | server_id: CryptoVec::new(), 298 | client_kex_init: CryptoVec::new(), 299 | server_kex_init: CryptoVec::new(), 300 | client_ephemeral: CryptoVec::new(), 301 | server_ephemeral: CryptoVec::new(), 302 | } 303 | } 304 | } 305 | 306 | #[derive(Debug)] 307 | pub enum Kex { 308 | /// Version number sent. `algo` and `sent` tell wether kexinit has 309 | /// been received, and sent, respectively. 310 | KexInit(KexInit), 311 | 312 | /// Algorithms have been determined, the DH algorithm should run. 313 | KexDh(KexDh), 314 | 315 | /// The kex has run. 316 | KexDhDone(KexDhDone), 317 | 318 | /// The DH is over, we've sent the NEWKEYS packet, and are waiting 319 | /// the NEWKEYS from the other side. 320 | NewKeys(NewKeys), 321 | } 322 | 323 | 324 | #[derive(Debug)] 325 | pub struct KexInit { 326 | pub algo: Option, 327 | pub exchange: Exchange, 328 | pub session_id: Option, 329 | pub sent: bool, 330 | } 331 | 332 | 333 | impl KexInit { 334 | pub fn received_rekey( 335 | ex: Exchange, 336 | algo: negotiation::Names, 337 | session_id: &hash::DigestBytes, 338 | ) -> Self { 339 | let mut kexinit = KexInit { 340 | exchange: ex, 341 | algo: Some(algo), 342 | sent: false, 343 | session_id: Some(session_id.clone()), 344 | }; 345 | kexinit.exchange.client_kex_init.clear(); 346 | kexinit.exchange.server_kex_init.clear(); 347 | kexinit.exchange.client_ephemeral.clear(); 348 | kexinit.exchange.server_ephemeral.clear(); 349 | kexinit 350 | } 351 | 352 | pub fn initiate_rekey(ex: Exchange, session_id: &hash::DigestBytes) -> Self { 353 | let mut kexinit = KexInit { 354 | exchange: ex, 355 | algo: None, 356 | sent: true, 357 | session_id: Some(session_id.clone()), 358 | }; 359 | kexinit.exchange.client_kex_init.clear(); 360 | kexinit.exchange.server_kex_init.clear(); 361 | kexinit.exchange.client_ephemeral.clear(); 362 | kexinit.exchange.server_ephemeral.clear(); 363 | kexinit 364 | } 365 | } 366 | 367 | #[derive(Debug)] 368 | pub struct KexDh { 369 | pub exchange: Exchange, 370 | pub names: negotiation::Names, 371 | pub key: usize, 372 | pub session_id: Option, 373 | } 374 | 375 | #[derive(Debug)] 376 | pub struct KexDhDone { 377 | pub exchange: Exchange, 378 | pub kex: kex::Algorithm, 379 | pub key: usize, 380 | pub session_id: Option, 381 | pub names: negotiation::Names, 382 | } 383 | 384 | impl KexDhDone { 385 | pub fn compute_keys( 386 | self, 387 | hash: hash::DigestBytes, 388 | buffer: &mut CryptoVec, 389 | buffer2: &mut CryptoVec, 390 | is_server: bool, 391 | ) -> Result { 392 | let session_id = if let Some(session_id) = self.session_id { 393 | session_id 394 | } else { 395 | hash.clone() 396 | }; 397 | // Now computing keys. 398 | let c = self.kex.compute_keys( 399 | &session_id, 400 | &hash, 401 | buffer, 402 | buffer2, 403 | self.names.cipher, 404 | is_server, 405 | )?; 406 | Ok(NewKeys { 407 | exchange: self.exchange, 408 | names: self.names, 409 | kex: self.kex, 410 | key: self.key, 411 | cipher: c, 412 | session_id: session_id, 413 | received: false, 414 | sent: false, 415 | }) 416 | } 417 | } 418 | 419 | #[derive(Debug)] 420 | pub struct NewKeys { 421 | pub exchange: Exchange, 422 | pub names: negotiation::Names, 423 | pub kex: kex::Algorithm, 424 | pub key: usize, 425 | pub cipher: cipher::CipherPair, 426 | pub session_id: hash::DigestBytes, 427 | pub received: bool, 428 | pub sent: bool, 429 | } 430 | --------------------------------------------------------------------------------