7FRJYUI62VW257VVFQXND6OKSAILVTHGEJCXFE6CG6FIOIUTDVYAC extern crate libc;#[macro_use]extern crate lazy_static;use libc::c_ulonglong;use libsodium_sys::*;lazy_static! {static ref SODIUM: i32 = unsafe { sodium_init() };}pub mod chacha20 {use super::*;pub const NONCE_BYTES: usize = 8;pub const KEY_BYTES: usize = 32;pub struct Nonce(pub [u8; NONCE_BYTES]);pub struct Key(pub [u8; KEY_BYTES]);pub fn chacha20_xor(c: &mut [u8], n: &Nonce, k: &Key) {lazy_static::initialize(&super::SODIUM);unsafe {crypto_stream_chacha20_xor(c.as_mut_ptr(),c.as_ptr(),c.len() as c_ulonglong,n.0.as_ptr(),k.0.as_ptr(),);}}pub fn chacha20_xor_ic(c: &mut [u8], n: &Nonce, ic: u64, k: &Key) {lazy_static::initialize(&super::SODIUM);unsafe {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(),);}}}pub mod poly1305 {use super::*;pub const KEY_BYTES: usize = 32;pub const TAG_BYTES: usize = 16;pub struct Key(pub [u8; KEY_BYTES]);pub struct Tag(pub [u8; TAG_BYTES]);pub fn poly1305_auth(m: &[u8], key: &Key) -> Tag {lazy_static::initialize(&super::SODIUM);let mut tag = Tag([0; TAG_BYTES]);unsafe {crypto_onetimeauth(tag.0.as_mut_ptr(),m.as_ptr(),m.len() as c_ulonglong,key.0.as_ptr(),);}tag}pub fn poly1305_verify(tag: &[u8], m: &[u8], key: &Key) -> bool {lazy_static::initialize(&super::SODIUM);if tag.len() != TAG_BYTES {false} else {unsafe {crypto_onetimeauth_verify(tag.as_ptr(),m.as_ptr(),m.len() as c_ulonglong,key.0.as_ptr(),) == 0}}}}pub mod ed25519 {use super::*;pub const PUBLICKEY_BYTES: usize = 32;pub const SECRETKEY_BYTES: usize = 64;pub const SIGNATURE_BYTES: usize = 64;/// Ed25519 public key.#[derive(Debug, PartialEq, Eq)]pub struct PublicKey {/// Actual keypub key: [u8; PUBLICKEY_BYTES],}impl PublicKey {pub fn new_zeroed() -> Self {PublicKey {key: [0; PUBLICKEY_BYTES],}}}/// Ed25519 secret key.#[derive(Clone)]pub struct SecretKey {/// Actual keypub key: [u8; SECRETKEY_BYTES],}impl SecretKey {pub fn new_zeroed() -> Self {SecretKey {key: [0; SECRETKEY_BYTES],}}}pub struct Signature(pub [u8; SIGNATURE_BYTES]);/// Generate a key pair.pub fn keypair() -> (PublicKey, SecretKey) {unsafe {lazy_static::initialize(&super::SODIUM);let mut pk = PublicKey {key: [0; PUBLICKEY_BYTES],};let mut sk = SecretKey {key: [0; SECRETKEY_BYTES],};crypto_sign_keypair(pk.key.as_mut_ptr(), sk.key.as_mut_ptr());(pk, sk)}}/// Verify a signature, `sig` could as well be a `Signature`.pub fn verify_detached(sig: &[u8], m: &[u8], pk: &PublicKey) -> bool {lazy_static::initialize(&super::SODIUM);if sig.len() == SIGNATURE_BYTES {unsafe {crypto_sign_verify_detached(sig.as_ptr(),m.as_ptr(),m.len() as c_ulonglong,pk.key.as_ptr(),) == 0}} else {false}}/// Sign a message with a secret key.pub fn sign_detached(m: &[u8], sk: &SecretKey) -> Signature {lazy_static::initialize(&super::SODIUM);let mut sig = Signature([0; SIGNATURE_BYTES]);let mut sig_len = 0;unsafe {crypto_sign_detached(sig.0.as_mut_ptr(),&mut sig_len,m.as_ptr(),m.len() as c_ulonglong,sk.key.as_ptr(),);}sig}}pub mod scalarmult {use super::*;pub const BYTES: usize = 32;#[derive(Debug)]pub struct Scalar(pub [u8; BYTES]);#[derive(Debug)]pub struct GroupElement(pub [u8; BYTES]);pub fn scalarmult_base(n: &Scalar) -> GroupElement {lazy_static::initialize(&super::SODIUM);let mut q = GroupElement([0; BYTES]);unsafe {crypto_scalarmult_curve25519_base(q.0.as_mut_ptr(), n.0.as_ptr());}q}pub fn scalarmult(n: &Scalar, p: &GroupElement) -> GroupElement {lazy_static::initialize(&super::SODIUM);let mut q = GroupElement([0; BYTES]);unsafe {crypto_scalarmult_curve25519(q.0.as_mut_ptr(), n.0.as_ptr(), p.0.as_ptr());}q}}
[package]name = "thrussh-libsodium"version = "0.2.1"license = "Apache-2.0/MIT"description = "Straightforward bindings to libsodium"homepage = "https://nest.pijul.com/pijul_org/thrussh"repository = "https://nest.pijul.com/pijul_org/thrussh"authors = ["pe@pijul.org <pe@pijul.org>"]include = [ "Cargo.toml", "src/lib.rs" ]edition = "2018"[dependencies]libc = "0.2"lazy_static = "1.4"libsodium-sys = "0.2"[build-dependencies]pkg-config = "0.3"vcpkg = "0.2"
use crate::key::SignatureHash;use crate::Error;use byteorder::{BigEndian, WriteBytesExt};use serde;use serde::de::{SeqAccess, Visitor};use serde::ser::SerializeTuple;use serde::{Deserialize, Deserializer, Serialize, Serializer};use std::fmt;pub struct SignatureBytes(pub [u8; 64]);/// The type of a signature, depending on the algorithm used.#[derive(Serialize, Deserialize, Clone)]pub enum Signature {/// An Ed25519 signatureEd25519(SignatureBytes),/// An RSA signatureRSA { hash: SignatureHash, bytes: Vec<u8> },}impl Signature {pub fn to_base64(&self) -> String {use crate::encoding::Encoding;let mut bytes_ = Vec::new();match self {Signature::Ed25519(ref bytes) => {let t = b"ssh-ed25519";bytes_.write_u32::<BigEndian>((t.len() + bytes.0.len() + 8) as u32).unwrap();bytes_.extend_ssh_string(t);bytes_.extend_ssh_string(&bytes.0[..]);}Signature::RSA {ref hash,ref bytes,} => {let t = match hash {SignatureHash::SHA2_256 => &b"rsa-sha2-256"[..],SignatureHash::SHA2_512 => &b"rsa-sha2-512"[..],SignatureHash::SHA1 => &b"ssh-rsa"[..],};bytes_.write_u32::<BigEndian>((t.len() + bytes.len() + 8) as u32).unwrap();bytes_.extend_ssh_string(t);bytes_.extend_ssh_string(&bytes[..]);}}data_encoding::BASE64_NOPAD.encode(&bytes_[..])}pub fn from_base64(s: &[u8]) -> Result<Self, anyhow::Error> {let bytes_ = data_encoding::BASE64_NOPAD.decode(s)?;use crate::encoding::Reader;let mut r = bytes_.reader(0);let sig = r.read_string()?;let mut r = sig.reader(0);let typ = r.read_string()?;let bytes = r.read_string()?;match typ {b"ssh-ed25519" => {let mut bytes_ = [0; 64];bytes_.clone_from_slice(bytes);Ok(Signature::Ed25519(SignatureBytes(bytes_)))}b"rsa-sha2-256" => Ok(Signature::RSA {hash: SignatureHash::SHA2_256,bytes: bytes.to_vec(),}),b"rsa-sha2-512" => Ok(Signature::RSA {hash: SignatureHash::SHA2_512,bytes: bytes.to_vec(),}),_ => Err((Error::UnknownSignatureType {sig_type: std::str::from_utf8(typ).unwrap_or("").to_string(),}).into()),}}}impl AsRef<[u8]> for Signature {fn as_ref(&self) -> &[u8] {match *self {Signature::Ed25519(ref signature) => &signature.0,Signature::RSA { ref bytes, .. } => &bytes[..],}}}impl AsRef<[u8]> for SignatureBytes {fn as_ref(&self) -> &[u8] {&self.0}}impl<'de> Deserialize<'de> for SignatureBytes {fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>whereD: Deserializer<'de>,{struct Vis;impl<'de> Visitor<'de> for Vis {type Value = SignatureBytes;fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {formatter.write_str("64 bytes")}fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {let mut result = [0; 64];for x in result.iter_mut() {if let Some(y) = seq.next_element()? {*x = y} else {return Err(serde::de::Error::invalid_length(64, &self));}}Ok(SignatureBytes(result))}}deserializer.deserialize_tuple(64, Vis)}}impl Serialize for SignatureBytes {fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>whereS: Serializer,{let mut tup = serializer.serialize_tuple(64)?;for byte in self.0.iter() {tup.serialize_element(byte)?;}tup.end()}}impl fmt::Debug for SignatureBytes {fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {write!(fmt, "{:?}", &self.0[..])}}impl Clone for SignatureBytes {fn clone(&self) -> Self {let mut result = SignatureBytes([0; 64]);result.0.clone_from_slice(&self.0);result}}
use {Error, ErrorKind, KEYTYPE_ED25519, KEYTYPE_RSA, rsa_key_from_components};use std;use thrussh::encoding::Reader;use thrussh::key;use super::is_base64_char;use hex::FromHex;use base64::{decode_config, MIME};use ring::signature;use untrusted;use yasna;use ring;use openssl::symm::{encrypt, decrypt, Cipher, Mode, Crypter};use openssl::hash::{MessageDigest, Hasher};use bcrypt_pbkdf;const PBES2: &'static [u64] = &[1, 2, 840, 113549, 1, 5, 13];const PBKDF2: &'static [u64] = &[1, 2, 840, 113549, 1, 5, 12];const HMAC_SHA256: &'static [u64] = &[1, 2, 840, 113549, 2, 9];const AES256CBC: &'static [u64] = &[2, 16, 840, 1, 101, 3, 4, 1, 42];const ED25519: &'static [u64] = &[1, 3, 101, 112];const RSA: &'static [u64] = &[1, 2, 840, 113549, 1, 1, 1];// https://tools.ietf.org/html/rfc5208fn decode_pkcs8(secret: &[u8],password: Option<&[u8]>,) -> Result<(key::Algorithm, super::KeyPairComponents), Error> {if let Some(pass) = password {// let mut sec = Vec::new();let secret = yasna::parse_der(&secret, |reader| {reader.read_sequence(|reader| {// Encryption parameterslet parameters = reader.next().read_sequence(|reader| {let oid = reader.next().read_oid()?;debug!("oid = {:?} {:?}", oid, oid.components().as_slice() == PBES2);if oid.components().as_slice() == PBES2 {asn1_read_pbes2(reader)} else {Ok(Err(ErrorKind::UnknownAlgorithm(oid).into()))}})?;// Ciphertextlet ciphertext = reader.next().read_bytes()?;Ok(parameters.map(|p| p.decrypt(pass, &ciphertext)))})})???;debug!("secret {:?}", secret);let mut oid = None;yasna::parse_der(&secret, |reader| {reader.read_sequence(|reader| {let version = reader.next().read_u64()?;debug!("version = {:?}", version);reader.next().read_sequence(|reader| {oid = Some(reader.next().read_oid()?);Ok(())}).unwrap_or(());Ok(())})}).unwrap_or(());debug!("pkcs8 oid {:?}", oid);let oid = if let Some(oid) = oid {oid} else {return Err(ErrorKind::CouldNotReadKey.into())};if oid.components().as_slice() == ED25519 {let components = signature::primitive::Ed25519KeyPairComponents::from_pkcs8(untrusted::Input::from(&secret),)?;debug!("components!");let keypair = signature::Ed25519KeyPair::from_pkcs8(untrusted::Input::from(&secret))?;debug!("keypair!");Ok((key::Algorithm::Ed25519(keypair),super::KeyPairComponents::Ed25519(components)))} else if oid.components().as_slice() == RSA {let components = signature::primitive::RSAKeyPairComponents::from_pkcs8(untrusted::Input::from(&secret),)?;let keypair = signature::RSAKeyPair::from_pkcs8(untrusted::Input::from(&secret))?;Ok((key::Algorithm::RSA(std::sync::Arc::new(keypair),key::RSAPublicKey {n: components.n.as_slice_less_safe().to_vec(),e: components.e.as_slice_less_safe().to_vec(),hash: key::SignatureHash::SHA2_512,},),super::KeyPairComponents::RSA(super::RSAKeyPairComponents::from_components(&components),),))} else {Err(ErrorKind::CouldNotReadKey.into())}} else {Err(ErrorKind::KeyIsEncrypted.into())}}#[cfg(test)]use env_logger;#[test]fn test_read_write_pkcs8() {env_logger::init().unwrap_or(());let r = ring::rand::SystemRandom::new();let key = ring::signature::Ed25519KeyPair::generate_pkcs8(&r).unwrap();let password = b"blabla";let ciphertext = encode_pkcs8(&r, &key, Some(password), 100).unwrap();let (_, comp) = decode_pkcs8(&ciphertext, Some(password)).unwrap();use super::KeyPairComponents;match comp {KeyPairComponents::Ed25519(_) => debug!("Ed25519"),KeyPairComponents::RSA(_) => debug!("RSA"),}}use yasna::models::ObjectIdentifier;pub fn encode_pkcs8<R:ring::rand::SecureRandom>(rand: &R,plaintext: &[u8],password: Option<&[u8]>,rounds: u32) -> Result<Vec<u8>, Error> {if let Some(pass) = password {let mut salt = [0; 64];rand.fill(&mut salt)?;let mut iv = [0; 16];rand.fill(&mut iv)?;let mut key = [0; 32]; // AES256-CBCring::pbkdf2::derive(&ring::digest::SHA256, rounds, &salt, pass, &mut key[..]);debug!("key = {:?}", key);let mut plaintext = plaintext.to_vec();let padding_len = 32 - (plaintext.len() % 32);plaintext.extend(std::iter::repeat(padding_len as u8).take(padding_len));debug!("plaintext {:?}", plaintext);let ciphertext = encrypt(Cipher::aes_256_cbc(), &key, Some(&iv), &plaintext)?;let v = yasna::construct_der(|writer| {writer.write_sequence(|writer| {// Encryption parameterswriter.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(PBES2));asn1_write_pbes2(writer.next(), rounds as u64, &salt, &iv)});// Ciphertextwriter.next().write_bytes(&ciphertext[..])})});Ok(v)} else {Err(ErrorKind::KeyIsEncrypted.into())}}fn asn1_write_pbes2(writer: yasna::DERWriter, rounds: u64, salt: &[u8], iv: &[u8]) {writer.write_sequence(|writer| {// 1. Key generation algorithmwriter.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(PBKDF2));asn1_write_pbkdf2(writer.next(), rounds, salt)});// 2. Encryption algorithm.writer.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(AES256CBC));writer.next().write_bytes(iv)});})}fn asn1_write_pbkdf2(writer: yasna::DERWriter, rounds: u64, salt: &[u8]) {writer.write_sequence(|writer| {writer.next().write_bytes(salt);writer.next().write_u64(rounds);writer.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(HMAC_SHA256));writer.next().write_null()})})}enum Algorithms {Pbes2(KeyDerivation, Encryption),}impl Algorithms {fn decrypt(&self, password: &[u8], cipher: &[u8]) -> Result<Vec<u8>, Error> {match *self {Algorithms::Pbes2(ref der, ref enc) => {let mut key = enc.key();der.derive(password, &mut key);let out = enc.decrypt(&key, cipher)?;Ok(out)}}}}impl KeyDerivation {fn derive(&self, password: &[u8], key: &mut [u8]) {match *self {KeyDerivation::Pbkdf2 {ref salt,rounds,digest,} => ring::pbkdf2::derive(digest, rounds as u32, salt, password, key),}}}enum Key {K128([u8; 16]),K256([u8; 32]),}impl std::ops::Deref for Key {type Target = [u8];fn deref(&self) -> &[u8] {match *self {Key::K128(ref k) => k,Key::K256(ref k) => k,}}}impl std::ops::DerefMut for Key {fn deref_mut(&mut self) -> &mut [u8] {match *self {Key::K128(ref mut k) => k,Key::K256(ref mut k) => k,}}}impl Encryption {fn key(&self) -> Key {match *self {Encryption::Aes128Cbc(_) => Key::K128([0; 16]),Encryption::Aes256Cbc(_) => Key::K256([0; 32]),}}fn decrypt(&self, key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {let (cipher, iv) = match *self {Encryption::Aes128Cbc(ref iv) => (Cipher::aes_128_cbc(), iv),Encryption::Aes256Cbc(ref iv) => (Cipher::aes_256_cbc(), iv),};let mut dec = decrypt(cipher,&key,Some(&iv[..]),ciphertext)?;pkcs_unpad(&mut dec);Ok(dec)}}enum KeyDerivation {Pbkdf2 {salt: Vec<u8>,rounds: u64,digest: &'static ring::digest::Algorithm,},}fn asn1_read_pbes2(reader: &mut yasna::BERReaderSeq,) -> Result<Result<Algorithms, Error>, yasna::ASN1Error> {reader.next().read_sequence(|reader| {// PBES2 has two components.// 1. Key generation algorithmlet keygen = reader.next().read_sequence(|reader| {let oid = reader.next().read_oid()?;if oid.components().as_slice() == PBKDF2 {asn1_read_pbkdf2(reader)} else {Ok(Err(ErrorKind::UnknownAlgorithm(oid).into()))}})?;// 2. Encryption algorithm.let algorithm = reader.next().read_sequence(|reader| {let oid = reader.next().read_oid()?;if oid.components().as_slice() == AES256CBC {asn1_read_aes256cbc(reader)} else {Ok(Err(ErrorKind::UnknownAlgorithm(oid).into()))}})?;Ok(keygen.and_then(|keygen| {algorithm.map(|algo| Algorithms::Pbes2(keygen, algo))}))})}fn asn1_read_pbkdf2(reader: &mut yasna::BERReaderSeq,) -> Result<Result<KeyDerivation, Error>, yasna::ASN1Error> {reader.next().read_sequence(|reader| {let salt = reader.next().read_bytes()?;let rounds = reader.next().read_u64()?;let digest = reader.next().read_sequence(|reader| {let oid = reader.next().read_oid()?;if oid.components().as_slice() == HMAC_SHA256 {reader.next().read_null()?;Ok(Ok(&ring::digest::SHA256))} else {Ok(Err(ErrorKind::UnknownAlgorithm(oid).into()))}})?;Ok(digest.map(|digest| {KeyDerivation::Pbkdf2 {salt,rounds,digest,}}))})}fn asn1_read_aes256cbc(reader: &mut yasna::BERReaderSeq,) -> Result<Result<Encryption, Error>, yasna::ASN1Error> {let iv = reader.next().read_bytes()?;let mut i = [0; 16];i.clone_from_slice(&iv);Ok(Ok(Encryption::Aes256Cbc(i)))}
#![deny(trivial_casts, unstable_features, unused_import_braces)]//! This crate contains methods to deal with SSH keys, as defined in//! crate Thrussh. This includes in particular various functions for//! opening key files, deciphering encrypted keys, and dealing with//! agents.//!//! The following example shows how to do all these in a single//! example: start and SSH agent server, connect to it with a client,//! decipher an encrypted private key (the password is `b"blabla"`),//! send it to the agent, and ask the agent to sign a piece of data//! (`b"Please sign this", below).//!//!```//! use thrussh_keys::*;//! use futures::Future;//!//! #[derive(Clone)]//! struct X{}//! impl agent::server::Agent for X {//! fn confirm(&self, _: std::sync::Arc<key::KeyPair>) -> Box<dyn Future<Output = bool> + Send + Unpin> {//! Box::new(futures::future::ready(true))//! }//! }//!//! const PKCS8_ENCRYPTED: &'static str = "-----BEGIN ENCRYPTED PRIVATE KEY-----\nMIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA\nMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE\n0KN9ednYwcTGSX3hg7fROhTw7JAJ1D4IdT1fsoGeNu2BFuIgF3cthGHe6S5zceI2\nMpkfwvHbsOlDFWMUIAb/VY8/iYxhNmd5J6NStMYRC9NC0fVzOmrJqE1wITqxtORx\nIkzqkgFUbaaiFFQPepsh5CvQfAgGEWV329SsTOKIgyTj97RxfZIKA+TR5J5g2dJY\nj346SvHhSxJ4Jc0asccgMb0HGh9UUDzDSql0OIdbnZW5KzYJPOx+aDqnpbz7UzY/\nP8N0w/pEiGmkdkNyvGsdttcjFpOWlLnLDhtLx8dDwi/sbEYHtpMzsYC9jPn3hnds\nTcotqjoSZ31O6rJD4z18FOQb4iZs3MohwEdDd9XKblTfYKM62aQJWH6cVQcg+1C7\njX9l2wmyK26Tkkl5Qg/qSfzrCveke5muZgZkFwL0GCcgPJ8RixSB4GOdSMa/hAMU\nkvFAtoV2GluIgmSe1pG5cNMhurxM1dPPf4WnD+9hkFFSsMkTAuxDZIdDk3FA8zof\nYhv0ZTfvT6V+vgH3Hv7Tqcxomy5Qr3tj5vvAqqDU6k7fC4FvkxDh2mG5ovWvc4Nb\nXv8sed0LGpYitIOMldu6650LoZAqJVv5N4cAA2Edqldf7S2Iz1QnA/usXkQd4tLa\nZ80+sDNv9eCVkfaJ6kOVLk/ghLdXWJYRLenfQZtVUXrPkaPpNXgD0dlaTN8KuvML\nUw/UGa+4ybnPsdVflI0YkJKbxouhp4iB4S5ACAwqHVmsH5GRnujf10qLoS7RjDAl\no/wSHxdT9BECp7TT8ID65u2mlJvH13iJbktPczGXt07nBiBse6OxsClfBtHkRLzE\nQF6UMEXsJnIIMRfrZQnduC8FUOkfPOSXc8r9SeZ3GhfbV/DmWZvFPCpjzKYPsM5+\nN8Bw/iZ7NIH4xzNOgwdp5BzjH9hRtCt4sUKVVlWfEDtTnkHNOusQGKu7HkBF87YZ\nRN/Nd3gvHob668JOcGchcOzcsqsgzhGMD8+G9T9oZkFCYtwUXQU2XjMN0R4VtQgZ\nrAxWyQau9xXMGyDC67gQ5xSn+oqMK0HmoW8jh2LG/cUowHFAkUxdzGadnjGhMOI2\nzwNJPIjF93eDF/+zW5E1l0iGdiYyHkJbWSvcCuvTwma9FIDB45vOh5mSR+YjjSM5\nnq3THSWNi7Cxqz12Q1+i9pz92T2myYKBBtu1WDh+2KOn5DUkfEadY5SsIu/Rb7ub\n5FBihk2RN3y/iZk+36I69HgGg1OElYjps3D+A9AjVby10zxxLAz8U28YqJZm4wA/\nT0HLxBiVw+rsHmLP79KvsT2+b4Diqih+VTXouPWC/W+lELYKSlqnJCat77IxgM9e\nYIhzD47OgWl33GJ/R10+RDoDvY4koYE+V5NLglEhbwjloo9Ryv5ywBJNS7mfXMsK\n/uf+l2AscZTZ1mhtL38efTQCIRjyFHc3V31DI0UdETADi+/Omz+bXu0D5VvX+7c6\nb1iVZKpJw8KUjzeUV8yOZhvGu3LrQbhkTPVYL555iP1KN0Eya88ra+FUKMwLgjYr\nJkUx4iad4dTsGPodwEP/Y9oX/Qk3ZQr+REZ8lg6IBoKKqqrQeBJ9gkm1jfKE6Xkc\nCog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux\n-----END ENCRYPTED PRIVATE KEY-----\n";//!//! fn main() {//! env_logger::try_init().unwrap_or(());//! let dir = tempdir::TempDir::new("thrussh").unwrap();//! let agent_path = dir.path().join("agent");//!//! let mut core = tokio::runtime::Runtime::new().unwrap();//! let agent_path_ = agent_path.clone();//! // Starting a server//! core.spawn(async move {//! let mut listener = tokio::net::UnixListener::bind(&agent_path_)//! .unwrap();//! thrussh_keys::agent::server::serve(listener.incoming(), X {}).await//! });//! let key = decode_secret_key(PKCS8_ENCRYPTED, Some(b"blabla")).unwrap();//! let public = key.clone_public_key();//! core.block_on(async move {//! let stream = tokio::net::UnixStream::connect(&agent_path).await?;//! let mut client = agent::client::AgentClient::connect(stream);//! client.add_identity(&key, &[agent::Constraint::KeyLifetime { seconds: 60 }]).await?;//! client.request_identities().await?;//! let buf = b"signed message";//! let sig = client.sign_request(&public, buf).await?.unwrap();//! assert!(public.verify_detached(buf, sig.as_ref()));//! Ok::<(), Error>(())//! }).unwrap()//! }//!```#![recursion_limit = "128"]extern crate thrussh_libsodium as sodium;#[macro_use]extern crate serde_derive;#[macro_use]extern crate thiserror;#[macro_use]extern crate log;#[cfg(test)]extern crate env_logger;use byteorder::{BigEndian, WriteBytesExt};use data_encoding::BASE64_MIME;use std::borrow::Cow;use std::fs::{File, OpenOptions};use std::io::{BufRead, BufReader, Read, Seek, SeekFrom, Write};use std::path::Path;pub mod encoding;pub mod key;pub mod signature;mod bcrypt_pbkdf;mod blowfish;mod format;pub use format::*;/// A module to write SSH agent.pub mod agent;#[derive(Debug, Error)]pub enum Error {/// The key could not be read, for an unknown reason#[error("Could not read key")]CouldNotReadKey,/// The type of the key is unsupported#[error("Unsupported key type")]UnsupportedKeyType(Vec<u8>),/// The key is encrypted (should supply a password?)#[error("The key is encrypted")]KeyIsEncrypted,/// Home directory could not be found#[error("No home directory found")]NoHomeDir,/// The server key has changed#[error("The server key changed at line {}", line)]KeyChanged { line: usize },/// The key uses an unsupported algorithm#[error("Unknown key algorithm")]UnknownAlgorithm(yasna::models::ObjectIdentifier),/// Index out of bounds#[error("Index out of bounds")]IndexOutOfBounds,/// Unknown signature type#[error("Unknown signature type: {}", sig_type)]UnknownSignatureType { sig_type: String },/// Agent protocol error#[error("Agent protocol error")]AgentProtocolError,#[error("Agent failure")]AgentFailure,}const KEYTYPE_ED25519: &'static [u8] = b"ssh-ed25519";const KEYTYPE_RSA: &'static [u8] = b"ssh-rsa";/// Load a public key from a file. Ed25519 and RSA keys are supported.////// ```/// thrussh_keys::load_public_key("/home/pe/.ssh/id_ed25519.pub").unwrap();/// ```pub fn load_public_key<P: AsRef<Path>>(path: P) -> Result<key::PublicKey, anyhow::Error> {let mut pubkey = String::new();let mut file = File::open(path.as_ref())?;file.read_to_string(&mut pubkey)?;let mut split = pubkey.split_whitespace();match (split.next(), split.next()) {(Some(_), Some(key)) => parse_public_key_base64(key),(Some(key), None) => parse_public_key_base64(key),_ => Err(Error::CouldNotReadKey.into()),}}/// Reads a public key from the standard encoding. In some cases, the/// encoding is prefixed with a key type identifier and a space (such/// as `ssh-ed25519 AAAAC3N...`).////// ```/// thrussh_keys::parse_public_key_base64("AAAAC3NzaC1lZDI1NTE5AAAAIJdD7y3aLq454yWBdwLWbieU1ebz9/cu7/QEXn9OIeZJ").is_ok();/// ```pub fn parse_public_key_base64(key: &str) -> Result<key::PublicKey, anyhow::Error> {let base = BASE64_MIME.decode(key.as_bytes())?;Ok(key::parse_public_key(&base)?)}pub trait PublicKeyBase64 {/// Create the base64 part of the public key blob.fn public_key_bytes(&self) -> Vec<u8>;fn public_key_base64(&self) -> String {BASE64_MIME.encode(&self.public_key_bytes())}}impl PublicKeyBase64 for key::PublicKey {fn public_key_bytes(&self) -> Vec<u8> {let name = self.name().as_bytes();let mut s = Vec::new();s.write_u32::<BigEndian>(name.len() as u32).unwrap();s.extend_from_slice(name);match *self {key::PublicKey::Ed25519(ref publickey) => {s.write_u32::<BigEndian>(publickey.key.len() as u32).unwrap();s.extend_from_slice(&publickey.key);}key::PublicKey::RSA { ref key, .. } => {use encoding::Encoding;s.extend_ssh_mpint(&key.0.rsa().unwrap().e().to_vec());s.extend_ssh_mpint(&key.0.rsa().unwrap().n().to_vec());}}s}}impl PublicKeyBase64 for key::KeyPair {fn public_key_bytes(&self) -> Vec<u8> {let name = self.name().as_bytes();let mut s = Vec::new();s.write_u32::<BigEndian>(name.len() as u32).unwrap();s.extend_from_slice(name);match *self {key::KeyPair::Ed25519(ref key) => {let public = &key.key[32..];s.write_u32::<BigEndian>(32).unwrap();s.extend_from_slice(&public);}key::KeyPair::RSA { ref key, .. } => {use encoding::Encoding;s.extend_ssh_mpint(&key.e().to_vec());s.extend_ssh_mpint(&key.n().to_vec());}}s}}/// Write a public key onto the provided `Write`, encoded in base-64.pub fn write_public_key_base64<W: Write>(mut w: W,publickey: &key::PublicKey,) -> Result<(), anyhow::Error> {let name = publickey.name().as_bytes();w.write_all(name)?;w.write_all(b" ")?;w.write_all(publickey.public_key_base64().as_bytes())?;Ok(())}/// Load a secret key, deciphering it with the supplied password if necessary.pub fn load_secret_key<P: AsRef<Path>>(secret_: P,password: Option<&[u8]>,) -> Result<key::KeyPair, anyhow::Error> {let mut secret_file = std::fs::File::open(secret_)?;let mut secret = String::new();secret_file.read_to_string(&mut secret)?;decode_secret_key(&secret, password)}fn is_base64_char(c: char) -> bool {(c >= 'a' && c <= 'z')|| (c >= 'A' && c <= 'Z')|| (c >= '0' && c <= '9')|| c == '/'|| c == '+'|| c == '='}/// Record a host's public key into a nonstandard location.pub fn learn_known_hosts_path<P: AsRef<Path>>(host: &str,port: u16,pubkey: &key::PublicKey,path: P,) -> Result<(), anyhow::Error> {if let Some(parent) = path.as_ref().parent() {std::fs::create_dir_all(parent)?}let mut file = OpenOptions::new().read(true).append(true).create(true).open(path)?;// Test whether the known_hosts file ends with a \nlet mut buf = [0; 1];let mut ends_in_newline = false;if file.seek(SeekFrom::End(-1)).is_ok() {file.read_exact(&mut buf)?;ends_in_newline = buf[0] == b'\n';}// Write the key.file.seek(SeekFrom::Start(0))?;let mut file = std::io::BufWriter::new(file);if !ends_in_newline {file.write(b"\n")?;}if port != 22 {write!(file, "[{}]:{} ", host, port)?} else {write!(file, "{} ", host)?}write_public_key_base64(&mut file, pubkey)?;file.write(b"\n")?;Ok(())}/// Check that a server key matches the one recorded in file `path`.pub fn check_known_hosts_path<P: AsRef<Path>>(host: &str,port: u16,pubkey: &key::PublicKey,path: P,) -> Result<bool, anyhow::Error> {let mut f = if let Ok(f) = File::open(path) {BufReader::new(f)} else {return Ok(false);};let mut buffer = String::new();let host_port = if port == 22 {Cow::Borrowed(host)} else {Cow::Owned(format!("[{}]:{}", host, port))};let mut line = 1;while f.read_line(&mut buffer).unwrap() > 0 {{if buffer.as_bytes()[0] == b'#' {buffer.clear();continue;}debug!("line = {:?}", buffer);let mut s = buffer.split(' ');let hosts = s.next();let _ = s.next();let key = s.next();match (hosts, key) {(Some(h), Some(k)) => {debug!("{:?} {:?}", h, k);let host_matches = h.split(',').any(|x| x == host_port);if host_matches {if &parse_public_key_base64(k)? == pubkey {return Ok(true);} else {return Err((Error::KeyChanged { line }).into());}}}_ => {}}}buffer.clear();line += 1;}Ok(false)}/// Record a host's public key into the user's known_hosts file.#[cfg(target_os = "windows")]pub fn learn_known_hosts(host: &str, port: u16, pubkey: &key::PublicKey) -> Result<()> {if let Some(mut known_host_file) = dirs::home_dir() {known_host_file.push("ssh");known_host_file.push("known_hosts");learn_known_hosts_path(host, port, pubkey, &known_host_file)} else {Err(Error::NoHomeDir.into())}}/// Record a host's public key into the user's known_hosts file.#[cfg(not(target_os = "windows"))]pub fn learn_known_hosts(host: &str,port: u16,pubkey: &key::PublicKey,) -> Result<(), anyhow::Error> {if let Some(mut known_host_file) = dirs::home_dir() {known_host_file.push(".ssh");known_host_file.push("known_hosts");learn_known_hosts_path(host, port, pubkey, &known_host_file)} else {Err(Error::NoHomeDir.into())}}/// Check whether the host is known, from its standard location.#[cfg(target_os = "windows")]pub fn check_known_hosts(host: &str,port: u16,pubkey: &key::PublicKey,) -> Result<bool, anyhow::Error> {if let Some(mut known_host_file) = dirs::home_dir() {known_host_file.push("ssh");known_host_file.push("known_hosts");check_known_hosts_path(host, port, pubkey, &known_host_file)} else {Err(Error::NoHomeDir.into())}}/// Check whether the host is known, from its standard location.#[cfg(not(target_os = "windows"))]pub fn check_known_hosts(host: &str,port: u16,pubkey: &key::PublicKey,) -> Result<bool, anyhow::Error> {if let Some(mut known_host_file) = dirs::home_dir() {known_host_file.push(".ssh");known_host_file.push("known_hosts");check_known_hosts_path(host, port, pubkey, &known_host_file)} else {Err(Error::NoHomeDir.into())}}#[cfg(test)]mod test {extern crate tempdir;use super::*;use futures::Future;use std::fs::File;use std::io::Write;const ED25519_KEY: &'static str = "-----BEGIN OPENSSH PRIVATE KEY-----b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABDLGyfA39J2FcJygtYqi5ISAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIN+Wjn4+4Fcvl2JlKpggT+wCRxpSvtqqpVrQrKN1/A22AAAAkOHDLnYZvYS6H9Q3S3Nk4ri3R2jAZlQlBbUos5FkHpYgNw65KCWCTXtP7ye2czMC3zjn2r98pJLobsLYQgRiHIv/CUdAdsqbvMPECB+wl/UQe+JpiSq66Z6GIt0801skPh20jxOO3F52SoX1IeO5D5PXfZrfSZlw6S8c7bwyp2FHxDewRx7/wNsnDM0T7nLv/Q==-----END OPENSSH PRIVATE KEY-----";const RSA_KEY: &'static str = "-----BEGIN OPENSSH PRIVATE KEY-----b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAuSvQ9m76zhRB4m0BUKPf17lwccj7KQ1Qtse63AOqP/VYItqEH8unrxPogXNBgrcCEm/ccLZZsyE3qgp3DRQkkqvJhZ6O8VBPsXxjZesRCqoFNCczy+Mf0R/QmvRnpu5+4DDLz0p7vrsRZW9ji/c98KzxeUonWgkplQaCBYLN875WdeUYMGtb1MLfNCEj177jgZl3CzttLRK3su6dckowXcXYv1gPTPZAwJb49J43o1QhV7+1zdwXvuFM6zuYHdu9ZHSKir6k1dXOET3/U+LWG5ofAo8oxUWv/7vs6h7MeajwkUeIBOWYtD+wGYRvVpxvj7nyOoWtg+jm0X6ndnsD+QAAA8irV+ZAq1fmQAAAAAdzc2gtcnNhAAABAQC5K9D2bvrOFEHibQFQo9/XuXBxyPspDVC2x7rcA6o/9Vgi2oQfy6evE+iBc0GCtwISb9xwtlmzITeqCncNFCSSq8mFno7xUE+xfGNl6xEKqgU0JzPL4x/RH9Ca9Gem7n7gMMvPSnu+uxFlb2OL9z3wrPF5SidaCSmVBoIFgs3zvlZ15Rgwa1vUwt80ISPXvuOBmXcLO20tErey7p1ySjBdxdi/WA9M9kDAlvj0njejVCFXv7XN3Be+4UzrO5gd271kdIqKvqTV1c4RPf9T4tYbmh8CjyjFRa//u+zqHsx5qPCRR4gE5Zi0P7AZhG9WnG+PufI6ha2D6ObRfqd2ewP5AAAAAwEAAQAAAQAdELqhI/RsSpO45eFR9hcZtnrm8WQzImrr9dfn1w9vMKSf++rHTuFIQvi48Q10ZiOGH1bbvlPAIVOqdjAPtnyzJRHhzmyjhjasJlk30zj+kod0kz63HzSMT9EfsYNfmYoCyMYFCKz52EU3xc87Vhi74XmZz0D0CgIj6TyZftmzC4YJCiwwU8K+29nxBhcbFRxpgwAksFL6PCSQsPl4y7yvXGcX+7lpZD8547v58q3jIkH1g2tBOusIuaiphDDStVJhVdKA55Z0Kju2kvCqsRIlf1efrq43blRgJFFFCxNZ8Cpolt4lOHhg+o3ucjILlCOgjDV8dB21YLxmgN5q+xFNAAAAgQC1P+eLUkHDFXnleCEVrWxL/DFxEyneLQz3IawGdw7cyAb7vxsYrGUvbVUFkxeiv397pDHLZ5U+t5cOYDBZ7G43Mt2gYfWBuRNvYhHA9Sdf38m5qPA6XCvm51f+FxInwd/kwRKH01RHJuRGsl/4Apu4DqVob8y00VWTYyV6JBNDkQAAAIEA322lj7ZJXfK/oLhMM/RS+DvaMea1g/q43mdRJFQQso4XRCL6IIVnoZXFeOxrMIRByVZBw+FSeB6OayWcZMySpJQBo70GdJOc3pJb3js0T+P2XA9+/jwXS58K9a+IkgLkv9XkfxNGNKyPEEzXC8QQzvjs1LbmO59VLko8ypwHq/cAAACBANQqaULI0qdwa0vmd3Ae1+k3YLZ0kapSQGVIMT2lkrhKV35tj7HIFpUPa4vitHzcUwtjYhqFezVF+JyPbJ/FspXmEc0g1fFnQp5/SkUwoN2zm8Up52GBelkq2Jk57mOMzWO0QzzNuNV/feJk02b2aE8rrAqPQR+u0AypRPmzHnOPAAAAEXJvb3RAMTQwOTExNTQ5NDBkAQ==-----END OPENSSH PRIVATE KEY-----";#[test]fn test_decode_ed25519_secret_key() {extern crate env_logger;env_logger::try_init().unwrap_or(());decode_secret_key(ED25519_KEY, Some(b"blabla")).unwrap();}#[test]fn test_decode_rsa_secret_key() {extern crate env_logger;env_logger::try_init().unwrap_or(());decode_secret_key(RSA_KEY, None).unwrap();}#[test]fn test_fingerprint() {let key = parse_public_key_base64("AAAAC3NzaC1lZDI1NTE5AAAAILagOJFgwaMNhBWQINinKOXmqS4Gh5NgxgriXwdOoINJ",).unwrap();assert_eq!(key.fingerprint(),"ldyiXa1JQakitNU5tErauu8DvWQ1dZ7aXu+rm7KQuog");}#[test]fn test_check_known_hosts() {env_logger::try_init().unwrap_or(());let dir = tempdir::TempDir::new("thrussh").unwrap();let path = dir.path().join("known_hosts");{let mut f = File::create(&path).unwrap();f.write(b"[localhost]:13265 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJdD7y3aLq454yWBdwLWbieU1ebz9/cu7/QEXn9OIeZJ\n#pijul.org,37.120.161.53 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G2sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X\npijul.org,37.120.161.53 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G1sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X\n").unwrap();}// Valid key, non-standard port.let host = "localhost";let port = 13265;let hostkey = parse_public_key_base64("AAAAC3NzaC1lZDI1NTE5AAAAIJdD7y3aLq454yWBdwLWbieU1ebz9/cu7/QEXn9OIeZJ",).unwrap();assert!(check_known_hosts_path(host, port, &hostkey, &path).unwrap());// Valid key, several hosts, port 22let host = "pijul.org";let port = 22;let hostkey = parse_public_key_base64("AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G1sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X",).unwrap();assert!(check_known_hosts_path(host, port, &hostkey, &path).unwrap());// Now with the key in a comment above, check that it's not recognizedlet host = "pijul.org";let port = 22;let hostkey = parse_public_key_base64("AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G2sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X",).unwrap();assert!(check_known_hosts_path(host, port, &hostkey, &path).is_err());}#[test]fn test_srhb() {env_logger::try_init().unwrap_or(());let key = "AAAAB3NzaC1yc2EAAAADAQABAAACAQC0Xtz3tSNgbUQAXem4d+d6hMx7S8Nwm/DOO2AWyWCru+n/+jQ7wz2b5+3oG2+7GbWZNGj8HCc6wJSA3jUsgv1N6PImIWclD14qvoqY3Dea1J0CJgXnnM1xKzBz9C6pDHGvdtySg+yzEO41Xt4u7HFn4Zx5SGuI2NBsF5mtMLZXSi33jCIWVIkrJVd7sZaY8jiqeVZBB/UvkLPWewGVuSXZHT84pNw4+S0Rh6P6zdNutK+JbeuO+5Bav4h9iw4t2sdRkEiWg/AdMoSKmo97Gigq2mKdW12ivnXxz3VfxrCgYJj9WwaUUWSfnAju5SiNly0cTEAN4dJ7yB0mfLKope1kRhPsNaOuUmMUqlu/hBDM/luOCzNjyVJ+0LLB7SV5vOiV7xkVd4KbEGKou8eeCR3yjFazUe/D1pjYPssPL8cJhTSuMc+/UC9zD8yeEZhB9V+vW4NMUR+lh5+XeOzenl65lWYd/nBZXLBbpUMf1AOfbz65xluwCxr2D2lj46iApSIpvE63i3LzFkbGl9GdUiuZJLMFJzOWdhGGc97cB5OVyf8umZLqMHjaImxHEHrnPh1MOVpv87HYJtSBEsN4/omINCMZrk++CRYAIRKRpPKFWV7NQHcvw3m7XLR3KaTYe+0/MINIZwGdou9fLUU3zSd521vDjA/weasH0CyDHq7sZw==";parse_public_key_base64(key).unwrap();}#[test]fn test_nikao() {env_logger::try_init().unwrap_or(());let key = "-----BEGIN RSA PRIVATE KEY-----MIIEpQIBAAKCAQEAw/FG8YLVoXhsUVZcWaY7iZekMxQ2TAfSVh0LTnRuzsumeLhb0fh4scIt4C4MLwpGe/u3vj290C28jLkOtysqnIpB4iBUrFNRmEz2YuvjOzkFE8Ju0l1VrTZ9APhpLZvzT2N7YmTXcLz1yWopCe4KqTHczEP4lfkothxEoACXMaxezt5owIYfagDaaH6jXJgJk1SQ5VYrROVpDjjX8/Zg01H1faFQUikYx0M8EwL1fY5B80Hd6DYSok8kUZGfkZT8HQ54DBgocjSs449CVqkVoQC1aDB+LZpMWovY15q7hFgfQmYDqulbZRWDxxogS6ui/zUR2IpX7wpQMKKkBS1qdQIDAQABAoIBAQCodpcCKfS2gSzPuapowY1KvP/FkskkEU18EDiaWWyzi1AzVn5LRo+udT6wEacUAoebLU5K2BaMF+aWLr1CKnDWaeA/JIDoMDJk+TaU0i5pyppc5LwXTXvOEpzi6rCzL/O++88nR4AbQ7smUom6KdksotwtGvttJe0ktaUi058qaoFZbels5Fwk5bM5GHDdV6De8uQjSfYV813PtM/6A5rRVBjC5uY0ocBHxPXkqAdHfJuVk0uApjLrbm6k0M2dg1X5oyhDOf7ZIzAgQGPgvtsVZkQlyrD1OoCMPwzgULPXTe8SktaP9EGvKdMf5kQOqUstqfyx+E4OZa0AT82weLjBAoGBAOUChhaLQShL3Vsml/Nuhhw5LsxU7Li34QWM6P5AH0HMtsSncH8XULYcUKGbCmmMkVb7GtsrHa4ozy0fjq0Iq9cgufolytlvC0t1vKRsOY6poC2MQgaZbqRa05IKwhZdHTr9SUwB/ngtVNWRzzbFKLkn2W5oCpQGStAKqz3LbKstAoGBANsJEyrXPbWbG+QWzerCIi6shQl+vzOd3cxqWyWJVaZglCXtlyySV2eKWRW7TcVvaXQrNzm/99GNnux3pUCY6szy+9eevjFLLHbd+knzCZWKTZiWZWr503h/ztfFwrMzhoAhz4nukD/OETugPvtG01c2sxZb/F8LH9KORznhlSlpAoGBAJnqg1J9j3JU4tZTbwcGfo5ThHeCkINp2owPc70GPbvMqf4sBzjz46QyDaM//9SGzFwocplhNhaKiQvrzMnRLSVucnCEm/xdXLr/y6S6tEiFCwnx3aJv1uQRw2bBYkcDmBTAjVXPdUcyOHU+BYXrJv6ioMlKlel8/SUsNoFWypeVAoGAXhr3Bjf1xlm+0O9PRyZjQ0RR4DN5eHbB/XpQcL8hclsaK3V5tuek79JL1f9kOYhVeVi74G7uzTSYbCY3dJp+ftGCjDAirNEMaIGUcEMgAgSqs/0h06VESwg2WRQZQ57GkbR1E2DQzuj9FG4TwSe700OoC9o3gqon4PHJ/j9CM8kCgYEAtPJf3xaeqtbiVVzpPAGcuPyajTzU0QHPrXEl8zr/+iSK4Thc1K+cb9sblB+ssEUQD5IQkhTWcsXdslINQeL77WhIMZ2vBAH8Hcin4jgcLmwUZfpfnnFsQaChXiDsryJZwsRnruvMRX9nedtqHrgnIsJLTXjppIhGhq5Kg4RQfOU=-----END RSA PRIVATE KEY-----";decode_secret_key(key, None).unwrap();}pub const PKCS8_RSA: &'static str = "-----BEGIN RSA PRIVATE KEY-----MIIEpAIBAAKCAQEAwBGetHjW+3bDQpVktdemnk7JXgu1NBWUM+ysifYLDBvJ9ttXGNZSyQKA4v/dNr0FhAJ8I9BuOTjYCy1YfKylhl5D/DiSSXFPsQzERMmGgAlYvU2U+FTxpBC11EZg69CPVMKKevfoUD+PZA5zB7Hc1dXFfwqFc5249SdbAwD39VTbrOUIWECvWZs6/ucQxHHXP2O9qxWqhzb/ddOnqsDHUNoeceiNiCf2anNymovrIMjAqq1Rt2UP3f06/Zt7Jx5AxKqS4seFkaDlMAK8JkEDuMDOdKI36raHkKanfx8CnGMSNjFQQtvnpD8VSGkDTJN3Qs14vj2wvS477BQXkBKN1QIDAQABAoIBABb6xLMw9f+2ENyJhTggagXsxTjkS7TElCu2OFp1PpMfTAWl7oDBO7xi+UqvdCcVbHCD35hlWpqsC2Ui8sBP46n040ts9UumK/Ox5FWaiuYMuDpF6vnfJ94KRcb0+KmeFVf9wpW9zWS0hhJhjC+yfwpyfiOZ/ad8imGCaOguGHyYiiwbRf381T/1FlaOGSae88h+O8SKTG1Oahq40HZ/KBQf9pij0mfVQhYBzsNu2JsHNx9+DwJkrXT7K9SHBpiBAKisTTCnQmS89GtE6J2+bq96WgugiM7X6OPnmBmE/q1TgV18OhT+rlvvNi5/n8Z1ag5Xlg1Rtq/bxByPCeIVHsECgYEA9dX+LQdv/Mg/VGIos2LbpJUhJDj0XWnTRq9Kk2tVzr+9aL5VikEb09UPIEa2ToL6LjlkDOnyqIMd/WY1W0+9Zf1ttg43S/6Rvv1W8YQde0Nc7QTcuZ1K9jSSP9hzsa3KZtx0fCtvVHm+ac9fP6u80tqumbiD2F0cnCZcSxOb4+UCgYEAyAKJ70nNKegH4rTCStAqR7WGAsdPE3hBsC814jguplCpb4TwID+U78Xxu0DQF8WtVJ10SJuR0R2q4L9uYWpo0MxdawSK5s9Am27MtJL0mkFQX0QiM7hSZ3oqimsdUdXwxCGgoktxCUUHDIPJNVd4Xjg0JTh4UZT6WK9hl1zLQzECgYEAiZRCFGc2KCzVLF9m0cXAkGIZUxFAyMqBv+w3+zq1oegyk1z5uE7pyOpS9cg9HME2TAo4UPXYpLAEZ5z8vWZp45sp/BoGnlQQsudK8gzzBtnTNp5i/MnnetQ/CNYVIVnWjSxRUHBqdMdRZhv0/Ugae5KA5myZ9MtfSJA7VJTbyHUCgYBCcS13M1IXaMAt3JRqm+pftfqVs7YeJqXTrGs/AiDlGQigRk4quFR2rpAV/3rhWsawxDmb4So4iJ16Wb2GWP4G1sz1vyWRdSnmOJGCLwtYrvfPHegqvEGLpHa7UsgDpol77hvZriwXwzmLO8A8mxkeW5dfAfpeR5o+mcxWpvnTEQKBgQCKx6Ln0ku6jDyuDzA9xV2/PET5D75X61R2yhdxi8zurY/5Qon3OWzkjn/nHT3AZghGngOnzyv9wPMKt9BTHyTB6DlB6bRVLDkmNqZh5Wi8U1/IjyNYI0t2xV/JrzLAwPoKk3bkqys3bUmgo6DxVC/6RmMwPQ0rmpw78kOgEej90g==-----END RSA PRIVATE KEY-----";#[test]fn test_loewenheim() {env_logger::try_init().unwrap_or(());let key = "-----BEGIN RSA PRIVATE KEY-----Proc-Type: 4,ENCRYPTEDDEK-Info: AES-128-CBC,80E4FCAD049EE007CCE1C65D52CDB87AZKBKtex8+DA/d08TTPp4vY8RV+r+1nUC1La+r0dSiXsfunRNDPcYhHbyA/Fdr9kQ+d1/E3cEb0k2nq7xYyMzy8hpNp/uHu7UfllGdaBusiPjHR+feg6AQfbM0FWpdGzo9l/Vho5Ocw8abQq1Q9aPW5QQXBURC7HtCQXbpuYjUAQBeea1LzPCw6UIF80GUUkY1AycXxVfx1AeURAKTZR4hsxC5pqI4yhAvVNXxP+tTTa9NE8lOP0yqVNurfIqyAnp5ELMwNdHXZyUcT+EH5PsC69ocQgEZqLs0chvke62woMOjeSpsW5cIjGohW9lOD1fnJkECVZ50kE0SDvcL4Y338tHwMt7wdwdj1dkAWSUjAJT4ShjqV/TzaLAiNAyRxLlcm3mAccaFIIBZG/bPLGI0B5+mf9VExXGJrbGlvURhtE3nwmjLg1vT8lVfqbyL3a+0tFvmDYn71L97t/3hcD2tVnKLv9g8+/OCsUAk3+/0eS7D6GpmlOMRHdLLUHc4SOmbIDT/dE6MjsCSm7n/JkTb8P+Ta1Hp94dUnX4pfjzZ+O8V1H8wv7QW5KsuJhJ8cn4eS3BEgNH1I4FCCjLsZdWve9ehV3/19WXh+BF4WXFq9b3plmfJgTiZslvjy4dgThmOhEK44+fN1UhzguofxTR4Maz7lcehQxGAxp14hf1EnaAEt3LVjEPEShgK5dx1FtuLWFz9nR4vZcMsaiszElrevqMhPQHXY7cnWqBenkMfkdcQDoZjKvV86K98kBIDMu+kf855vqRF8b2n/6HPdm3eqFh/F410nSB0bBSglUfyOZH1nS+cs79RQZEF9fNUmpHEPQtQ/PALohicj9Vh7rRaMKpsORdC8/Ahh20s01xL6siZ334ka3BLYT94UG796/C4K1S2kPdUP8POJ2HhaK2l6qaG8tcEX7HbwwZeKiEHVNvWuIGQO9TiDONLycp9x4ykNM3sv2pI7vEhs7d2NapWgNha1RcTSv0CQ6Th/qhGo73LBpVmKwombVImHAyMGAEaVF32OycVd9c9tDgW5KdhWedbeaxD6qkSs0no71083kYIS7c6iC1R3ZeufEkMhmxdwrciWTJ+ZAk6rS975onKz6mo/4PytcCY7Df/6xUxHF3iJCnuK8hNpLdJcdOiqEKzj/d5YGyw3J2r+NrlV1gs3FyvR3eMCWWH2gpIQISBpnEANY40PxA/ogH+nCUvI/On8m437ZeLTg6lnPqsE4nlk2hUEwRdy/SVaQURbn7YlcYIt0e81r5sBXb4MXkLrf0XRWmpSggdcaaMuXi7nVSdkgCMjGP7epS7HsfP46OrTtJLHn5LxvdOEaW53nPOVQg/PlVfDbwWl8adE3i3PDQOw9jhYXnYS3sv4R8M8y2GYEXbINrTJyUGrlNggKFS6ohHjgt0gsM2N/D8vBrQwnRtyymRnFd4dXFEYKAyt+vk0sa36eLfl0z6bWzIchkJbduraMODVc+NiJE0Qe6bwAi4HSpJ0qw2lKwVHYB8cdnNVv13acApod326/9itdbb3ltKJaj7gc0n6gmKY6r0/Ddufy1JZ6eihBCSJ64RARBXeg2rZpyT+xxhMEZLK5meOeR-----END RSA PRIVATE KEY-----";let key = decode_secret_key(key, Some(b"passphrase")).unwrap();let public = key.clone_public_key();let buf = b"blabla";let sig = key.sign_detached(buf).unwrap();assert!(public.verify_detached(buf, sig.as_ref()));}#[test]fn test_o01eg() {env_logger::try_init().unwrap_or(());let key = "-----BEGIN RSA PRIVATE KEY-----Proc-Type: 4,ENCRYPTEDDEK-Info: AES-128-CBC,EA77308AAF46981303D8C44D548D097EQR18hXmAgGehm1QMMYGF34PAtBpTj+8/ZPFx2zZxir7pzDpfYoNAIf/fzLsW1ruG0xo/ZK/T3/TpMgjmLsCR6q+KU4jmCcCqWQIGWYJt9ljFI5y/CXr5uqP3DKcqtdxQfbBAfXJ8ITF+Tj0Cljm2S1KYHor+mkil5Lf/ZNiHxcLfoI3xRnpd+2cemN9Ly9eYHNTbeWbLosfjwdfPJNWFNV5flm/j49klx/UhXhr5HNFNgp/MlTrvkH4rBt4wYPpEcZBykt4Fo1KGl95pT22inGxQEXVHF1Cfzrf5doYWxjiRTmfhpPSz/Tt0ev3+jIb8Htx6N8tNBoVxwCiQb7jj3XNim2OGohIp5vgW9sh6RDfIvr1jphVOgCTFKSo37xk0156EoCVo3VcLf+p0/QitbUHR+RGW/PvUJV/wFR5ShYqjI+N2iPhkD24kftJ/MjPtAAwCm/GYoYjGDhIzQMB+FETZKU5kz23MQtZFbYjzkcI/RE87c4fkToekNCdQrsoZwG0Ne2CxrwwEnipHCqT4qY+lZB9EbqQgbWOXJgxA7lfznBFjdSX7uDc/mnIt9Y6BMZRXH3PTfotHlHMe+Ypt5lfPBi/nruOl5wLo3L4kY5pUyqR0cXKNycIJZb/pJAnEryIb59pZP7njvoHzRqnC9dycnTFW3geK5LU+4+JMUS32F636aorunRCl6IBmVQHLuZ+ue714fn/Sn6H4dw6IH1HMDG1hr8ozP4sNUCiAQ05LsjDMGTdrUsr2iBBpkQhuVhUDZy9g/5XF1EgiMbZahmqi5WaJ5K75ToINHb7RjOE7MEiuZ+RPpmYLE0HXyn9XHTx0ZGr022dDI6nkvUm6OvEwLUUmmGKRHKe0y1EdICGNV+HWqnlhGDbLWeMyUcIYM6Zh9Dw3WXD3kROf5MrJ6n9MDIXx9jy7nmBh7m6zKjBVIw94TE0dsRcWb0O1IoqSzLQ6ihno+KsQHDyMVLEUz1TuE52rIpBmqexDm3PdDfCgsNdBKP6QSTcoqcfHKeexK93FWgSlvFFQQAkJumJJ+B7ZWnK+2pdjdtWwTpflAKNqc8t//WmjWZzCtbhTHCXV1dnMk7azWltBAuXnjW+OqmuAzyh3ayKgqfW66mzSuyQNa1KqFhqpJxOG7IHvxVfQkYeSpqODnL87Zd/dU8s0lOxz3/ymtjPMHlOZ/nHNqW90IIeUwWJKJ46Kv6zXqM1tMeD1lvysBbU9rmcUdop0D3MOgGpKkinR5gy4pUsARBiz4WhIm8muZFIObWes/GDSzmmkQRO1IcfXKAHbq/OdwbLBm4vM9nk8vPfszoEQCnfOSd7aWrLRjDR+q2RnzNzhK+fodaJ864JFIfB/A+aVviVWvBSt0eEbEawhTmNPerMrAQ8tRRhmNxqlDP4gOcziiKUmK5recsXk5us5Ik7peIR/f9GAghpoJkF0HrHio47SfABuK30pzcj62uNWGljS3d9UQLCepT6RiPFhks/lgimbtSoiJHql1H9Q/3q4MuO2PuG7FXzlTnui3zGw/Vvybr8gXU8KyiY9sZVbmplRPF+ar462zcI2kt0a18mr0vbrdqp2eMjb37QDbVBJ+rPE-----END RSA PRIVATE KEY-----";decode_secret_key(key, Some(b"12345")).unwrap();}#[test]fn test_pkcs8() {env_logger::try_init().unwrap_or(());println!("test");decode_secret_key(PKCS8_RSA, Some(b"blabla")).unwrap();}const PKCS8_ENCRYPTED: &'static str = "-----BEGIN ENCRYPTED PRIVATE KEY-----MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggAMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE0KN9ednYwcTGSX3hg7fROhTw7JAJ1D4IdT1fsoGeNu2BFuIgF3cthGHe6S5zceI2MpkfwvHbsOlDFWMUIAb/VY8/iYxhNmd5J6NStMYRC9NC0fVzOmrJqE1wITqxtORxIkzqkgFUbaaiFFQPepsh5CvQfAgGEWV329SsTOKIgyTj97RxfZIKA+TR5J5g2dJYj346SvHhSxJ4Jc0asccgMb0HGh9UUDzDSql0OIdbnZW5KzYJPOx+aDqnpbz7UzY/P8N0w/pEiGmkdkNyvGsdttcjFpOWlLnLDhtLx8dDwi/sbEYHtpMzsYC9jPn3hndsTcotqjoSZ31O6rJD4z18FOQb4iZs3MohwEdDd9XKblTfYKM62aQJWH6cVQcg+1C7jX9l2wmyK26Tkkl5Qg/qSfzrCveke5muZgZkFwL0GCcgPJ8RixSB4GOdSMa/hAMUkvFAtoV2GluIgmSe1pG5cNMhurxM1dPPf4WnD+9hkFFSsMkTAuxDZIdDk3FA8zofYhv0ZTfvT6V+vgH3Hv7Tqcxomy5Qr3tj5vvAqqDU6k7fC4FvkxDh2mG5ovWvc4NbXv8sed0LGpYitIOMldu6650LoZAqJVv5N4cAA2Edqldf7S2Iz1QnA/usXkQd4tLaZ80+sDNv9eCVkfaJ6kOVLk/ghLdXWJYRLenfQZtVUXrPkaPpNXgD0dlaTN8KuvMLUw/UGa+4ybnPsdVflI0YkJKbxouhp4iB4S5ACAwqHVmsH5GRnujf10qLoS7RjDAlo/wSHxdT9BECp7TT8ID65u2mlJvH13iJbktPczGXt07nBiBse6OxsClfBtHkRLzEQF6UMEXsJnIIMRfrZQnduC8FUOkfPOSXc8r9SeZ3GhfbV/DmWZvFPCpjzKYPsM5+N8Bw/iZ7NIH4xzNOgwdp5BzjH9hRtCt4sUKVVlWfEDtTnkHNOusQGKu7HkBF87YZRN/Nd3gvHob668JOcGchcOzcsqsgzhGMD8+G9T9oZkFCYtwUXQU2XjMN0R4VtQgZrAxWyQau9xXMGyDC67gQ5xSn+oqMK0HmoW8jh2LG/cUowHFAkUxdzGadnjGhMOI2zwNJPIjF93eDF/+zW5E1l0iGdiYyHkJbWSvcCuvTwma9FIDB45vOh5mSR+YjjSM5nq3THSWNi7Cxqz12Q1+i9pz92T2myYKBBtu1WDh+2KOn5DUkfEadY5SsIu/Rb7ub5FBihk2RN3y/iZk+36I69HgGg1OElYjps3D+A9AjVby10zxxLAz8U28YqJZm4wA/T0HLxBiVw+rsHmLP79KvsT2+b4Diqih+VTXouPWC/W+lELYKSlqnJCat77IxgM9eYIhzD47OgWl33GJ/R10+RDoDvY4koYE+V5NLglEhbwjloo9Ryv5ywBJNS7mfXMsK/uf+l2AscZTZ1mhtL38efTQCIRjyFHc3V31DI0UdETADi+/Omz+bXu0D5VvX+7c6b1iVZKpJw8KUjzeUV8yOZhvGu3LrQbhkTPVYL555iP1KN0Eya88ra+FUKMwLgjYrJkUx4iad4dTsGPodwEP/Y9oX/Qk3ZQr+REZ8lg6IBoKKqqrQeBJ9gkm1jfKE6XkcCog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/MtlOAr5rux-----END ENCRYPTED PRIVATE KEY-----";#[test]fn test_gpg() {env_logger::try_init().unwrap_or(());let algo = [115, 115, 104, 45, 114, 115, 97];let key = [0, 0, 0, 7, 115, 115, 104, 45, 114, 115, 97, 0, 0, 0, 3, 1, 0, 1, 0, 0, 1, 129, 0, 163,72, 59, 242, 4, 248, 139, 217, 57, 126, 18, 195, 170, 3, 94, 154, 9, 150, 89, 171, 236,192, 178, 185, 149, 73, 210, 121, 95, 126, 225, 209, 199, 208, 89, 130, 175, 229, 163,102, 176, 155, 69, 199, 155, 71, 214, 170, 61, 202, 2, 207, 66, 198, 147, 65, 10, 176,20, 105, 197, 133, 101, 126, 193, 252, 245, 254, 182, 14, 250, 118, 113, 18, 220, 38,220, 75, 247, 50, 163, 39, 2, 61, 62, 28, 79, 199, 238, 189, 33, 194, 190, 22, 87, 91,1, 215, 115, 99, 138, 124, 197, 127, 237, 228, 170, 42, 25, 117, 1, 106, 36, 54, 163,163, 207, 129, 133, 133, 28, 185, 170, 217, 12, 37, 113, 181, 182, 180, 178, 23, 198,233, 31, 214, 226, 114, 146, 74, 205, 177, 82, 232, 238, 165, 44, 5, 250, 150, 236, 45,30, 189, 254, 118, 55, 154, 21, 20, 184, 235, 223, 5, 20, 132, 249, 147, 179, 88, 146,6, 100, 229, 200, 221, 157, 135, 203, 57, 204, 43, 27, 58, 85, 54, 219, 138, 18, 37,80, 106, 182, 95, 124, 140, 90, 29, 48, 193, 112, 19, 53, 84, 201, 153, 52, 249, 15,41, 5, 11, 147, 18, 8, 27, 31, 114, 45, 224, 118, 111, 176, 86, 88, 23, 150, 184, 252,128, 52, 228, 90, 30, 34, 135, 234, 123, 28, 239, 90, 202, 239, 188, 175, 8, 141, 80,59, 194, 80, 43, 205, 34, 137, 45, 140, 244, 181, 182, 229, 247, 94, 216, 115, 173,107, 184, 170, 102, 78, 249, 4, 186, 234, 169, 148, 98, 128, 33, 115, 232, 126, 84, 76,222, 145, 90, 58, 1, 4, 163, 243, 93, 215, 154, 205, 152, 178, 109, 241, 197, 82, 148,222, 78, 44, 193, 248, 212, 157, 118, 217, 75, 211, 23, 229, 121, 28, 180, 208, 173,204, 14, 111, 226, 25, 163, 220, 95, 78, 175, 189, 168, 67, 159, 179, 176, 200, 150,202, 248, 174, 109, 25, 89, 176, 220, 226, 208, 187, 84, 169, 157, 14, 88, 217, 221,117, 254, 51, 45, 93, 184, 80, 225, 158, 29, 76, 38, 69, 72, 71, 76, 50, 191, 210, 95,152, 175, 26, 207, 91, 7,];debug!("algo = {:?}", std::str::from_utf8(&algo));key::PublicKey::parse(&algo, &key).unwrap();}#[test]fn test_pkcs8_encrypted() {env_logger::try_init().unwrap_or(());println!("test");decode_secret_key(PKCS8_ENCRYPTED, Some(b"blabla")).unwrap();}fn test_client_agent(key: key::KeyPair) {env_logger::try_init().unwrap_or(());use std::process::{Command, Stdio};let dir = tempdir::TempDir::new("thrussh").unwrap();let agent_path = dir.path().join("agent");let mut agent = Command::new("ssh-agent").arg("-a").arg(&agent_path).arg("-D").stdout(Stdio::null()).stderr(Stdio::null()).spawn().expect("failed to execute process");std::thread::sleep(std::time::Duration::from_millis(10));let mut rt = tokio::runtime::Runtime::new().unwrap();rt.block_on(async move {let public = key.clone_public_key();let stream = tokio::net::UnixStream::connect(&agent_path).await?;let mut client = agent::client::AgentClient::connect(stream);client.add_identity(&key, &[]).await?;client.request_identities().await?;let buf = cryptovec::CryptoVec::from_slice(b"blabla");let len = buf.len();let (_, buf) = client.sign_request(&public, buf).await?;let (a, b) = buf.split_at(len);assert!(public.verify_detached(a, b));Ok::<(), anyhow::Error>(())}).unwrap();agent.kill().unwrap();agent.wait().unwrap();}#[test]fn test_client_agent_ed25519() {let key = decode_secret_key(ED25519_KEY, Some(b"blabla")).unwrap();test_client_agent(key)}#[test]fn test_client_agent_rsa() {let key = decode_secret_key(PKCS8_ENCRYPTED, Some(b"blabla")).unwrap();test_client_agent(key)}#[test]fn test_client_agent_openssh_rsa() {let key = decode_secret_key(RSA_KEY, None).unwrap();test_client_agent(key)}#[test]fn test_agent() {env_logger::try_init().unwrap_or(());let dir = tempdir::TempDir::new("thrussh").unwrap();let agent_path = dir.path().join("agent");let mut core = tokio::runtime::Runtime::new().unwrap();use agent;#[derive(Clone)]struct X {}impl agent::server::Agent for X {fn confirm(self,_: std::sync::Arc<key::KeyPair>,) -> Box<dyn Future<Output = (Self, bool)> + Send + Unpin> {Box::new(futures::future::ready((self, true)))}}let agent_path_ = agent_path.clone();core.spawn(async move {let mut listener = tokio::net::UnixListener::bind(&agent_path_).unwrap();agent::server::serve(listener.incoming(), X {}).await});let key = decode_secret_key(PKCS8_ENCRYPTED, Some(b"blabla")).unwrap();let public = key.clone_public_key();core.block_on(async move {let stream = tokio::net::UnixStream::connect(&agent_path).await?;let mut client = agent::client::AgentClient::connect(stream);client.add_identity(&key, &[agent::Constraint::KeyLifetime { seconds: 60 }]).await?;client.request_identities().await?;let buf = cryptovec::CryptoVec::from_slice(b"blabla");let len = buf.len();let (_, buf) = client.sign_request(&public, buf).await?;let (a, b) = buf.split_at(len);assert!(public.verify_detached(a, b));Ok::<(), anyhow::Error>(())}).unwrap()}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use crate::encoding::{Encoding, Reader};pub use crate::signature::*;use crate::Error;use cryptovec::CryptoVec;use openssl::pkey::{Private, Public};/// Keys for elliptic curve Ed25519 cryptography.pub mod ed25519 {pub use sodium::ed25519::{keypair, sign_detached, verify_detached, PublicKey, SecretKey};}#[derive(Debug, PartialEq, Eq, Copy, Clone)]/// Name of a public key algorithm.pub struct Name(pub &'static str);impl AsRef<str> for Name {fn as_ref(&self) -> &str {self.0}}/// The name of the Ed25519 algorithm for SSH.pub const ED25519: Name = Name("ssh-ed25519");/// The name of the ssh-sha2-512 algorithm for SSH.pub const RSA_SHA2_512: Name = Name("rsa-sha2-512");/// The name of the ssh-sha2-256 algorithm for SSH.pub const RSA_SHA2_256: Name = Name("rsa-sha2-256");pub const SSH_RSA: Name = Name("ssh-rsa");impl Name {/// Base name of the private key file for a key name.pub fn identity_file(&self) -> &'static str {match *self {ED25519 => "id_ed25519",RSA_SHA2_512 => "id_rsa",RSA_SHA2_256 => "id_rsa",_ => unreachable!(),}}}#[doc(hidden)]pub trait Verify {fn verify_client_auth(&self, buffer: &[u8], sig: &[u8]) -> bool;fn verify_server_auth(&self, buffer: &[u8], sig: &[u8]) -> bool;}/// The hash function used for hashing buffers.#[derive(Eq, PartialEq, Clone, Copy, Debug, Hash, Serialize, Deserialize)]#[allow(non_camel_case_types)]pub enum SignatureHash {/// SHA2, 256 bits.SHA2_256,/// SHA2, 512 bits.SHA2_512,/// SHA1SHA1,}impl SignatureHash {pub fn name(&self) -> Name {match *self {SignatureHash::SHA2_256 => RSA_SHA2_256,SignatureHash::SHA2_512 => RSA_SHA2_512,SignatureHash::SHA1 => SSH_RSA,}}fn to_message_digest(&self) -> openssl::hash::MessageDigest {use openssl::hash::MessageDigest;match *self {SignatureHash::SHA2_256 => MessageDigest::sha256(),SignatureHash::SHA2_512 => MessageDigest::sha512(),SignatureHash::SHA1 => MessageDigest::sha1(),}}}/// Public key#[derive(Eq, PartialEq, Debug)]pub enum PublicKey {#[doc(hidden)]Ed25519(sodium::ed25519::PublicKey),#[doc(hidden)]RSA {key: OpenSSLPKey,hash: SignatureHash,},}/// A public key from OpenSSL.pub struct OpenSSLPKey(pub openssl::pkey::PKey<Public>);use std::cmp::{Eq, PartialEq};impl PartialEq for OpenSSLPKey {fn eq(&self, b: &OpenSSLPKey) -> bool {self.0.public_eq(&b.0)}}impl Eq for OpenSSLPKey {}impl std::fmt::Debug for OpenSSLPKey {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {write!(f, "OpenSSLPKey {{ (hidden) }}")}}impl PublicKey {/// Parse a public key in SSH format.pub fn parse(algo: &[u8], pubkey: &[u8]) -> Result<Self, anyhow::Error> {match algo {b"ssh-ed25519" => {let mut p = pubkey.reader(0);let key_algo = p.read_string()?;let key_bytes = p.read_string()?;if key_algo != b"ssh-ed25519" || key_bytes.len() != sodium::ed25519::PUBLICKEY_BYTES{return Err(Error::CouldNotReadKey.into());}let mut p = sodium::ed25519::PublicKey {key: [0; sodium::ed25519::PUBLICKEY_BYTES],};p.key.clone_from_slice(key_bytes);Ok(PublicKey::Ed25519(p))}b"ssh-rsa" | b"rsa-sha2-256" | b"rsa-sha2-512" => {let mut p = pubkey.reader(0);let key_algo = p.read_string()?;debug!("{:?}", std::str::from_utf8(key_algo));if key_algo != b"ssh-rsa"&& key_algo != b"rsa-sha2-256"&& key_algo != b"rsa-sha2-512"{return Err(Error::CouldNotReadKey.into());}let key_e = p.read_string()?;let key_n = p.read_string()?;use openssl::bn::BigNum;use openssl::pkey::PKey;use openssl::rsa::Rsa;Ok(PublicKey::RSA {key: OpenSSLPKey(PKey::from_rsa(Rsa::from_public_components(BigNum::from_slice(key_n)?,BigNum::from_slice(key_e)?,)?)?),hash: {if algo == b"rsa-sha2-256" {SignatureHash::SHA2_256} else if algo == b"rsa-sha2-512" {SignatureHash::SHA2_512} else {SignatureHash::SHA1}},})}_ => Err(Error::CouldNotReadKey.into()),}}/// Algorithm name for that key.pub fn name(&self) -> &'static str {match *self {PublicKey::Ed25519(_) => ED25519.0,PublicKey::RSA { ref hash, .. } => hash.name().0,}}/// Verify a signature.pub fn verify_detached(&self, buffer: &[u8], sig: &[u8]) -> bool {match self {&PublicKey::Ed25519(ref public) => {sodium::ed25519::verify_detached(&sig, buffer, &public)}&PublicKey::RSA { ref key, ref hash } => {use openssl::sign::*;let verify = || {let mut verifier = Verifier::new(hash.to_message_digest(), &key.0)?;verifier.update(buffer)?;verifier.verify(&sig)};verify().unwrap_or(false)}}}/// Compute the key fingerprint, hashed with sha2-256.pub fn fingerprint(&self) -> String {use super::PublicKeyBase64;let key = self.public_key_bytes();data_encoding::BASE64_NOPAD.encode(&openssl::sha::sha256(&key[..]))}}impl Verify for PublicKey {fn verify_client_auth(&self, buffer: &[u8], sig: &[u8]) -> bool {self.verify_detached(buffer, sig)}fn verify_server_auth(&self, buffer: &[u8], sig: &[u8]) -> bool {self.verify_detached(buffer, sig)}}/// Public key exchange algorithms.pub enum KeyPair {Ed25519(sodium::ed25519::SecretKey),RSA {key: openssl::rsa::Rsa<Private>,hash: SignatureHash,},}impl std::fmt::Debug for KeyPair {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {match *self {KeyPair::Ed25519(ref key) => write!(f,"Ed25519 {{ public: {:?}, secret: (hidden) }}",&key.key[32..]),KeyPair::RSA { .. } => write!(f, "RSA {{ (hidden) }}"),}}}impl<'b> crate::encoding::Bytes for &'b KeyPair {fn bytes(&self) -> &[u8] {self.name().as_bytes()}}impl KeyPair {/// Copy the public key of this algorithm.pub fn clone_public_key(&self) -> PublicKey {match self {&KeyPair::Ed25519(ref key) => {let mut public = sodium::ed25519::PublicKey { key: [0; 32] };public.key.clone_from_slice(&key.key[32..]);PublicKey::Ed25519(public)}&KeyPair::RSA { ref key, ref hash } => {use openssl::pkey::PKey;use openssl::rsa::Rsa;let key = Rsa::from_public_components(key.n().to_owned().unwrap(),key.e().to_owned().unwrap(),).unwrap();PublicKey::RSA {key: OpenSSLPKey(PKey::from_rsa(key).unwrap()),hash: hash.clone(),}}}}/// Name of this key algorithm.pub fn name(&self) -> &'static str {match *self {KeyPair::Ed25519(_) => ED25519.0,KeyPair::RSA { ref hash, .. } => hash.name().0,}}/// Generate a key pair.pub fn generate_ed25519() -> Option<Self> {let (public, secret) = sodium::ed25519::keypair();assert_eq!(&public.key, &secret.key[32..]);Some(KeyPair::Ed25519(secret))}pub fn generate_rsa(bits: usize, hash: SignatureHash) -> Option<Self> {let key = openssl::rsa::Rsa::generate(bits as u32).ok()?;Some(KeyPair::RSA { key, hash })}/// Sign a slice using this algorithm.pub fn sign_detached(&self, to_sign: &[u8]) -> Result<Signature, anyhow::Error> {match self {&KeyPair::Ed25519(ref secret) => Ok(Signature::Ed25519(SignatureBytes(sodium::ed25519::sign_detached(to_sign.as_ref(), secret).0,))),&KeyPair::RSA { ref key, ref hash } => Ok(Signature::RSA {bytes: rsa_signature(hash, key, to_sign.as_ref())?,hash: *hash,}),}}#[doc(hidden)]/// This is used by the server to sign the initial DH kex/// message. Note: we are not signing the same kind of thing as in/// the function below, `add_self_signature`.pub fn add_signature<H: AsRef<[u8]>>(&self,buffer: &mut CryptoVec,to_sign: H,) -> Result<(), anyhow::Error> {match self {&KeyPair::Ed25519(ref secret) => {let signature = sodium::ed25519::sign_detached(to_sign.as_ref(), secret);buffer.push_u32_be((ED25519.0.len() + signature.0.len() + 8) as u32);buffer.extend_ssh_string(ED25519.0.as_bytes());buffer.extend_ssh_string(&signature.0);}&KeyPair::RSA { ref key, ref hash } => {// https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2let signature = rsa_signature(hash, key, to_sign.as_ref())?;let name = hash.name();buffer.push_u32_be((name.0.len() + signature.len() + 8) as u32);buffer.extend_ssh_string(name.0.as_bytes());buffer.extend_ssh_string(&signature);}}Ok(())}#[doc(hidden)]/// This is used by the client for authentication. Note: we are/// not signing the same kind of thing as in the above function,/// `add_signature`.pub fn add_self_signature(&self, buffer: &mut CryptoVec) -> Result<(), anyhow::Error> {match self {&KeyPair::Ed25519(ref secret) => {let signature = sodium::ed25519::sign_detached(&buffer, secret);buffer.push_u32_be((ED25519.0.len() + signature.0.len() + 8) as u32);buffer.extend_ssh_string(ED25519.0.as_bytes());buffer.extend_ssh_string(&signature.0);}&KeyPair::RSA { ref key, ref hash } => {// https://tools.ietf.org/html/draft-rsa-dsa-sha2-256-02#section-2.2let signature = rsa_signature(hash, key, buffer)?;let name = hash.name();buffer.push_u32_be((name.0.len() + signature.len() + 8) as u32);buffer.extend_ssh_string(name.0.as_bytes());buffer.extend_ssh_string(&signature);}}Ok(())}}fn rsa_signature(hash: &SignatureHash,key: &openssl::rsa::Rsa<Private>,b: &[u8],) -> Result<Vec<u8>, anyhow::Error> {use openssl::pkey::*;use openssl::rsa::*;use openssl::sign::Signer;let pkey = PKey::from_rsa(Rsa::from_private_components(key.n().to_owned()?,key.e().to_owned()?,key.d().to_owned()?,key.p().unwrap().to_owned()?,key.q().unwrap().to_owned()?,key.dmp1().unwrap().to_owned()?,key.dmq1().unwrap().to_owned()?,key.iqmp().unwrap().to_owned()?,)?)?;let mut signer = Signer::new(hash.to_message_digest(), &pkey)?;signer.update(b)?;Ok(signer.sign_to_vec()?)}/// Parse a public key from a byte slice.pub fn parse_public_key(p: &[u8]) -> Result<PublicKey, anyhow::Error> {let mut pos = p.reader(0);let t = pos.read_string()?;if t == b"ssh-ed25519" {if let Ok(pubkey) = pos.read_string() {use sodium::ed25519;let mut p = ed25519::PublicKey {key: [0; ed25519::PUBLICKEY_BYTES],};p.key.clone_from_slice(pubkey);return Ok(PublicKey::Ed25519(p));}}if t == b"ssh-rsa" {let e = pos.read_string()?;let n = pos.read_string()?;use openssl::bn::*;use openssl::pkey::*;use openssl::rsa::*;return Ok(PublicKey::RSA {key: OpenSSLPKey(PKey::from_rsa(Rsa::from_public_components(BigNum::from_slice(n)?,BigNum::from_slice(e)?,)?)?),hash: SignatureHash::SHA2_256,});}Err(Error::CouldNotReadKey.into())}
use super::{pkcs_unpad, Encryption};use crate::key;use crate::key::SignatureHash;use crate::Error;use bit_vec::BitVec;use openssl::hash::MessageDigest;use openssl::pkey::Private;use openssl::rand::rand_bytes;use openssl::rsa::Rsa;use openssl::symm::{decrypt, encrypt, Cipher};use std;use std::borrow::Cow;use yasna;use yasna::BERReaderSeq;const PBES2: &'static [u64] = &[1, 2, 840, 113549, 1, 5, 13];const PBKDF2: &'static [u64] = &[1, 2, 840, 113549, 1, 5, 12];const HMAC_SHA256: &'static [u64] = &[1, 2, 840, 113549, 2, 9];const AES256CBC: &'static [u64] = &[2, 16, 840, 1, 101, 3, 4, 1, 42];const ED25519: &'static [u64] = &[1, 3, 101, 112];const RSA: &'static [u64] = &[1, 2, 840, 113549, 1, 1, 1];/// Decode a PKCS#8-encoded private key.pub fn decode_pkcs8(ciphertext: &[u8],password: Option<&[u8]>,) -> Result<key::KeyPair, anyhow::Error> {let secret = if let Some(pass) = password {// let mut sec = Vec::new();Cow::Owned(yasna::parse_der(&ciphertext, |reader| {reader.read_sequence(|reader| {// Encryption parameterslet parameters = reader.next().read_sequence(|reader| {let oid = reader.next().read_oid()?;if oid.components().as_slice() == PBES2 {asn1_read_pbes2(reader)} else {Ok(Err(Error::UnknownAlgorithm(oid)).into())}})?;// Ciphertextlet ciphertext = reader.next().read_bytes()?;Ok(parameters.map(|p| p.decrypt(pass, &ciphertext)))})})???)} else {Cow::Borrowed(ciphertext)};yasna::parse_der(&secret, |reader| {reader.read_sequence(|reader| {let version = reader.next().read_u64()?;if version == 0 {Ok(read_key_v0(reader))} else if version == 1 {Ok(read_key_v1(reader))} else {Ok(Err(Error::CouldNotReadKey.into()))}})})?}fn asn1_read_pbes2(reader: &mut yasna::BERReaderSeq,) -> Result<Result<Algorithms, Error>, yasna::ASN1Error> {reader.next().read_sequence(|reader| {// PBES2 has two components.// 1. Key generation algorithmlet keygen = reader.next().read_sequence(|reader| {let oid = reader.next().read_oid()?;if oid.components().as_slice() == PBKDF2 {asn1_read_pbkdf2(reader)} else {Ok(Err(Error::UnknownAlgorithm(oid)))}})?;// 2. Encryption algorithm.let algorithm = reader.next().read_sequence(|reader| {let oid = reader.next().read_oid()?;if oid.components().as_slice() == AES256CBC {asn1_read_aes256cbc(reader)} else {Ok(Err(Error::UnknownAlgorithm(oid)))}})?;Ok(keygen.and_then(|keygen| algorithm.map(|algo| Algorithms::Pbes2(keygen, algo))))})}fn asn1_read_pbkdf2(reader: &mut yasna::BERReaderSeq,) -> Result<Result<KeyDerivation, Error>, yasna::ASN1Error> {reader.next().read_sequence(|reader| {let salt = reader.next().read_bytes()?;let rounds = reader.next().read_u64()?;let digest = reader.next().read_sequence(|reader| {let oid = reader.next().read_oid()?;if oid.components().as_slice() == HMAC_SHA256 {reader.next().read_null()?;Ok(Ok(MessageDigest::sha256()))} else {Ok(Err(Error::UnknownAlgorithm(oid)))}})?;Ok(digest.map(|digest| KeyDerivation::Pbkdf2 {salt,rounds,digest,}))})}fn asn1_read_aes256cbc(reader: &mut yasna::BERReaderSeq,) -> Result<Result<Encryption, Error>, yasna::ASN1Error> {let iv = reader.next().read_bytes()?;let mut i = [0; 16];i.clone_from_slice(&iv);Ok(Ok(Encryption::Aes256Cbc(i)))}fn write_key_v1(writer: &mut yasna::DERWriterSeq, secret: &key::ed25519::SecretKey) {writer.next().write_u32(1);// write OIDwriter.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(ED25519));});let seed = yasna::construct_der(|writer| writer.write_bytes(&secret.key));writer.next().write_bytes(&seed);writer.next().write_tagged(yasna::Tag::context(1), |writer| {let public = &secret.key[32..];writer.write_bitvec(&BitVec::from_bytes(&public))})}fn read_key_v1(reader: &mut BERReaderSeq) -> Result<key::KeyPair, anyhow::Error> {let oid = reader.next().read_sequence(|reader| reader.next().read_oid())?;if oid.components().as_slice() == ED25519 {use key::ed25519::{PublicKey, SecretKey};let secret = {let mut seed = SecretKey::new_zeroed();let s = yasna::parse_der(&reader.next().read_bytes()?, |reader| reader.read_bytes())?;clone(&s, &mut seed.key);seed};let _public = {let public = reader.next().read_tagged(yasna::Tag::context(1), |reader| reader.read_bitvec())?.to_bytes();let mut p = PublicKey::new_zeroed();clone(&public, &mut p.key);p};Ok(key::KeyPair::Ed25519(secret))} else {Err(Error::CouldNotReadKey.into())}}fn write_key_v0(writer: &mut yasna::DERWriterSeq, key: &Rsa<Private>) {writer.next().write_u32(0);// write OIDwriter.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(RSA));writer.next().write_null()});let bytes = yasna::construct_der(|writer| {writer.write_sequence(|writer| {writer.next().write_u32(0);use num_bigint::BigUint;writer.next().write_biguint(&BigUint::from_bytes_be(&key.n().to_vec()));writer.next().write_biguint(&BigUint::from_bytes_be(&key.e().to_vec()));writer.next().write_biguint(&BigUint::from_bytes_be(&key.d().to_vec()));writer.next().write_biguint(&BigUint::from_bytes_be(&key.p().unwrap().to_vec()));writer.next().write_biguint(&BigUint::from_bytes_be(&key.q().unwrap().to_vec()));writer.next().write_biguint(&BigUint::from_bytes_be(&key.dmp1().unwrap().to_vec()));writer.next().write_biguint(&BigUint::from_bytes_be(&key.dmq1().unwrap().to_vec()));writer.next().write_biguint(&BigUint::from_bytes_be(&key.iqmp().unwrap().to_vec()));})});writer.next().write_bytes(&bytes);}fn read_key_v0(reader: &mut BERReaderSeq) -> Result<key::KeyPair, anyhow::Error> {let oid = reader.next().read_sequence(|reader| {let oid = reader.next().read_oid()?;reader.next().read_null()?;Ok(oid)})?;if oid.components().as_slice() == RSA {let seq = &reader.next().read_bytes()?;let rsa: Result<Rsa<Private>, anyhow::Error> = yasna::parse_der(seq, |reader| {reader.read_sequence(|reader| {let version = reader.next().read_u32()?;if version != 0 {return Ok(Err(Error::CouldNotReadKey.into()));}use openssl::bn::BigNum;let mut read_key = || -> Result<Rsa<Private>, anyhow::Error> {Ok(Rsa::from_private_components(BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?,BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?,BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?,BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?,BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?,BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?,BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?,BigNum::from_slice(&reader.next().read_biguint()?.to_bytes_be())?,)?)};Ok(read_key())})})?;Ok(key::KeyPair::RSA {key: rsa?,hash: SignatureHash::SHA2_256,})} else {Err(Error::CouldNotReadKey.into())}}#[test]fn test_read_write_pkcs8() {let (public, secret) = key::ed25519::keypair();assert_eq!(&public.key, &secret.key[32..]);let key = key::KeyPair::Ed25519(secret);let password = b"blabla";let ciphertext = encode_pkcs8_encrypted(password, 100, &key).unwrap();let key = decode_pkcs8(&ciphertext, Some(password)).unwrap();match key {key::KeyPair::Ed25519 { .. } => println!("Ed25519"),key::KeyPair::RSA { .. } => println!("RSA"),}}use openssl::pkcs5::pbkdf2_hmac;use yasna::models::ObjectIdentifier;/// Encode a password-protected PKCS#8-encoded private key.pub fn encode_pkcs8_encrypted(pass: &[u8],rounds: u32,key: &key::KeyPair,) -> Result<Vec<u8>, anyhow::Error> {let mut salt = [0; 64];rand_bytes(&mut salt)?;let mut iv = [0; 16];rand_bytes(&mut iv)?;let mut dkey = [0; 32]; // AES256-CBCpbkdf2_hmac(pass,&salt,rounds as usize,MessageDigest::sha256(),&mut dkey,)?;let mut plaintext = encode_pkcs8(key);let padding_len = 32 - (plaintext.len() % 32);plaintext.extend(std::iter::repeat(padding_len as u8).take(padding_len));let ciphertext = encrypt(Cipher::aes_256_cbc(), &dkey, Some(&iv), &plaintext)?;Ok(yasna::construct_der(|writer| {writer.write_sequence(|writer| {// Encryption parameterswriter.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(PBES2));asn1_write_pbes2(writer.next(), rounds as u64, &salt, &iv)});// Ciphertextwriter.next().write_bytes(&ciphertext[..])})}))}/// Encode a Decode a PKCS#8-encoded private key.pub fn encode_pkcs8(key: &key::KeyPair) -> Vec<u8> {yasna::construct_der(|writer| {writer.write_sequence(|writer| match *key {key::KeyPair::Ed25519(ref secret) => write_key_v1(writer, secret),key::KeyPair::RSA { ref key, .. } => write_key_v0(writer, key),})})}fn clone(src: &[u8], dest: &mut [u8]) {let i = src.iter().take_while(|b| **b == 0).count();let src = &src[i..];let l = dest.len();(&mut dest[l - src.len()..]).clone_from_slice(src)}fn asn1_write_pbes2(writer: yasna::DERWriter, rounds: u64, salt: &[u8], iv: &[u8]) {writer.write_sequence(|writer| {// 1. Key generation algorithmwriter.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(PBKDF2));asn1_write_pbkdf2(writer.next(), rounds, salt)});// 2. Encryption algorithm.writer.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(AES256CBC));writer.next().write_bytes(iv)});})}fn asn1_write_pbkdf2(writer: yasna::DERWriter, rounds: u64, salt: &[u8]) {writer.write_sequence(|writer| {writer.next().write_bytes(salt);writer.next().write_u64(rounds);writer.next().write_sequence(|writer| {writer.next().write_oid(&ObjectIdentifier::from_slice(HMAC_SHA256));writer.next().write_null()})})}enum Algorithms {Pbes2(KeyDerivation, Encryption),}impl Algorithms {fn decrypt(&self, password: &[u8], cipher: &[u8]) -> Result<Vec<u8>, anyhow::Error> {match *self {Algorithms::Pbes2(ref der, ref enc) => {let mut key = enc.key();der.derive(password, &mut key)?;let out = enc.decrypt(&key, cipher)?;Ok(out)}}}}impl KeyDerivation {fn derive(&self, password: &[u8], key: &mut [u8]) -> Result<(), anyhow::Error> {match *self {KeyDerivation::Pbkdf2 {ref salt,rounds,digest,} => pbkdf2_hmac(password, salt, rounds as usize, digest, key)?,}Ok(())}}enum Key {K128([u8; 16]),K256([u8; 32]),}impl std::ops::Deref for Key {type Target = [u8];fn deref(&self) -> &[u8] {match *self {Key::K128(ref k) => k,Key::K256(ref k) => k,}}}impl std::ops::DerefMut for Key {fn deref_mut(&mut self) -> &mut [u8] {match *self {Key::K128(ref mut k) => k,Key::K256(ref mut k) => k,}}}impl Encryption {fn key(&self) -> Key {match *self {Encryption::Aes128Cbc(_) => Key::K128([0; 16]),Encryption::Aes256Cbc(_) => Key::K256([0; 32]),}}fn decrypt(&self, key: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, anyhow::Error> {let (cipher, iv) = match *self {Encryption::Aes128Cbc(ref iv) => (Cipher::aes_128_cbc(), iv),Encryption::Aes256Cbc(ref iv) => (Cipher::aes_256_cbc(), iv),};let mut dec = decrypt(cipher, &key, Some(&iv[..]), ciphertext)?;pkcs_unpad(&mut dec);Ok(dec)}}enum KeyDerivation {Pbkdf2 {salt: Vec<u8>,rounds: u64,digest: MessageDigest,},}
use super::{decode_rsa, pkcs_unpad, Encryption};use crate::key;use crate::Error;use openssl::hash::{Hasher, MessageDigest};use openssl::symm::{decrypt, Cipher};/// Decode a secret key in the PKCS#5 format, possible deciphering it/// using the supplied password.pub fn decode_pkcs5(secret: &[u8],password: Option<&[u8]>,enc: Encryption,) -> Result<key::KeyPair, anyhow::Error> {if let Some(pass) = password {let sec = match enc {Encryption::Aes128Cbc(ref iv) => {let mut h = Hasher::new(MessageDigest::md5()).unwrap();h.update(pass).unwrap();h.update(&iv[..8]).unwrap();let md5 = h.finish().unwrap();let mut dec = decrypt(Cipher::aes_128_cbc(), &md5, Some(&iv[..]), secret)?;pkcs_unpad(&mut dec);dec}Encryption::Aes256Cbc(_) => unimplemented!(),};decode_rsa(&sec)} else {Err(Error::KeyIsEncrypted.into())}}
use crate::bcrypt_pbkdf;use crate::encoding::Reader;use crate::key;use crate::{Error, KEYTYPE_ED25519, KEYTYPE_RSA};use cryptovec::CryptoVec;use openssl::bn::BigNum;use openssl::symm::{Cipher, Crypter, Mode};/// Decode a secret key given in the OpenSSH format, deciphering it if/// needed using the supplied password.pub fn decode_openssh(secret: &[u8],password: Option<&[u8]>,) -> Result<key::KeyPair, anyhow::Error> {if &secret[0..15] == b"openssh-key-v1\0" {let mut position = secret.reader(15);let ciphername = position.read_string()?;let kdfname = position.read_string()?;let kdfoptions = position.read_string()?;let nkeys = position.read_u32()?;// Read all public keysfor _ in 0..nkeys {position.read_string()?;}// Read all secret keyslet secret_ = position.read_string()?;let secret = decrypt_secret_key(ciphername, kdfname, kdfoptions, password, secret_)?;let mut position = secret.reader(0);let _check0 = position.read_u32()?;let _check1 = position.read_u32()?;for _ in 0..nkeys {let key_type = position.read_string()?;if key_type == KEYTYPE_ED25519 {let pubkey = position.read_string()?;let seckey = position.read_string()?;let _comment = position.read_string()?;assert_eq!(pubkey, &seckey[32..]);use key::ed25519::*;let mut secret = SecretKey::new_zeroed();secret.key.clone_from_slice(seckey);return Ok(key::KeyPair::Ed25519(secret));} else if key_type == KEYTYPE_RSA {let n = BigNum::from_slice(position.read_string()?)?;let e = BigNum::from_slice(position.read_string()?)?;let d = BigNum::from_slice(position.read_string()?)?;let iqmp = BigNum::from_slice(position.read_string()?)?;let p = BigNum::from_slice(position.read_string()?)?;let q = BigNum::from_slice(position.read_string()?)?;let mut ctx = openssl::bn::BigNumContext::new()?;let un = openssl::bn::BigNum::from_u32(1)?;let mut p1 = openssl::bn::BigNum::new()?;let mut q1 = openssl::bn::BigNum::new()?;p1.checked_sub(&p, &un)?;q1.checked_sub(&q, &un)?;let mut dmp1 = openssl::bn::BigNum::new()?; // d mod p-1dmp1.checked_rem(&d, &p1, &mut ctx)?;let mut dmq1 = openssl::bn::BigNum::new()?; // d mod q-1dmq1.checked_rem(&d, &q1, &mut ctx)?;let key = openssl::rsa::RsaPrivateKeyBuilder::new(n, e, d)?.set_factors(p, q)?.set_crt_params(dmp1, dmq1, iqmp)?.build();key.check_key().unwrap();return Ok(key::KeyPair::RSA {key,hash: key::SignatureHash::SHA2_512,});} else {return Err(Error::UnsupportedKeyType(key_type.to_vec()).into());}}Err(Error::CouldNotReadKey.into())} else {Err(Error::CouldNotReadKey.into())}}fn decrypt_secret_key(ciphername: &[u8],kdfname: &[u8],kdfoptions: &[u8],password: Option<&[u8]>,secret_key: &[u8],) -> Result<Vec<u8>, anyhow::Error> {if kdfname == b"none" {if password.is_none() {Ok(secret_key.to_vec())} else {Err(Error::CouldNotReadKey.into())}} else if let Some(password) = password {let mut key = CryptoVec::new();let cipher = match ciphername {b"aes128-cbc" => {key.resize(16 + 16);Cipher::aes_128_cbc()}b"aes128-ctr" => {key.resize(16 + 16);Cipher::aes_128_ctr()}b"aes256-cbc" => {key.resize(16 + 32);Cipher::aes_256_cbc()}b"aes256-ctr" => {key.resize(16 + 32);Cipher::aes_256_ctr()}_ => return Err(Error::CouldNotReadKey.into()),};match kdfname {b"bcrypt" => {let mut kdfopts = kdfoptions.reader(0);let salt = kdfopts.read_string()?;let rounds = kdfopts.read_u32()?;bcrypt_pbkdf::bcrypt_pbkdf(password, salt, rounds, &mut key);}_kdfname => {return Err(Error::CouldNotReadKey.into());}};let iv = &key[32..];let key = &key[..32];let mut c = Crypter::new(cipher, Mode::Decrypt, &key, Some(&iv))?;c.pad(false);let mut dec = vec![0; secret_key.len() + 32];let n = c.update(&secret_key, &mut dec)?;let n = n + c.finalize(&mut dec[n..])?;dec.truncate(n);Ok(dec)} else {Err(Error::KeyIsEncrypted.into())}}
use super::is_base64_char;use crate::key;use crate::Error;use data_encoding::{BASE64_MIME, HEXLOWER_PERMISSIVE};use openssl::rsa::Rsa;use std::io::Write;pub mod openssh;pub use self::openssh::*;pub mod pkcs5;pub use self::pkcs5::*;pub mod pkcs8;const AES_128_CBC: &'static str = "DEK-Info: AES-128-CBC,";#[derive(Clone, Copy, Debug)]/// AES encryption key.pub enum Encryption {/// Key for AES128Aes128Cbc([u8; 16]),/// Key for AES256Aes256Cbc([u8; 16]),}#[derive(Clone, Debug)]enum Format {Rsa,Openssh,Pkcs5Encrypted(Encryption),Pkcs8Encrypted,Pkcs8,}/// Decode a secret key, possibly deciphering it with the supplied/// password.pub fn decode_secret_key(secret: &str,password: Option<&[u8]>,) -> Result<key::KeyPair, anyhow::Error> {let mut format = None;let secret = {let mut started = false;let mut sec = String::new();for l in secret.lines() {if started == true {if l.starts_with("-----END ") {break;}if l.chars().all(is_base64_char) {sec.push_str(l)} else if l.starts_with(AES_128_CBC) {let iv_: Vec<u8> =HEXLOWER_PERMISSIVE.decode(l.split_at(AES_128_CBC.len()).1.as_bytes())?;if iv_.len() != 16 {return Err(Error::CouldNotReadKey.into());}let mut iv = [0; 16];iv.clone_from_slice(&iv_);format = Some(Format::Pkcs5Encrypted(Encryption::Aes128Cbc(iv)))}}if l == "-----BEGIN OPENSSH PRIVATE KEY-----" {started = true;format = Some(Format::Openssh);} else if l == "-----BEGIN RSA PRIVATE KEY-----" {started = true;format = Some(Format::Rsa);} else if l == "-----BEGIN ENCRYPTED PRIVATE KEY-----" {started = true;format = Some(Format::Pkcs8Encrypted);} else if l == "-----BEGIN PRIVATE KEY-----" {started = true;format = Some(Format::Pkcs8);}}sec};let secret = BASE64_MIME.decode(secret.as_bytes())?;match format {Some(Format::Openssh) => decode_openssh(&secret, password),Some(Format::Rsa) => decode_rsa(&secret),Some(Format::Pkcs5Encrypted(enc)) => decode_pkcs5(&secret, password, enc),Some(Format::Pkcs8Encrypted) | Some(Format::Pkcs8) => {self::pkcs8::decode_pkcs8(&secret, password)}None => Err(Error::CouldNotReadKey.into()),}}pub fn encode_pkcs8_pem<W: Write>(key: &key::KeyPair, mut w: W) -> Result<(), anyhow::Error> {let x = self::pkcs8::encode_pkcs8(key);w.write_all(b"-----BEGIN PRIVATE KEY-----\n")?;w.write_all(BASE64_MIME.encode(&x).as_bytes())?;w.write_all(b"\n-----END PRIVATE KEY-----\n")?;Ok(())}pub fn encode_pkcs8_pem_encrypted<W: Write>(key: &key::KeyPair,pass: &[u8],rounds: u32,mut w: W,) -> Result<(), anyhow::Error> {let x = self::pkcs8::encode_pkcs8_encrypted(pass, rounds, key)?;w.write_all(b"-----BEGIN ENCRYPTED PRIVATE KEY-----\n")?;w.write_all(BASE64_MIME.encode(&x).as_bytes())?;w.write_all(b"\n-----END ENCRYPTED PRIVATE KEY-----\n")?;Ok(())}fn decode_rsa(secret: &[u8]) -> Result<key::KeyPair, anyhow::Error> {Ok(key::KeyPair::RSA {key: Rsa::private_key_from_der(secret)?,hash: key::SignatureHash::SHA2_256,})}fn pkcs_unpad(dec: &mut Vec<u8>) {let len = dec.len();if len > 0 {let padding_len = dec[len - 1];if dec[(len - padding_len as usize)..].iter().all(|&x| x == padding_len){dec.truncate(len - padding_len as usize)}}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use crate::Error;use byteorder::{BigEndian, ByteOrder, WriteBytesExt};use cryptovec::CryptoVec;#[doc(hidden)]pub trait Bytes {fn bytes(&self) -> &[u8];}impl<A: AsRef<str>> Bytes for A {fn bytes(&self) -> &[u8] {self.as_ref().as_bytes()}}/// Encode in the SSH format.pub trait Encoding {/// Push an SSH-encoded string to `self`.fn extend_ssh_string(&mut self, s: &[u8]);/// Push an SSH-encoded blank string of length `s` to `self`.fn extend_ssh_string_blank(&mut self, s: usize) -> &mut [u8];/// Push an SSH-encoded multiple-precision integer.fn extend_ssh_mpint(&mut self, s: &[u8]);/// Push an SSH-encoded list.fn extend_list<A: Bytes, I: Iterator<Item = A>>(&mut self, list: I);/// Push an SSH-encoded empty list.fn write_empty_list(&mut self);}/// Encoding length of the given mpint.pub fn mpint_len(s: &[u8]) -> usize {let mut i = 0;while i < s.len() && s[i] == 0 {i += 1}(if s[i] & 0x80 != 0 { 5 } else { 4 }) + s.len() - i}impl Encoding for Vec<u8> {fn extend_ssh_string(&mut self, s: &[u8]) {self.write_u32::<BigEndian>(s.len() as u32).unwrap();self.extend(s);}fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] {self.write_u32::<BigEndian>(len as u32).unwrap();let current = self.len();self.resize(current + len, 0u8);&mut self[current..]}fn extend_ssh_mpint(&mut self, s: &[u8]) {// Skip initial 0s.let mut i = 0;while i < s.len() && s[i] == 0 {i += 1}// If the first non-zero is >= 128, write its length (u32, BE), followed by 0.if s[i] & 0x80 != 0 {self.write_u32::<BigEndian>((s.len() - i + 1) as u32).unwrap();self.push(0)} else {self.write_u32::<BigEndian>((s.len() - i) as u32).unwrap();}self.extend(&s[i..]);}fn extend_list<A: Bytes, I: Iterator<Item = A>>(&mut self, list: I) {let len0 = self.len();self.extend(&[0, 0, 0, 0]);let mut first = true;for i in list {if !first {self.push(b',')} else {first = false;}self.extend(i.bytes())}let len = (self.len() - len0 - 4) as u32;BigEndian::write_u32(&mut self[len0..], len);}fn write_empty_list(&mut self) {self.extend(&[0, 0, 0, 0]);}}impl Encoding for CryptoVec {fn extend_ssh_string(&mut self, s: &[u8]) {self.push_u32_be(s.len() as u32);self.extend(s);}fn extend_ssh_string_blank(&mut self, len: usize) -> &mut [u8] {self.push_u32_be(len as u32);let current = self.len();self.resize(current + len);&mut self[current..]}fn extend_ssh_mpint(&mut self, s: &[u8]) {// Skip initial 0s.let mut i = 0;while i < s.len() && s[i] == 0 {i += 1}// If the first non-zero is >= 128, write its length (u32, BE), followed by 0.if s[i] & 0x80 != 0 {self.push_u32_be((s.len() - i + 1) as u32);self.push(0)} else {self.push_u32_be((s.len() - i) as u32);}self.extend(&s[i..]);}fn extend_list<A: Bytes, I: Iterator<Item = A>>(&mut self, list: I) {let len0 = self.len();self.extend(&[0, 0, 0, 0]);let mut first = true;for i in list {if !first {self.push(b',')} else {first = false;}self.extend(i.bytes())}let len = (self.len() - len0 - 4) as u32;BigEndian::write_u32(&mut self[len0..], len);}fn write_empty_list(&mut self) {self.extend(&[0, 0, 0, 0]);}}/// A cursor-like trait to read SSH-encoded things.pub trait Reader {/// Create an SSH reader for `self`.fn reader<'a>(&'a self, starting_at: usize) -> Position<'a>;}impl Reader for CryptoVec {fn reader<'a>(&'a self, starting_at: usize) -> Position<'a> {Position {s: &self,position: starting_at,}}}impl Reader for [u8] {fn reader<'a>(&'a self, starting_at: usize) -> Position<'a> {Position {s: self,position: starting_at,}}}/// A cursor-like type to read SSH-encoded values.#[derive(Debug)]pub struct Position<'a> {s: &'a [u8],#[doc(hidden)]pub position: usize,}impl<'a> Position<'a> {/// Read one string from this reader.pub fn read_string(&mut self) -> Result<&'a [u8], Error> {let len = self.read_u32()? as usize;if self.position + len <= self.s.len() {let result = &self.s[self.position..(self.position + len)];self.position += len;Ok(result)} else {Err(Error::IndexOutOfBounds)}}/// Read a `u32` from this reader.pub fn read_u32(&mut self) -> Result<u32, Error> {if self.position + 4 <= self.s.len() {let u = BigEndian::read_u32(&self.s[self.position..]);self.position += 4;Ok(u)} else {Err(Error::IndexOutOfBounds)}}/// Read one byte from this reader.pub fn read_byte(&mut self) -> Result<u8, Error> {if self.position + 1 <= self.s.len() {let u = self.s[self.position];self.position += 1;Ok(u)} else {Err(Error::IndexOutOfBounds)}}/// Read one byte from this reader.pub fn read_mpint(&mut self) -> Result<&'a [u8], Error> {let len = self.read_u32()? as usize;if self.position + len <= self.s.len() {let result = &self.s[self.position..(self.position + len)];self.position += len;Ok(result)} else {Err(Error::IndexOutOfBounds)}}}
// From the rust-crypto project.// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your// option. This file may not be copied, modified, or distributed// except according to those terms.#[derive(Clone, Copy)]pub struct Blowfish {s: [[u32; 256]; 4],p: [u32; 18],}fn next_u32_wrap(buf: &[u8], offset: &mut usize) -> u32 {let mut v = 0;for _ in 0..4 {if *offset >= buf.len() {*offset = 0;}v = (v << 8) | buf[*offset] as u32;*offset += 1;}v}impl Blowfish {// For bcrypt. Use Blowfish::new instead.pub fn init_state() -> Blowfish {Blowfish {p: [0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98,0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd,0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b,],s: [[0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96,0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658,0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e,0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6,0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c,0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1,0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a,0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176,0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706,0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b,0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c,0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a,0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760,0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8,0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33,0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0,0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777,0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705,0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e,0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9,0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f,0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a,],[0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d,0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65,0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9,0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d,0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc,0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908,0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124,0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908,0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b,0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa,0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d,0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5,0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96,0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca,0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77,0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054,0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea,0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646,0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea,0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e,0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd,0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7,],[0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7,0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af,0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4,0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec,0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332,0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58,0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22,0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60,0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99,0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74,0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3,0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979,0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa,0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086,0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24,0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84,0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09,0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe,0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0,0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188,0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8,0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0,],[0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742,0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79,0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a,0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1,0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797,0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6,0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba,0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5,0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce,0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd,0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb,0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc,0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc,0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a,0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a,0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b,0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e,0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623,0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a,0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3,0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c,0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,],],}}// For bcrypt. Use Blowfish::new instead.pub fn expand_key(&mut self, key: &[u8]) {let mut key_pos = 0;for i in 0..18 {self.p[i] ^= next_u32_wrap(key, &mut key_pos);}let mut l = 0u32;let mut r = 0u32;let mut i = 0;while i < 18 {let (new_l, new_r) = self.encrypt(l, r);l = new_l;r = new_r;self.p[i] = l;self.p[i + 1] = r;i += 2}for i in 0..4 {let mut j = 0;while j < 256 {let (new_l, new_r) = self.encrypt(l, r);l = new_l;r = new_r;self.s[i][j] = l;self.s[i][j + 1] = r;j += 2}}}// Bcrypt key schedule.pub fn salted_expand_key(&mut self, salt: &[u8], key: &[u8]) {let mut key_pos = 0;for i in 0..18 {self.p[i] ^= next_u32_wrap(key, &mut key_pos);}let mut l = 0u32;let mut r = 0u32;let mut salt_pos = 0;let mut i = 0;while i < 18 {let (new_l, new_r) = self.encrypt(l ^ next_u32_wrap(salt, &mut salt_pos),r ^ next_u32_wrap(salt, &mut salt_pos),);l = new_l;r = new_r;self.p[i] = l;self.p[i + 1] = r;i += 2}for i in 0..4 {let mut j = 0;while j < 256 {let (new_l, new_r) = self.encrypt(l ^ next_u32_wrap(salt, &mut salt_pos),r ^ next_u32_wrap(salt, &mut salt_pos),);l = new_l;r = new_r;self.s[i][j] = l;self.s[i][j + 1] = r;let (new_l, new_r) = self.encrypt(l ^ next_u32_wrap(salt, &mut salt_pos),r ^ next_u32_wrap(salt, &mut salt_pos),);l = new_l;r = new_r;self.s[i][j + 2] = l;self.s[i][j + 3] = r;j += 4}}}fn round_function(&self, x: u32) -> u32 {((self.s[0][(x >> 24) as usize].wrapping_add(self.s[1][((x >> 16) & 0xff) as usize]))^ self.s[2][((x >> 8) & 0xff) as usize]).wrapping_add(self.s[3][(x & 0xff) as usize])}// Public for bcrypt.pub fn encrypt(&self, mut l: u32, mut r: u32) -> (u32, u32) {let mut i = 0;while i < 16 {l ^= self.p[i];r ^= self.round_function(l);r ^= self.p[i + 1];l ^= self.round_function(r);i += 2}l ^= self.p[16];r ^= self.p[17];(r, l)}}
// From the rust-crypto project.// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your// option. This file may not be copied, modified, or distributed// except according to those terms.use crate::blowfish::*;use byteorder::{BigEndian, ByteOrder, LittleEndian};use openssl::sha::Sha512;fn bcrypt_hash(hpass: &[u8], hsalt: &[u8], output: &mut [u8; 32]) {let mut bf = Blowfish::init_state();bf.salted_expand_key(hsalt, hpass);for _ in 0..64 {bf.expand_key(hsalt);bf.expand_key(hpass);}// b"OxychromaticBlowfishSwatDynamite"let mut buf: [u32; 8] = [1333295459, 1752330093, 1635019107, 1114402679, 1718186856, 1400332660, 1148808801,1835627621,];let mut i = 0;while i < 8 {for _ in 0..64 {let (l, r) = bf.encrypt(buf[i], buf[i + 1]);buf[i] = l;buf[i + 1] = r;}i += 2}for i in 0..8 {LittleEndian::write_u32(&mut output[i * 4..(i + 1) * 4], buf[i]);}}pub fn bcrypt_pbkdf(password: &[u8], salt: &[u8], rounds: u32, output: &mut [u8]) {assert!(password.len() > 0);assert!(salt.len() > 0);assert!(rounds > 0);assert!(output.len() > 0);assert!(output.len() <= 1024);let nblocks = (output.len() + 31) / 32;let hpass = {let mut hasher = Sha512::new();hasher.update(password);hasher.finish()};for block in 1..(nblocks + 1) {let mut count = [0u8; 4];let mut out = [0u8; 32];BigEndian::write_u32(&mut count, block as u32);let mut hasher = Sha512::new();hasher.update(salt);hasher.update(&count);let hsalt = hasher.finish();bcrypt_hash(hpass.as_ref(), hsalt.as_ref(), &mut out);let mut tmp = out;for _ in 1..rounds {let mut hasher = Sha512::new();hasher.update(&tmp);let hsalt = hasher.finish();bcrypt_hash(hpass.as_ref(), hsalt.as_ref(), &mut tmp);for i in 0..out.len() {out[i] ^= tmp[i];}for i in 0..out.len() {let idx = i * nblocks + (block - 1);if idx < output.len() {output[idx] = out[i];}}}}}
use crate::encoding::{Encoding, Position, Reader};use crate::key;use crate::key::SignatureHash;use byteorder::{BigEndian, ByteOrder};use cryptovec::CryptoVec;use futures::future::Future;use std;use std::collections::HashMap;use std::sync::{Arc, RwLock};use std::time::Duration;use std::time::SystemTime;use tokio;use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};use tokio::stream::{Stream, StreamExt};use tokio::time::delay_for;use super::msg;use super::Constraint;use crate::Error;#[derive(Clone)]struct KeyStore(Arc<RwLock<HashMap<Vec<u8>, (Arc<key::KeyPair>, SystemTime, Vec<Constraint>)>>>);#[derive(Clone)]struct Lock(Arc<RwLock<CryptoVec>>);#[allow(missing_docs)]#[derive(Debug)]pub enum ServerError<E> {E(E),Error(Error),}pub trait Agent: Clone + Send + 'static {fn confirm(self,_pk: Arc<key::KeyPair>,) -> Box<dyn Future<Output = (Self, bool)> + Unpin + Send> {Box::new(futures::future::ready((self, true)))}}pub async fn serve<S, L, A>(mut listener: L, agent: A) -> Result<(), Error>whereS: AsyncRead + AsyncWrite + Send + Sync + Unpin + 'static,L: Stream<Item = tokio::io::Result<S>> + Unpin,A: Agent + Send + Sync + 'static,{let keys = KeyStore(Arc::new(RwLock::new(HashMap::new())));let lock = Lock(Arc::new(RwLock::new(CryptoVec::new())));while let Some(Ok(stream)) = listener.next().await {let mut buf = CryptoVec::new();buf.resize(4);tokio::spawn((Connection {lock: lock.clone(),keys: keys.clone(),agent: Some(agent.clone()),s: stream,buf: CryptoVec::new(),}).run(),);}Ok(())}impl Agent for () {fn confirm(self,_: Arc<key::KeyPair>,) -> Box<dyn Future<Output = (Self, bool)> + Unpin + Send> {Box::new(futures::future::ready((self, true)))}}struct Connection<S: AsyncRead + AsyncWrite + Send + 'static, A: Agent> {lock: Lock,keys: KeyStore,agent: Option<A>,s: S,buf: CryptoVec,}impl<S: AsyncRead + AsyncWrite + Send + Unpin + 'static, A: Agent + Send + 'static>Connection<S, A>{async fn run(mut self) -> Result<(), anyhow::Error> {let mut writebuf = CryptoVec::new();loop {// Reading the lengthself.buf.clear();self.buf.resize(4);self.s.read_exact(&mut self.buf).await?;// Reading the rest of the bufferlet len = BigEndian::read_u32(&self.buf) as usize;self.buf.clear();self.buf.resize(len);self.s.read_exact(&mut self.buf).await?;// respondwritebuf.clear();self.respond(&mut writebuf).await?;self.s.write_all(&writebuf).await?;self.s.flush().await?}}async fn respond(&mut self, writebuf: &mut CryptoVec) -> Result<(), anyhow::Error> {let is_locked = {if let Ok(password) = self.lock.0.read() {!password.is_empty()} else {true}};writebuf.extend(&[0, 0, 0, 0]);let mut r = self.buf.reader(0);match r.read_byte() {Ok(11) if !is_locked => {// request identitiesif let Ok(keys) = self.keys.0.read() {writebuf.push(msg::IDENTITIES_ANSWER);writebuf.push_u32_be(keys.len() as u32);for (k, _) in keys.iter() {writebuf.extend_ssh_string(k);writebuf.extend_ssh_string(b"");}} else {writebuf.push(msg::FAILURE)}}Ok(13) if !is_locked => {// sign requestlet agent = self.agent.take().unwrap();let (agent, signed) = self.try_sign(agent, r, writebuf).await?;self.agent = Some(agent);if signed {return Ok(());} else {writebuf.resize(4);writebuf.push(msg::FAILURE)}}Ok(17) if !is_locked => {// add identityif let Ok(true) = self.add_key(r, false, writebuf).await {} else {writebuf.push(msg::FAILURE)}}Ok(18) if !is_locked => {// remove identityif let Ok(true) = self.remove_identity(r) {writebuf.push(msg::SUCCESS)} else {writebuf.push(msg::FAILURE)}}Ok(19) if !is_locked => {// remove all identitiesif let Ok(mut keys) = self.keys.0.write() {keys.clear();writebuf.push(msg::SUCCESS)} else {writebuf.push(msg::FAILURE)}}Ok(22) if !is_locked => {// lockif let Ok(()) = self.lock(r) {writebuf.push(msg::SUCCESS)} else {writebuf.push(msg::FAILURE)}}Ok(23) if is_locked => {// unlockif let Ok(true) = self.unlock(r) {writebuf.push(msg::SUCCESS)} else {writebuf.push(msg::FAILURE)}}Ok(25) if !is_locked => {// add identity constrainedif let Ok(true) = self.add_key(r, true, writebuf).await {} else {writebuf.push(msg::FAILURE)}}_ => {// Message not understoodwritebuf.push(msg::FAILURE)}}let len = writebuf.len() - 4;BigEndian::write_u32(&mut writebuf[0..], len as u32);Ok(())}fn lock(&self, mut r: Position) -> Result<(), anyhow::Error> {let password = r.read_string()?;let mut lock = self.lock.0.write().unwrap();lock.extend(password);Ok(())}fn unlock(&self, mut r: Position) -> Result<bool, anyhow::Error> {let password = r.read_string()?;let mut lock = self.lock.0.write().unwrap();if &lock[0..] == password {lock.clear();Ok(true)} else {Ok(false)}}fn remove_identity(&self, mut r: Position) -> Result<bool, anyhow::Error> {if let Ok(mut keys) = self.keys.0.write() {if keys.remove(r.read_string()?).is_some() {Ok(true)} else {Ok(false)}} else {Ok(false)}}async fn add_key<'a>(&self,mut r: Position<'a>,constrained: bool,writebuf: &mut CryptoVec,) -> Result<bool, anyhow::Error> {let pos0 = r.position;let t = r.read_string()?;let (blob, key) = match t {b"ssh-ed25519" => {let public_ = r.read_string()?;let pos1 = r.position;let concat = r.read_string()?;let _comment = r.read_string()?;if &concat[32..64] != public_ {return Ok(false);}use key::ed25519::*;let mut public = PublicKey::new_zeroed();let mut secret = SecretKey::new_zeroed();public.key.clone_from_slice(&public_[..32]);secret.key.clone_from_slice(&concat[..]);writebuf.push(msg::SUCCESS);(self.buf[pos0..pos1].to_vec(), key::KeyPair::Ed25519(secret))}b"ssh-rsa" => {use openssl::bn::{BigNum, BigNumContext};use openssl::rsa::Rsa;let n = r.read_mpint()?;let e = r.read_mpint()?;let d = BigNum::from_slice(r.read_mpint()?)?;let q_inv = r.read_mpint()?;let p = BigNum::from_slice(r.read_mpint()?)?;let q = BigNum::from_slice(r.read_mpint()?)?;let (dp, dq) = {let one = BigNum::from_u32(1)?;let p1 = p.as_ref() - one.as_ref();let q1 = q.as_ref() - one.as_ref();let mut context = BigNumContext::new()?;let mut dp = BigNum::new()?;let mut dq = BigNum::new()?;dp.checked_rem(&d, &p1, &mut context)?;dq.checked_rem(&d, &q1, &mut context)?;(dp, dq)};let _comment = r.read_string()?;let key = Rsa::from_private_components(BigNum::from_slice(n)?,BigNum::from_slice(e)?,d,p,q,dp,dq,BigNum::from_slice(&q_inv)?,)?;let len0 = writebuf.len();writebuf.extend_ssh_string(b"ssh-rsa");writebuf.extend_ssh_mpint(&e);writebuf.extend_ssh_mpint(&n);let blob = writebuf[len0..].to_vec();writebuf.resize(len0);writebuf.push(msg::SUCCESS);(blob,key::KeyPair::RSA {key,hash: SignatureHash::SHA2_256,},)}_ => return Ok(false),};let mut w = self.keys.0.write().unwrap();let now = SystemTime::now();if constrained {let n = r.read_u32()?;let mut c = Vec::new();for _ in 0..n {let t = r.read_byte()?;if t == msg::CONSTRAIN_LIFETIME {let seconds = r.read_u32()?;c.push(Constraint::KeyLifetime { seconds });let blob = blob.clone();let keys = self.keys.clone();tokio::spawn(async move {delay_for(Duration::from_secs(seconds as u64)).await;let mut keys = keys.0.write().unwrap();let delete = if let Some(&(_, time, _)) = keys.get(&blob) {time == now} else {false};if delete {keys.remove(&blob);}});} else if t == msg::CONSTRAIN_CONFIRM {c.push(Constraint::Confirm)} else {return Ok(false);}}w.insert(blob, (Arc::new(key), now, Vec::new()));} else {w.insert(blob, (Arc::new(key), now, Vec::new()));}Ok(true)}async fn try_sign<'a>(&self,agent: A,mut r: Position<'a>,writebuf: &mut CryptoVec,) -> Result<(A, bool), anyhow::Error> {let mut needs_confirm = false;let key = {let blob = r.read_string()?;let k = self.keys.0.read().unwrap();if let Some(&(ref key, _, ref constraints)) = k.get(blob) {if constraints.iter().any(|c| *c == Constraint::Confirm) {needs_confirm = true;}key.clone()} else {return Ok((agent, false));}};let agent = if needs_confirm {let (agent, ok) = agent.confirm(key.clone()).await;if !ok {return Ok((agent, false));}agent} else {agent};writebuf.push(msg::SIGN_RESPONSE);let data = r.read_string()?;key.add_signature(writebuf, data)?;let len = writebuf.len();BigEndian::write_u32(writebuf, (len - 4) as u32);Ok((agent, true))}}
pub const FAILURE: u8 = 5;pub const SUCCESS: u8 = 6;pub const IDENTITIES_ANSWER: u8 = 12;pub const SIGN_RESPONSE: u8 = 14;// pub const EXTENSION_FAILURE: u8 = 28;pub const REQUEST_IDENTITIES: u8 = 11;pub const SIGN_REQUEST: u8 = 13;pub const ADD_IDENTITY: u8 = 17;pub const REMOVE_IDENTITY: u8 = 18;pub const REMOVE_ALL_IDENTITIES: u8 = 19;pub const ADD_ID_CONSTRAINED: u8 = 25;pub const ADD_SMARTCARD_KEY: u8 = 20;pub const REMOVE_SMARTCARD_KEY: u8 = 21;pub const LOCK: u8 = 22;pub const UNLOCK: u8 = 23;pub const ADD_SMARTCARD_KEY_CONSTRAINED: u8 = 26;pub const EXTENSION: u8 = 27;pub const CONSTRAIN_LIFETIME: u8 = 1;pub const CONSTRAIN_CONFIRM: u8 = 2;pub const CONSTRAIN_EXTENSION: u8 = 3;
/// Write clients for SSH agents.pub mod client;mod msg;/// Write servers for SSH agents.pub mod server;/// Constraints on how keys can be used#[derive(Debug, PartialEq, Eq)]pub enum Constraint {/// The key shall disappear from the agent's memory after that many seconds.KeyLifetime { seconds: u32 },/// Signatures need to be confirmed by the agent (for instance using a dialog).Confirm,/// Custom constraintsExtensions { name: Vec<u8>, details: Vec<u8> },}
use super::msg;use super::Constraint;use crate::encoding::{Encoding, Reader};use crate::key;use crate::key::{PublicKey, SignatureHash};use crate::Error;use byteorder::{BigEndian, ByteOrder};use cryptovec::CryptoVec;use tokio;use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};/// SSH agent client.pub struct AgentClient<S: AsyncRead + AsyncWrite> {stream: S,buf: CryptoVec,}// https://tools.ietf.org/html/draft-miller-ssh-agent-00#section-4.1impl<S: AsyncRead + AsyncWrite + Unpin> AgentClient<S> {/// Build a future that connects to an SSH agent via the provided/// stream (on Unix, usually a Unix-domain socket).pub fn connect(stream: S) -> Self {AgentClient {stream,buf: CryptoVec::new(),}}}impl AgentClient<tokio::net::UnixStream> {/// Build a future that connects to an SSH agent via the provided/// stream (on Unix, usually a Unix-domain socket).pub async fn connect_uds<P: AsRef<std::path::Path>>(path: P) -> Result<Self, anyhow::Error> {let stream = tokio::net::UnixStream::connect(path).await?;Ok(AgentClient {stream,buf: CryptoVec::new(),})}/// Build a future that connects to an SSH agent via the provided/// stream (on Unix, usually a Unix-domain socket).pub async fn connect_env() -> Result<Self, anyhow::Error> {let var = std::env::var("SSH_AUTH_SOCK")?;Self::connect_uds(var).await}}impl<S: AsyncRead + AsyncWrite + Unpin> AgentClient<S> {async fn read_response(&mut self) -> Result<(), anyhow::Error> {// Writing the messageself.stream.write_all(&self.buf).await?;self.stream.flush().await?;// Reading the lengthself.buf.clear();self.buf.resize(4);self.stream.read_exact(&mut self.buf).await?;// Reading the rest of the bufferlet len = BigEndian::read_u32(&self.buf) as usize;self.buf.clear();self.buf.resize(len);self.stream.read_exact(&mut self.buf).await?;Ok(())}/// Send a key to the agent, with a (possibly empty) slice of/// constraints to apply when using the key to sign.pub async fn add_identity(&mut self,key: &key::KeyPair,constraints: &[Constraint],) -> Result<(), anyhow::Error> {self.buf.clear();self.buf.resize(4);if constraints.is_empty() {self.buf.push(msg::ADD_IDENTITY)} else {self.buf.push(msg::ADD_ID_CONSTRAINED)}match *key {key::KeyPair::Ed25519(ref secret) => {self.buf.extend_ssh_string(b"ssh-ed25519");let public = &secret.key[32..];self.buf.extend_ssh_string(public);self.buf.push_u32_be(64);self.buf.extend(&secret.key);self.buf.extend_ssh_string(b"");}key::KeyPair::RSA { ref key, .. } => {self.buf.extend_ssh_string(b"ssh-rsa");self.buf.extend_ssh_mpint(&key.n().to_vec());self.buf.extend_ssh_mpint(&key.e().to_vec());self.buf.extend_ssh_mpint(&key.d().to_vec());if let Some(iqmp) = key.iqmp() {self.buf.extend_ssh_mpint(&iqmp.to_vec());} else {let mut ctx = openssl::bn::BigNumContext::new()?;let mut iqmp = openssl::bn::BigNum::new()?;iqmp.mod_inverse(key.p().unwrap(), key.q().unwrap(), &mut ctx)?;self.buf.extend_ssh_mpint(&iqmp.to_vec());}self.buf.extend_ssh_mpint(&key.p().unwrap().to_vec());self.buf.extend_ssh_mpint(&key.q().unwrap().to_vec());self.buf.extend_ssh_string(b"");}}if !constraints.is_empty() {self.buf.push_u32_be(constraints.len() as u32);for cons in constraints {match *cons {Constraint::KeyLifetime { seconds } => {self.buf.push(msg::CONSTRAIN_LIFETIME);self.buf.push_u32_be(seconds)}Constraint::Confirm => self.buf.push(msg::CONSTRAIN_CONFIRM),Constraint::Extensions {ref name,ref details,} => {self.buf.push(msg::CONSTRAIN_EXTENSION);self.buf.extend_ssh_string(name);self.buf.extend_ssh_string(details);}}}}let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[..], len as u32);self.read_response().await?;Ok(())}/// Add a smart card to the agent, with a (possibly empty) set of/// constraints to apply when signing.pub async fn add_smartcard_key(&mut self,id: &str,pin: &[u8],constraints: &[Constraint],) -> Result<(), anyhow::Error> {self.buf.clear();self.buf.resize(4);if constraints.is_empty() {self.buf.push(msg::ADD_SMARTCARD_KEY)} else {self.buf.push(msg::ADD_SMARTCARD_KEY_CONSTRAINED)}self.buf.extend_ssh_string(id.as_bytes());self.buf.extend_ssh_string(pin);if !constraints.is_empty() {self.buf.push_u32_be(constraints.len() as u32);for cons in constraints {match *cons {Constraint::KeyLifetime { seconds } => {self.buf.push(msg::CONSTRAIN_LIFETIME);self.buf.push_u32_be(seconds)}Constraint::Confirm => self.buf.push(msg::CONSTRAIN_CONFIRM),Constraint::Extensions {ref name,ref details,} => {self.buf.push(msg::CONSTRAIN_EXTENSION);self.buf.extend_ssh_string(name);self.buf.extend_ssh_string(details);}}}}let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[0..], len as u32);self.read_response().await?;Ok(())}/// Lock the agent, making it refuse to sign until unlocked.pub async fn lock(&mut self, passphrase: &[u8]) -> Result<(), anyhow::Error> {self.buf.clear();self.buf.resize(4);self.buf.push(msg::LOCK);self.buf.extend_ssh_string(passphrase);let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[0..], len as u32);self.read_response().await?;Ok(())}/// Unlock the agent, allowing it to sign again.pub async fn unlock(&mut self, passphrase: &[u8]) -> Result<(), anyhow::Error> {self.buf.clear();self.buf.resize(4);self.buf.push(msg::UNLOCK);self.buf.extend_ssh_string(passphrase);let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[0..], len as u32);self.read_response().await?;Ok(())}/// Ask the agent for a list of the currently registered secret/// keys.pub async fn request_identities(&mut self) -> Result<Vec<PublicKey>, anyhow::Error> {self.buf.clear();self.buf.resize(4);self.buf.push(msg::REQUEST_IDENTITIES);let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[0..], len as u32);self.read_response().await?;debug!("identities: {:?}", &self.buf[..]);let mut keys = Vec::new();if self.buf[0] == msg::IDENTITIES_ANSWER {let mut r = self.buf.reader(1);let n = r.read_u32()?;for _ in 0..n {let key = r.read_string()?;let _ = r.read_string()?;let mut r = key.reader(0);let t = r.read_string()?;debug!("t = {:?}", std::str::from_utf8(t));match t {b"ssh-rsa" => {let e = r.read_mpint()?;let n = r.read_mpint()?;use openssl::bn::BigNum;use openssl::pkey::PKey;use openssl::rsa::Rsa;keys.push(PublicKey::RSA {key: key::OpenSSLPKey(PKey::from_rsa(Rsa::from_public_components(BigNum::from_slice(n)?,BigNum::from_slice(e)?,)?)?),hash: SignatureHash::SHA2_512,})}b"ssh-ed25519" => {let mut p = key::ed25519::PublicKey::new_zeroed();p.key.clone_from_slice(r.read_string()?);keys.push(PublicKey::Ed25519(p))}t => return Err(Error::UnsupportedKeyType(t.to_vec()).into()),}}}Ok(keys)}/// Ask the agent to sign the supplied piece of data.pub fn sign_request(mut self,public: &key::PublicKey,mut data: CryptoVec,) -> impl futures::Future<Output = (Self, Result<CryptoVec, anyhow::Error>)> {debug!("sign_request: {:?}", data);let hash = self.prepare_sign_request(public, &data);async move {let resp = self.read_response().await;debug!("resp = {:?}", &self.buf[..]);if let Err(e) = resp {return (self, Err(e));}if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {let resp = self.write_signature(hash, &mut data);if let Err(e) = resp {return (self, Err(e));}(self, Ok(data))} else if self.buf[0] == msg::FAILURE {(self, Err(Error::AgentFailure.into()))} else {debug!("self.buf = {:?}", &self.buf[..]);(self, Ok(data))}}}fn prepare_sign_request(&mut self, public: &key::PublicKey, data: &[u8]) -> u32 {self.buf.clear();self.buf.resize(4);self.buf.push(msg::SIGN_REQUEST);key_blob(&public, &mut self.buf);self.buf.extend_ssh_string(data);debug!("public = {:?}", public);let hash = match public {PublicKey::RSA { hash, .. } => match hash {SignatureHash::SHA2_256 => 2,SignatureHash::SHA2_512 => 4,SignatureHash::SHA1 => 0,},_ => 0,};self.buf.push_u32_be(hash);let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[0..], len as u32);hash}fn write_signature(&self, hash: u32, data: &mut CryptoVec) -> Result<(), anyhow::Error> {let mut r = self.buf.reader(1);let mut resp = r.read_string()?.reader(0);let t = resp.read_string()?;if (hash == 2 && t == b"rsa-sha2-256") || (hash == 4 && t == b"rsa-sha2-512") || hash == 0 {let sig = resp.read_string()?;data.push_u32_be((t.len() + sig.len() + 8) as u32);data.extend_ssh_string(t);data.extend_ssh_string(sig);}Ok(())}/// Ask the agent to sign the supplied piece of data.pub fn sign_request_base64(mut self,public: &key::PublicKey,data: &[u8],) -> impl futures::Future<Output = (Self, Result<String, anyhow::Error>)> {debug!("sign_request: {:?}", data);self.prepare_sign_request(public, &data);async move {let resp = self.read_response().await;if let Err(e) = resp {return (self, Err(e));}if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {let base64 = data_encoding::BASE64_NOPAD.encode(&self.buf[1..]);(self, Ok(base64))} else {(self, Ok(String::new()))}}}/// Ask the agent to sign the supplied piece of data, and return a `Signature`.pub fn sign_request_signature(mut self,public: &key::PublicKey,data: &[u8],) -> impl futures::Future<Output = Result<(Self, crate::signature::Signature), anyhow::Error>>{debug!("sign_request: {:?}", data);self.prepare_sign_request(public, data);async move {self.read_response().await?;if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {let sig: Result<crate::signature::Signature, anyhow::Error> = {let mut r = self.buf.reader(1);let mut resp = r.read_string()?.reader(0);let typ = resp.read_string()?;let sig = resp.read_string()?;use crate::signature::Signature;match typ {b"rsa-sha2-256" => Ok(Signature::RSA {bytes: sig.to_vec(),hash: SignatureHash::SHA2_256,}),b"rsa-sha2-512" => Ok(Signature::RSA {bytes: sig.to_vec(),hash: SignatureHash::SHA2_512,}),b"ssh-ed25519" => {let mut sig_bytes = [0; 64];sig_bytes.clone_from_slice(sig);Ok(Signature::Ed25519(crate::signature::SignatureBytes(sig_bytes,)))}_ => Err((Error::UnknownSignatureType {sig_type: std::str::from_utf8(typ).unwrap_or("").to_string(),}).into()),}};Ok((self, sig?))} else {Err(Error::AgentProtocolError.into())}}}/// Ask the agent to remove a key from its memory.pub async fn remove_identity(&mut self, public: &key::PublicKey) -> Result<(), anyhow::Error> {self.buf.clear();self.buf.resize(4);self.buf.push(msg::REMOVE_IDENTITY);key_blob(public, &mut self.buf);let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[0..], len as u32);self.read_response().await?;Ok(())}/// Ask the agent to remove a smartcard from its memory.pub async fn remove_smartcard_key(&mut self,id: &str,pin: &[u8],) -> Result<(), anyhow::Error> {self.buf.clear();self.buf.resize(4);self.buf.push(msg::REMOVE_SMARTCARD_KEY);self.buf.extend_ssh_string(id.as_bytes());self.buf.extend_ssh_string(pin);let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[0..], len as u32);self.read_response().await?;Ok(())}/// Ask the agent to forget all known keys.pub async fn remove_all_identities(&mut self) -> Result<(), anyhow::Error> {self.buf.clear();self.buf.resize(4);self.buf.push(msg::REMOVE_ALL_IDENTITIES);BigEndian::write_u32(&mut self.buf[0..], 5);self.read_response().await?;Ok(())}/// Send a custom message to the agent.pub async fn extension(&mut self, typ: &[u8], ext: &[u8]) -> Result<(), anyhow::Error> {self.buf.clear();self.buf.resize(4);self.buf.push(msg::EXTENSION);self.buf.extend_ssh_string(typ);self.buf.extend_ssh_string(ext);let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[0..], len as u32);self.read_response().await?;Ok(())}/// Ask the agent what extensions about supported extensions.pub async fn query_extension(&mut self,typ: &[u8],mut ext: CryptoVec,) -> Result<bool, anyhow::Error> {self.buf.clear();self.buf.resize(4);self.buf.push(msg::EXTENSION);self.buf.extend_ssh_string(typ);let len = self.buf.len() - 4;BigEndian::write_u32(&mut self.buf[0..], len as u32);self.read_response().await?;let mut r = self.buf.reader(1);ext.extend(r.read_string()?);Ok(!self.buf.is_empty() && self.buf[0] == msg::SUCCESS)}}fn key_blob(public: &key::PublicKey, buf: &mut CryptoVec) {match *public {PublicKey::RSA { ref key, .. } => {buf.extend(&[0, 0, 0, 0]);let len0 = buf.len();buf.extend_ssh_string(b"ssh-rsa");let rsa = key.0.rsa().unwrap();buf.extend_ssh_mpint(&rsa.e().to_vec());buf.extend_ssh_mpint(&rsa.n().to_vec());let len1 = buf.len();BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);}PublicKey::Ed25519(ref p) => {buf.extend(&[0, 0, 0, 0]);let len0 = buf.len();buf.extend_ssh_string(b"ssh-ed25519");buf.extend_ssh_string(&p.key[0..]);let len1 = buf.len();BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);}}}
[package]name = "thrussh-keys"version = "0.18.3"edition = "2018"authors = ["Pierre-Étienne Meunier <pe@pijul.org>"]description = "Deal with SSH keys: load them, decrypt them, call an SSH agent."keywords = ["ssh"]repository = "https://nest.pijul.com/pijul_org/thrussh"homepage = "https://pijul.org/thrussh"documentation = "https://docs.rs/thrussh-keys"license = "Apache-2.0"include = ["Cargo.toml","src/lib.rs","src/pem.rs","src/agent/mod.rs","src/agent/msg.rs","src/agent/server.rs","src/agent/client.rs","src/bcrypt_pbkdf.rs","src/blowfish.rs","src/encoding.rs","src/format/mod.rs","src/format/openssh.rs","src/format/pkcs5.rs","src/format/pkcs8.rs","src/key.rs","src/signature.rs"][dependencies]data-encoding = "2.1"byteorder = "1.3"tokio = { version = "0.2", features = [ "io-util", "rt-threaded", "time", "uds", "stream" ] }futures = "0.3"cryptovec = "0.6.0"yasna = { version = "0.3.1", features = [ "bit-vec", "num-bigint" ] }num-bigint = "0.2"num-integer = "0.1"openssl = "0.10"bit-vec = "0.6"thrussh-libsodium = "0.2.0"serde_derive = "1.0"serde = "1.0"dirs = "2.0"log = "0.4"anyhow = "1.0"thiserror = "1.0"[dev-dependencies]env_logger = "0.7"tempdir="0.3"
use futures::task::*;use std;use std::io::Read;use std::io::Write;use std::net::SocketAddr;use std::pin::Pin;use std::process::{Command, Stdio};use tokio;use tokio::net::TcpStream;/// A type to implement either a TCP socket, or proxying through an external command.pub enum Stream {#[allow(missing_docs)]Child(std::process::Child),#[allow(missing_docs)]Tcp(TcpStream),}impl Stream {/// Connect a direct TCP stream (as opposed to a proxied one).pub async fn tcp_connect(addr: &SocketAddr) -> Result<Stream, std::io::Error> {Ok(Stream::Tcp(tokio::net::TcpStream::connect(addr).await?))}/// Connect through a proxy command.pub async fn proxy_command(cmd: &str, args: &[&str]) -> Result<Stream, std::io::Error> {Ok(Stream::Child(Command::new(cmd).stdin(Stdio::piped()).stdout(Stdio::piped()).args(args).spawn().unwrap(),))}}impl tokio::io::AsyncRead for Stream {fn poll_read(mut self: Pin<&mut Self>,cx: &mut Context,buf: &mut [u8],) -> Poll<Result<usize, std::io::Error>> {match *self {Stream::Child(ref mut c) => Poll::Ready(c.stdout.as_mut().unwrap().read(buf)),Stream::Tcp(ref mut t) => Pin::new(t).poll_read(cx, buf),}}}impl tokio::io::AsyncWrite for Stream {fn poll_write(mut self: Pin<&mut Self>,cx: &mut Context,buf: &[u8],) -> Poll<Result<usize, std::io::Error>> {match *self {Stream::Child(ref mut c) => match c.stdin.as_mut().unwrap().write(buf) {Ok(n) => Poll::Ready(Ok(n)),Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => Poll::Pending,Err(e) => Poll::Ready(Err(e)),},Stream::Tcp(ref mut t) => Pin::new(t).poll_write(cx, buf),}}fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), std::io::Error>> {match *self {Stream::Child(ref mut c) => match c.stdin.as_mut().unwrap().flush() {Ok(_) => Poll::Ready(Ok(())),Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => Poll::Pending,Err(e) => Poll::Ready(Err(e)),},Stream::Tcp(ref mut t) => Pin::new(t).poll_flush(cx),}}fn poll_shutdown(mut self: Pin<&mut Self>,cx: &mut Context,) -> Poll<Result<(), std::io::Error>> {match *self {Stream::Child(ref mut c) => {c.stdin.take();Poll::Ready(Ok(()))}Stream::Tcp(ref mut t) => Pin::new(t).poll_shutdown(cx),}}}
extern crate regex;#[macro_use]extern crate log;#[macro_use]extern crate thiserror;use std::io::Read;use std::path::Path;#[derive(Debug, Error)]/// anyhow::Errors.pub enum Error {#[error("Host not found")]HostNotFound,#[error("No home directory")]NoHome,#[error("{}", source)]Io {#[from]source: std::io::Error,},}mod proxy;pub use proxy::*;#[derive(Debug, Default)]pub struct Config {pub user: Option<String>,pub host_name: Option<String>,pub port: Option<u16>,pub identity_file: Option<String>,pub proxy_command: Option<String>,pub add_keys_to_agent: AddKeysToAgent,}impl Config {pub fn update_proxy_command(&mut self) {if let Some(ref h) = self.host_name {if let Some(ref mut prox) = self.proxy_command {*prox = prox.replace("%h", h);}}if let Some(ref p) = self.port {if let Some(ref mut prox) = self.proxy_command {*prox = prox.replace("%p", &format!("{}", p));}}}}pub fn parse_home(host: &str) -> Result<Config, Error> {let mut home = if let Some(home) = dirs_next::home_dir() {home} else {return Err(Error::NoHome);};home.push(".ssh");home.push("config");parse_path(&home, host)}pub fn parse_path<P: AsRef<Path>>(path: P, host: &str) -> Result<Config, Error> {let mut s = String::new();let mut b = std::fs::File::open(path)?;b.read_to_string(&mut s)?;parse(&s, host)}#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum AddKeysToAgent {Yes,Confirm,Ask,No,}impl Default for AddKeysToAgent {fn default() -> Self {AddKeysToAgent::No}}pub fn parse(file: &str, host: &str) -> Result<Config, Error> {let mut config: Option<Config> = None;for line in file.lines() {let line = line.trim();if let Some(n) = line.find(' ') {let (key, value) = line.split_at(n);let lower = key.to_lowercase();if let Some(ref mut config) = config {match lower.as_str() {"host" => break,"user" => config.user = Some(value.trim_start().to_string()),"hostname" => config.host_name = Some(value.trim_start().to_string()),"port" => config.port = value.trim_start().parse().ok(),"identityfile" => config.identity_file = Some(value.trim_start().to_string()),"proxycommand" => config.proxy_command = Some(value.trim_start().to_string()),"addkeystoagent" => match value.to_lowercase().as_str() {"yes" => config.add_keys_to_agent = AddKeysToAgent::Yes,"confirm" => config.add_keys_to_agent = AddKeysToAgent::Confirm,"ask" => config.add_keys_to_agent = AddKeysToAgent::Ask,_ => config.add_keys_to_agent = AddKeysToAgent::No,},key => {debug!("{:?}", key);}}} else {match lower.as_str() {"host" => {if value.trim_start() == host {config = Some(Config::default())}}_ => {}}}}}if let Some(config) = config {Ok(config)} else {Err(Error::HostNotFound)}}
[package]name = "thrussh-config"description = "Utilities to parse .ssh/config files, including helpers to implement ProxyCommand in Thrussh."version = "0.2.1"authors = ["Pierre-Étienne Meunier <pe@pijul.org>"]include = [ "Cargo.toml", "src/lib.rs", "src/proxy.rs" ]license = "Apache-2.0"documentation = "https://docs.rs/thrussh-config"repository = "https://nest.pijul.com/pijul/thrussh"edition = "2018"[dependencies]regex = "1.0"lazy_static = "1.0"log = "0.4"dirs-next = "2.0"tokio = { version = "0.2", features = [ "io-util" ] }thrussh = "0.29.3"futures = "0.3"thiserror = "*"
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use super::*;use std::num::Wrapping;#[derive(Debug)]pub struct SSHBuffer {pub buffer: CryptoVec,pub len: usize, // next packet length.pub bytes: usize,// Sequence numbers are on 32 bits and wrap.// https://tools.ietf.org/html/rfc4253#section-6.4pub seqn: Wrapping<u32>,}impl SSHBuffer {pub fn new() -> Self {SSHBuffer {buffer: CryptoVec::new(),len: 0,bytes: 0,seqn: Wrapping(0),}}pub fn send_ssh_id(&mut self, id: &[u8]) {self.buffer.extend(id);self.buffer.push(b'\r');self.buffer.push(b'\n');}}
use crate::Error;use cryptovec::CryptoVec;use futures::task::*;use std;use std::pin::Pin;use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite};/// The buffer to read the identification string (first line in the/// protocol).struct ReadSshIdBuffer {pub buf: CryptoVec,pub total: usize,pub bytes_read: usize,pub sshid_len: usize,}impl ReadSshIdBuffer {pub fn id(&self) -> &[u8] {&self.buf[..self.sshid_len]}pub fn new() -> ReadSshIdBuffer {let mut buf = CryptoVec::new();buf.resize(256);ReadSshIdBuffer {buf: buf,sshid_len: 0,bytes_read: 0,total: 0,}}}impl std::fmt::Debug for ReadSshIdBuffer {fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {write!(fmt, "ReadSshId {:?}", self.id())}}/// SshRead<R> is the same as R, plus a small buffer in the beginning to/// read the identification string. After the first line in the/// connection, the `id` parameter is never used again.pub struct SshRead<R> {id: Option<ReadSshIdBuffer>,r: R,}impl<R: AsyncRead + Unpin> AsyncRead for SshRead<R> {fn poll_read(mut self: Pin<&mut Self>,cx: &mut Context,buf: &mut [u8],) -> Poll<Result<usize, std::io::Error>> {if let Some(mut id) = self.id.take() {debug!("id {:?} {:?}", id.total, id.bytes_read);if id.total > id.bytes_read {let result = {let mut readable = &id.buf[id.bytes_read..id.total];std::io::Read::read(&mut readable, buf).unwrap()};debug!("read {:?} bytes from id.buf", result);id.bytes_read += result;self.id = Some(id);return Poll::Ready(Ok(result));}}AsyncRead::poll_read(Pin::new(&mut self.get_mut().r), cx, buf)}}impl<R: std::io::Write> std::io::Write for SshRead<R> {fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {self.r.write(buf)}fn flush(&mut self) -> Result<(), std::io::Error> {self.r.flush()}}impl<R: AsyncWrite + Unpin> AsyncWrite for SshRead<R> {fn poll_write(mut self: Pin<&mut Self>,cx: &mut Context,buf: &[u8],) -> Poll<Result<usize, std::io::Error>> {AsyncWrite::poll_write(Pin::new(&mut self.r), cx, buf)}fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), std::io::Error>> {AsyncWrite::poll_flush(Pin::new(&mut self.r), cx)}fn poll_shutdown(mut self: Pin<&mut Self>,cx: &mut Context,) -> Poll<Result<(), std::io::Error>> {AsyncWrite::poll_shutdown(Pin::new(&mut self.r), cx)}}impl<R: AsyncRead + Unpin> SshRead<R> {pub fn new(r: R) -> Self {SshRead {id: Some(ReadSshIdBuffer::new()),r,}}pub async fn read_ssh_id(&mut self) -> Result<&[u8], anyhow::Error> {let ssh_id = self.id.as_mut().unwrap();loop {let mut i = 0;debug!("read_ssh_id: reading");let n = AsyncReadExt::read(&mut self.r, &mut ssh_id.buf[ssh_id.total..]).await?;debug!("read {:?}", n);ssh_id.total += n;debug!("{:?}", std::str::from_utf8(&ssh_id.buf[..ssh_id.total]));if n == 0 {return Err(Error::Disconnect.into());}loop {if i >= ssh_id.total - 1 {break;}if ssh_id.buf[i] == b'\r' && ssh_id.buf[i + 1] == b'\n' {ssh_id.bytes_read = i + 2;break;} else if ssh_id.buf[i + 1] == b'\n' {// This is really wrong, but OpenSSH 7.4 uses// it.ssh_id.bytes_read = i + 2;i += 1;break;} else {i += 1;}}if ssh_id.bytes_read > 0 {// If we have a full line, handle it.if i >= 8 {if &ssh_id.buf[0..8] == b"SSH-2.0-" {// Either the line starts with "SSH-2.0-"ssh_id.sshid_len = i;return Ok(&ssh_id.buf[..ssh_id.sshid_len]);}}// Else, it is a "preliminary" (see// https://tools.ietf.org/html/rfc4253#section-4.2),// and we can discard it and read the next one.ssh_id.total = 0;ssh_id.bytes_read = 0;}debug!("bytes_read: {:?}", ssh_id.bytes_read);}}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use crate::sshbuffer::SSHBuffer;use crate::{auth, cipher, kex, msg, negotiation};use crate::{Channel, ChannelId, Disconnect, Limits};use byteorder::{BigEndian, ByteOrder};use cryptovec::CryptoVec;use openssl::hash;use std::collections::HashMap;use std::num::Wrapping;use std::sync::Arc;use thrussh_keys::encoding::Encoding;#[derive(Debug)]pub(crate) struct Encrypted {pub state: EncryptedState,// It's always Some, except when we std::mem::replace it temporarily.pub exchange: Option<Exchange>,pub kex: kex::Algorithm,pub key: usize,pub mac: Option<&'static str>,pub session_id: hash::DigestBytes,pub rekey: Option<Kex>,pub channels: HashMap<ChannelId, Channel>,pub last_channel_id: Wrapping<u32>,pub wants_reply: bool,pub write: CryptoVec,pub write_cursor: usize,pub last_rekey: std::time::Instant,pub server_compression: crate::compression::Compression,pub client_compression: crate::compression::Compression,pub compress: crate::compression::Compress,pub decompress: crate::compression::Decompress,pub compress_buffer: CryptoVec,}pub(crate) struct CommonSession<Config> {pub auth_user: String,pub config: Config,pub encrypted: Option<Encrypted>,pub auth_method: Option<auth::Method>,pub write_buffer: SSHBuffer,pub kex: Option<Kex>,pub cipher: Arc<cipher::CipherPair>,pub wants_reply: bool,pub disconnected: bool,pub buffer: CryptoVec,}impl<C> CommonSession<C> {pub fn newkeys(&mut self, newkeys: NewKeys) {if let Some(ref mut enc) = self.encrypted {enc.exchange = Some(newkeys.exchange);enc.kex = newkeys.kex;enc.key = newkeys.key;enc.mac = newkeys.names.mac;self.cipher = Arc::new(newkeys.cipher);}}pub fn encrypted(&mut self, state: EncryptedState, newkeys: NewKeys) {self.encrypted = Some(Encrypted {exchange: Some(newkeys.exchange),kex: newkeys.kex,key: newkeys.key,mac: newkeys.names.mac,session_id: newkeys.session_id,state,rekey: None,channels: HashMap::new(),last_channel_id: Wrapping(1),wants_reply: false,write: CryptoVec::new(),write_cursor: 0,last_rekey: std::time::Instant::now(),server_compression: newkeys.names.server_compression,client_compression: newkeys.names.client_compression,compress: crate::compression::Compress::None,compress_buffer: CryptoVec::new(),decompress: crate::compression::Decompress::None,});self.cipher = Arc::new(newkeys.cipher);}/// Send a disconnect message.pub fn disconnect(&mut self, reason: Disconnect, description: &str, language_tag: &str) {let disconnect = |buf: &mut CryptoVec| {push_packet!(buf, {buf.push(msg::DISCONNECT);buf.push_u32_be(reason as u32);buf.extend_ssh_string(description.as_bytes());buf.extend_ssh_string(language_tag.as_bytes());});};if !self.disconnected {self.disconnected = true;if let Some(ref mut enc) = self.encrypted {disconnect(&mut enc.write)} else {disconnect(&mut self.write_buffer.buffer)}}}/// Send a single byte message onto the channel.pub fn byte(&mut self, channel: ChannelId, msg: u8) {if let Some(ref mut enc) = self.encrypted {enc.byte(channel, msg)}}}impl Encrypted {pub fn byte(&mut self, channel: ChannelId, msg: u8) {if let Some(channel) = self.channels.get(&channel) {push_packet!(self.write, {self.write.push(msg);self.write.push_u32_be(channel.recipient_channel);});}}/*pub fn authenticated(&mut self) {self.server_compression.init_compress(&mut self.compress);self.state = EncryptedState::Authenticated;}*/pub fn eof(&mut self, channel: ChannelId) {self.byte(channel, msg::CHANNEL_EOF);}pub fn sender_window_size(&self, channel: ChannelId) -> usize {if let Some(ref channel) = self.channels.get(&channel) {channel.sender_window_size as usize} else {0}}pub fn adjust_window_size(&mut self, channel: ChannelId, data: &[u8], target: u32) -> bool {debug!("adjust_window_size");if let Some(ref mut channel) = self.channels.get_mut(&channel) {debug!("channel {:?}", channel);// Ignore extra data.// https://tools.ietf.org/html/rfc4254#section-5.2if data.len() as u32 <= channel.sender_window_size {channel.sender_window_size -= data.len() as u32;}if channel.sender_window_size < target / 2 {debug!("sender_window_size {:?}, target {:?}",channel.sender_window_size, target);push_packet!(self.write, {self.write.push(msg::CHANNEL_WINDOW_ADJUST);self.write.push_u32_be(channel.recipient_channel);self.write.push_u32_be(target - channel.sender_window_size);});channel.sender_window_size = target;return true;}}false}pub fn flush_pending(&mut self, channel: ChannelId) -> usize {let mut pending_size = 0;if let Some(channel) = self.channels.get_mut(&channel) {while let Some((buf, a, size)) = channel.pending_data.pop_front() {let (buf, size_) = Self::data_noqueue(&mut self.write, channel, buf, size);pending_size += size_;if size_ < buf.len() {channel.pending_data.push_front((buf, a, size_));break;}}}pending_size}pub fn has_pending_data(&self, channel: ChannelId) -> bool {if let Some(channel) = self.channels.get(&channel) {!channel.pending_data.is_empty()} else {false}}fn data_noqueue(write: &mut CryptoVec,channel: &mut Channel,buf0: CryptoVec,from: usize,) -> (CryptoVec, usize) {use std::ops::Deref;let mut buf = if buf0.len() as u32 > channel.recipient_window_size {&buf0[from..channel.recipient_window_size as usize]} else {&buf0[from..]};let buf_len = buf.len();while buf.len() > 0 {// Compute the length we're allowed to send.let off = std::cmp::min(buf.len(), channel.recipient_maximum_packet_size as usize);push_packet!(write, {write.push(msg::CHANNEL_DATA);write.push_u32_be(channel.recipient_channel);write.extend_ssh_string(&buf[..off]);});debug!("buffer: {:?}", write.deref().len());channel.recipient_window_size -= off as u32;buf = &buf[off..]}debug!("buf.len() = {:?}, buf_len = {:?}", buf.len(), buf_len);(buf0, buf_len)}pub fn data(&mut self, channel: ChannelId, buf0: CryptoVec) {if let Some(channel) = self.channels.get_mut(&channel) {assert!(channel.confirmed);if !channel.pending_data.is_empty() {channel.pending_data.push_back((buf0, None, 0));return;}let (buf0, buf_len) = Self::data_noqueue(&mut self.write, channel, buf0, 0);if buf_len < buf0.len() {channel.pending_data.push_back((buf0, None, buf_len))}}}pub fn extended_data(&mut self, channel: ChannelId, ext: u32, buf0: CryptoVec) {use std::ops::Deref;if let Some(channel) = self.channels.get_mut(&channel) {assert!(channel.confirmed);if !channel.pending_data.is_empty() {channel.pending_data.push_back((buf0, Some(ext), 0));return;}let mut buf = if buf0.len() as u32 > channel.recipient_window_size {&buf0[0..channel.recipient_window_size as usize]} else {&buf0};let buf_len = buf.len();while buf.len() > 0 {// Compute the length we're allowed to send.let off = std::cmp::min(buf.len(), channel.recipient_maximum_packet_size as usize);push_packet!(self.write, {self.write.push(msg::CHANNEL_EXTENDED_DATA);self.write.push_u32_be(channel.recipient_channel);self.write.push_u32_be(ext);self.write.extend_ssh_string(&buf[..off]);});debug!("buffer: {:?}", self.write.deref().len());channel.recipient_window_size -= off as u32;buf = &buf[off..]}debug!("buf.len() = {:?}, buf_len = {:?}", buf.len(), buf_len);if buf_len < buf0.len() {channel.pending_data.push_back((buf0, Some(ext), buf_len))}}}pub fn flush(&mut self,limits: &Limits,cipher: &cipher::CipherPair,write_buffer: &mut SSHBuffer,) -> bool {// If there are pending packets (and we've not started to rekey), flush them.{while self.write_cursor < self.write.len() {// Read a single packet, encrypt and send it.let len = BigEndian::read_u32(&self.write[self.write_cursor..]) as usize;let packet = self.compress.compress(&self.write[(self.write_cursor + 4)..(self.write_cursor + 4 + len)],&mut self.compress_buffer,).unwrap();cipher.write(packet, write_buffer);self.write_cursor += 4 + len}}if self.write_cursor >= self.write.len() {// If all packets have been written, clear.self.write_cursor = 0;self.write.clear();}let now = std::time::Instant::now();let dur = now.duration_since(self.last_rekey);write_buffer.bytes >= limits.rekey_write_limit || dur >= limits.rekey_time_limit}pub fn new_channel_id(&mut self) -> ChannelId {self.last_channel_id += Wrapping(1);while self.channels.contains_key(&ChannelId(self.last_channel_id.0)){self.last_channel_id += Wrapping(1)}ChannelId(self.last_channel_id.0)}pub fn new_channel(&mut self, window_size: u32, maxpacket: u32) -> ChannelId {loop {self.last_channel_id += Wrapping(1);if let std::collections::hash_map::Entry::Vacant(vacant_entry) =self.channels.entry(ChannelId(self.last_channel_id.0)){vacant_entry.insert(Channel {recipient_channel: 0,sender_channel: ChannelId(self.last_channel_id.0),sender_window_size: window_size,recipient_window_size: 0,sender_maximum_packet_size: maxpacket,recipient_maximum_packet_size: 0,confirmed: false,wants_reply: false,pending_data: std::collections::VecDeque::new(),});return ChannelId(self.last_channel_id.0);}}}}#[derive(Debug)]pub enum EncryptedState {WaitingServiceRequest { sent: bool, accepted: bool },WaitingAuthRequest(auth::AuthRequest),InitCompression,Authenticated,}#[derive(Debug)]pub struct Exchange {pub client_id: CryptoVec,pub server_id: CryptoVec,pub client_kex_init: CryptoVec,pub server_kex_init: CryptoVec,pub client_ephemeral: CryptoVec,pub server_ephemeral: CryptoVec,}impl Exchange {pub fn new() -> Self {Exchange {client_id: CryptoVec::new(),server_id: CryptoVec::new(),client_kex_init: CryptoVec::new(),server_kex_init: CryptoVec::new(),client_ephemeral: CryptoVec::new(),server_ephemeral: CryptoVec::new(),}}}#[derive(Debug)]pub enum Kex {/// Version number sent. `algo` and `sent` tell wether kexinit has/// been received, and sent, respectively.KexInit(KexInit),/// Algorithms have been determined, the DH algorithm should run.KexDh(KexDh),/// The kex has run.KexDhDone(KexDhDone),/// The DH is over, we've sent the NEWKEYS packet, and are waiting/// the NEWKEYS from the other side.NewKeys(NewKeys),}#[derive(Debug)]pub struct KexInit {pub algo: Option<negotiation::Names>,pub exchange: Exchange,pub session_id: Option<hash::DigestBytes>,pub sent: bool,}impl KexInit {pub fn received_rekey(ex: Exchange,algo: negotiation::Names,session_id: &hash::DigestBytes,) -> Self {let mut kexinit = KexInit {exchange: ex,algo: Some(algo),sent: false,session_id: Some(session_id.clone()),};kexinit.exchange.client_kex_init.clear();kexinit.exchange.server_kex_init.clear();kexinit.exchange.client_ephemeral.clear();kexinit.exchange.server_ephemeral.clear();kexinit}pub fn initiate_rekey(ex: Exchange, session_id: &hash::DigestBytes) -> Self {let mut kexinit = KexInit {exchange: ex,algo: None,sent: true,session_id: Some(session_id.clone()),};kexinit.exchange.client_kex_init.clear();kexinit.exchange.server_kex_init.clear();kexinit.exchange.client_ephemeral.clear();kexinit.exchange.server_ephemeral.clear();kexinit}}#[derive(Debug)]pub struct KexDh {pub exchange: Exchange,pub names: negotiation::Names,pub key: usize,pub session_id: Option<hash::DigestBytes>,}#[derive(Debug)]pub struct KexDhDone {pub exchange: Exchange,pub kex: kex::Algorithm,pub key: usize,pub session_id: Option<hash::DigestBytes>,pub names: negotiation::Names,}impl KexDhDone {pub fn compute_keys(self,hash: hash::DigestBytes,is_server: bool,) -> Result<NewKeys, anyhow::Error> {let session_id = if let Some(session_id) = self.session_id {session_id} else {hash.clone()};// Now computing keys.let c = self.kex.compute_keys(&session_id, &hash, self.names.cipher, is_server)?;Ok(NewKeys {exchange: self.exchange,names: self.names,kex: self.kex,key: self.key,cipher: c,session_id: session_id,received: false,sent: false,})}}#[derive(Debug)]pub struct NewKeys {pub exchange: Exchange,pub names: negotiation::Names,pub kex: kex::Algorithm,pub key: usize,pub cipher: cipher::CipherPair,pub session_id: hash::DigestBytes,pub received: bool,pub sent: bool,}
use super::*;use crate::msg;use std::sync::Arc;use thrussh_keys::encoding::Encoding;use tokio::sync::mpsc::{Receiver, Sender};/// A connected server session. This type is unique to a client.pub struct Session {pub(crate) common: CommonSession<Arc<Config>>,pub(crate) sender: Handle,pub(crate) receiver: Receiver<(ChannelId, ChannelMsg)>,pub(crate) target_window_size: u32,}#[derive(Clone)]/// Handle to a session, used to send messages to a client outside of/// the request/response cycle.pub struct Handle {pub(crate) sender: Sender<(ChannelId, ChannelMsg)>,}impl Handle {/// Send data to the session referenced by this handler.pub async fn data(&mut self, id: ChannelId, data: CryptoVec) -> Result<(), CryptoVec> {self.sender.send((id, ChannelMsg::Data { data })).await.map_err(|e| match e.0 {(_, ChannelMsg::Data { data }) => data,_ => unreachable!(),})}/// Send data to the session referenced by this handler.pub async fn extended_data(&mut self,id: ChannelId,ext: u32,data: CryptoVec,) -> Result<(), CryptoVec> {self.sender.send((id, ChannelMsg::ExtendedData { ext, data })).await.map_err(|e| match e.0 {(_, ChannelMsg::ExtendedData { data, .. }) => data,_ => unreachable!(),})}/// Send EOF to the session referenced by this handler.pub async fn eof(&mut self, id: ChannelId) -> Result<(), ()> {self.sender.send((id, ChannelMsg::Eof)).await.map_err(|_| ())}/// Inform the client of whether they may perform/// control-S/control-Q flow control. See/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-6.8).pub async fn xon_xoff_request(&mut self, id: ChannelId, client_can_do: bool) -> Result<(), ()> {self.sender.send((id, ChannelMsg::XonXoff { client_can_do })).await.map_err(|_| ())}/// Send the exit status of a program.pub async fn exit_status_request(&mut self, id: ChannelId, exit_status: u32) -> Result<(), ()> {self.sender.send((id, ChannelMsg::ExitStatus { exit_status })).await.map_err(|_| ())}/// If the program was killed by a signal, send the details about the signal to the client.pub async fn exit_signal_request(&mut self,id: ChannelId,signal_name: Sig,core_dumped: bool,error_message: String,lang_tag: String,) -> Result<(), ()> {self.sender.send((id,ChannelMsg::ExitSignal {signal_name,core_dumped,error_message,lang_tag,},)).await.map_err(|_| ())}}impl Session {pub(crate) fn is_rekeying(&self) -> bool {if let Some(ref enc) = self.common.encrypted {enc.rekey.is_some()} else {true}}/// Get a handle to this session.pub fn handle(&self) -> Handle {self.sender.clone()}pub fn writable_packet_size(&self, channel: &ChannelId) -> u32 {if let Some(ref enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {return channel.sender_window_size.min(channel.sender_maximum_packet_size);}}0}pub fn window_size(&self, channel: &ChannelId) -> u32 {if let Some(ref enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {return channel.sender_window_size;}}0}pub fn max_packet_size(&self, channel: &ChannelId) -> u32 {if let Some(ref enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {return channel.sender_maximum_packet_size;}}0}/// Flush the session, i.e. encrypt the pending buffer.pub fn flush(&mut self) -> Result<(), anyhow::Error> {if let Some(ref mut enc) = self.common.encrypted {if enc.flush(&self.common.config.as_ref().limits,&self.common.cipher,&mut self.common.write_buffer,) {if enc.rekey.is_none() {if let Some(exchange) = enc.exchange.take() {let mut kexinit = KexInit::initiate_rekey(exchange, &enc.session_id);kexinit.server_write(&self.common.config.as_ref(),&mut self.common.cipher,&mut self.common.write_buffer,)?;enc.rekey = Some(Kex::KexInit(kexinit))}}}}Ok(())}pub fn flush_pending(&mut self, channel: ChannelId) -> usize {if let Some(ref mut enc) = self.common.encrypted {enc.flush_pending(channel)} else {0}}pub fn sender_window_size(&self, channel: ChannelId) -> usize {if let Some(ref enc) = self.common.encrypted {enc.sender_window_size(channel)} else {0}}pub fn has_pending_data(&self, channel: ChannelId) -> bool {if let Some(ref enc) = self.common.encrypted {enc.has_pending_data(channel)} else {false}}/// Retrieves the configuration of this session.pub fn config(&self) -> &Config {&self.common.config}/// Sends a disconnect message.pub fn disconnect(&mut self, reason: Disconnect, description: &str, language_tag: &str) {self.common.disconnect(reason, description, language_tag);}/// Send a "success" reply to a /global/ request (requests without/// a channel number, such as TCP/IP forwarding or/// cancelling). Always call this function if the request was/// successful (it checks whether the client expects an answer).pub fn request_success(&mut self) {if self.common.wants_reply {if let Some(ref mut enc) = self.common.encrypted {self.common.wants_reply = false;push_packet!(enc.write, enc.write.push(msg::REQUEST_SUCCESS))}}}/// Send a "failure" reply to a global request.pub fn request_failure(&mut self) {if let Some(ref mut enc) = self.common.encrypted {self.common.wants_reply = false;push_packet!(enc.write, enc.write.push(msg::REQUEST_FAILURE))}}/// Send a "success" reply to a channel request. Always call this/// function if the request was successful (it checks whether the/// client expects an answer).pub fn channel_success(&mut self, channel: ChannelId) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get_mut(&channel) {assert!(channel.confirmed);if channel.wants_reply {channel.wants_reply = false;debug!("channel_success {:?}", channel);push_packet!(enc.write, {enc.write.push(msg::CHANNEL_SUCCESS);enc.write.push_u32_be(channel.recipient_channel);})}}}}/// Send a "failure" reply to a global request.pub fn channel_failure(&mut self, channel: ChannelId) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get_mut(&channel) {assert!(channel.confirmed);if channel.wants_reply {channel.wants_reply = false;push_packet!(enc.write, {enc.write.push(msg::CHANNEL_FAILURE);enc.write.push_u32_be(channel.recipient_channel);})}}}}/// Send a "failure" reply to a request to open a channel open.pub fn channel_open_failure(&mut self,channel: ChannelId,reason: ChannelOpenFailure,description: &str,language: &str,) {if let Some(ref mut enc) = self.common.encrypted {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_OPEN_FAILURE);enc.write.push_u32_be(channel.0);enc.write.push_u32_be(reason as u32);enc.write.extend_ssh_string(description.as_bytes());enc.write.extend_ssh_string(language.as_bytes());})}}/// Close a channel.pub fn close(&mut self, channel: ChannelId) {self.common.byte(channel, msg::CHANNEL_CLOSE);}/// Send EOF to a channelpub fn eof(&mut self, channel: ChannelId) {self.common.byte(channel, msg::CHANNEL_EOF);}/// Send data to a channel. On session channels, `extended` can be/// used to encode standard error by passing `Some(1)`, and stdout/// by passing `None`.////// The number of bytes added to the "sending pipeline" (to be/// processed by the event loop) is returned.pub fn data(&mut self, channel: ChannelId, data: CryptoVec) {if let Some(ref mut enc) = self.common.encrypted {enc.data(channel, data)} else {unreachable!()}}/// Send data to a channel. On session channels, `extended` can be/// used to encode standard error by passing `Some(1)`, and stdout/// by passing `None`.////// The number of bytes added to the "sending pipeline" (to be/// processed by the event loop) is returned.pub fn extended_data(&mut self, channel: ChannelId, extended: u32, data: CryptoVec) {if let Some(ref mut enc) = self.common.encrypted {enc.extended_data(channel, extended, data)} else {unreachable!()}}/// Inform the client of whether they may perform/// control-S/control-Q flow control. See/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-6.8).pub fn xon_xoff_request(&mut self, channel: ChannelId, client_can_do: bool) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {assert!(channel.confirmed);push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"xon-xoff");enc.write.push(0);enc.write.push(if client_can_do { 1 } else { 0 });})}}}/// Send the exit status of a program.pub fn exit_status_request(&mut self, channel: ChannelId, exit_status: u32) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {assert!(channel.confirmed);push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"exit-status");enc.write.push(0);enc.write.push_u32_be(exit_status)})}}}/// If the program was killed by a signal, send the details about the signal to the client.pub fn exit_signal_request(&mut self,channel: ChannelId,signal: Sig,core_dumped: bool,error_message: &str,language_tag: &str,) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {assert!(channel.confirmed);push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"exit-signal");enc.write.push(0);enc.write.extend_ssh_string(signal.name().as_bytes());enc.write.push(if core_dumped { 1 } else { 0 });enc.write.extend_ssh_string(error_message.as_bytes());enc.write.extend_ssh_string(language_tag.as_bytes());})}}}/// Open a TCP/IP forwarding channel, when a connection comes to a/// local port for which forwarding has been requested. See/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-7). The/// TCP/IP packets can then be tunneled through the channel using/// `.data()`.pub fn channel_open_forwarded_tcpip(&mut self,connected_address: &str,connected_port: u32,originator_address: &str,originator_port: u32,) -> Result<ChannelId, Error> {let result = if let Some(ref mut enc) = self.common.encrypted {match enc.state {EncryptedState::Authenticated => {debug!("sending open request");let sender_channel = enc.new_channel(self.common.config.window_size,self.common.config.maximum_packet_size,);push_packet!(enc.write, {enc.write.push(msg::CHANNEL_OPEN);enc.write.extend_ssh_string(b"forwarded-tcpip");// sender channel id.enc.write.push_u32_be(sender_channel.0);// window.enc.write.push_u32_be(self.common.config.as_ref().window_size);// max packet size.enc.write.push_u32_be(self.common.config.as_ref().maximum_packet_size);enc.write.extend_ssh_string(connected_address.as_bytes());enc.write.push_u32_be(connected_port); // sender channel id.enc.write.extend_ssh_string(originator_address.as_bytes());enc.write.push_u32_be(originator_port); // sender channel id.});sender_channel}_ => return Err(Error::Inconsistent),}} else {return Err(Error::Inconsistent);};Ok(result)}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use std;use std::net::ToSocketAddrs;use std::sync::Arc;use futures::future::Future;use futures::stream::TryStreamExt;use thrussh_keys::key;use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};use tokio::net::TcpListener;use crate::session::*;use crate::ssh_read::*;use crate::sshbuffer::*;use crate::*;mod kex;mod session;pub use self::kex::*;pub use self::session::*;mod encrypted;#[derive(Debug)]/// Configuration of a server.pub struct Config {/// The server ID string sent at the beginning of the protocol.pub server_id: String,/// Authentication methods proposed to the client.pub methods: auth::MethodSet,/// The authentication banner, usually a warning message shown to the client.pub auth_banner: Option<&'static str>,/// Authentication rejections must happen in constant time for/// security reasons. Thrussh does not handle this by default.pub auth_rejection_time: std::time::Duration,/// The server's keys. The first key pair in the client's preference order will be chosen.pub keys: Vec<key::KeyPair>,/// The bytes and time limits before key re-exchange.pub limits: Limits,/// The initial size of a channel (used for flow control).pub window_size: u32,/// The maximal size of a single packet.pub maximum_packet_size: u32,/// Lists of preferred algorithms.pub preferred: Preferred,/// Maximal number of allowed authentication attempts.pub max_auth_attempts: usize,/// Time after which the connection is garbage-collected.pub connection_timeout: Option<std::time::Duration>,}impl Default for Config {fn default() -> Config {Config {server_id: format!("SSH-2.0-{}_{}",env!("CARGO_PKG_NAME"),env!("CARGO_PKG_VERSION")),methods: auth::MethodSet::all(),auth_banner: None,auth_rejection_time: std::time::Duration::from_secs(1),keys: Vec::new(),window_size: 2097152,maximum_packet_size: 32768,limits: Limits::default(),preferred: Default::default(),max_auth_attempts: 10,connection_timeout: Some(std::time::Duration::from_secs(600)),}}}/// A client's response in a challenge-response authentication.#[derive(Debug)]pub struct Response<'a> {pos: thrussh_keys::encoding::Position<'a>,n: u32,}impl<'a> Iterator for Response<'a> {type Item = &'a [u8];fn next(&mut self) -> Option<Self::Item> {if self.n == 0 {None} else {self.n -= 1;self.pos.read_string().ok()}}}use std::borrow::Cow;/// An authentication result, in a challenge-response authentication.#[derive(Debug, PartialEq, Eq)]pub enum Auth {/// Reject the authentication request.Reject,/// Accept the authentication request.Accept,/// Method was not accepted, but no other check was performed.UnsupportedMethod,/// Partially accept the challenge-response authentication/// request, providing more instructions for the client to follow.Partial {/// Name of this challenge.name: Cow<'static, str>,/// Instructions for this challenge.instructions: Cow<'static, str>,/// A number of prompts to the user. Each prompt has a `bool`/// indicating whether the terminal must echo the characters/// typed by the user.prompts: Cow<'static, [(Cow<'static, str>, bool)]>,},}/// Server handler. Each client will have their own handler.pub trait Handler: Sized {/// The type of authentications, which can be a future ultimately/// resolving totype FutureAuth: Future<Output = Result<(Self, Auth), anyhow::Error>> + Send;/// The type of units returned by some parts of this handler.type FutureUnit: Future<Output = Result<(Self, Session), anyhow::Error>> + Send;/// The type of future bools returned by some parts of this handler.type FutureBool: Future<Output = Result<(Self, Session, bool), anyhow::Error>> + Send;/// Convert an `Auth` to `Self::FutureAuth`. This is used to/// produce the default handlers.fn finished_auth(self, auth: Auth) -> Self::FutureAuth;/// Convert a `bool` to `Self::FutureBool`. This is used to/// produce the default handlers.fn finished_bool(self, b: bool, session: Session) -> Self::FutureBool;/// Produce a `Self::FutureUnit`. This is used to produce the/// default handlers.fn finished(self, session: Session) -> Self::FutureUnit;/// Check authentication using the "none" method. Thrussh makes/// sure rejection happens in time `config.auth_rejection_time`,/// except if this method takes more than that.#[allow(unused_variables)]fn auth_none(self, user: &str) -> Self::FutureAuth {self.finished_auth(Auth::Reject)}/// Check authentication using the "password" method. Thrussh/// makes sure rejection happens in time/// `config.auth_rejection_time`, except if this method takes more/// than that.#[allow(unused_variables)]fn auth_password(self, user: &str, password: &str) -> Self::FutureAuth {self.finished_auth(Auth::Reject)}/// Check authentication using the "publickey" method. This method/// should just check whether the public key matches the/// authorized ones. Thrussh then checks the signature. If the key/// is unknown, or the signature is invalid, Thrussh guarantees/// that rejection happens in constant time/// `config.auth_rejection_time`, except if this method takes more/// time than that.#[allow(unused_variables)]fn auth_publickey(self, user: &str, public_key: &key::PublicKey) -> Self::FutureAuth {self.finished_auth(Auth::Reject)}/// Check authentication using the "keyboard-interactive"/// method. Thrussh makes sure rejection happens in time/// `config.auth_rejection_time`, except if this method takes more/// than that.#[allow(unused_variables)]fn auth_keyboard_interactive(self,user: &str,submethods: &str,response: Option<Response>,) -> Self::FutureAuth {self.finished_auth(Auth::Reject)}/// Called when the client closes a channel.#[allow(unused_variables)]fn channel_close(self, channel: ChannelId, session: Session) -> Self::FutureUnit {self.finished(session)}/// Called when the client sends EOF to a channel.#[allow(unused_variables)]fn channel_eof(self, channel: ChannelId, session: Session) -> Self::FutureUnit {self.finished(session)}/// Called when a new session channel is created.#[allow(unused_variables)]fn channel_open_session(self, channel: ChannelId, session: Session) -> Self::FutureUnit {self.finished(session)}/// Called when a new X11 channel is created.#[allow(unused_variables)]fn channel_open_x11(self,channel: ChannelId,originator_address: &str,originator_port: u32,session: Session,) -> Self::FutureUnit {self.finished(session)}/// Called when a new channel is created.#[allow(unused_variables)]fn channel_open_direct_tcpip(self,channel: ChannelId,host_to_connect: &str,port_to_connect: u32,originator_address: &str,originator_port: u32,session: Session,) -> Self::FutureUnit {self.finished(session)}/// Called when a data packet is received. A response can be/// written to the `response` argument.#[allow(unused_variables)]fn data(self, channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit {self.finished(session)}/// Called when an extended data packet is received. Code 1 means/// that this packet comes from stderr, other codes are not/// defined (see/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-5.2)).#[allow(unused_variables)]fn extended_data(self,channel: ChannelId,code: u32,data: &[u8],session: Session,) -> Self::FutureUnit {self.finished(session)}/// Called when the network window is adjusted, meaning that we/// can send more bytes.#[allow(unused_variables)]fn window_adjusted(self,channel: ChannelId,new_window_size: usize,mut session: Session,) -> Self::FutureUnit {if let Some(ref mut enc) = session.common.encrypted {enc.flush_pending(channel);}self.finished(session)}/// Called when this server adjusts the network window. Return the/// next target window.#[allow(unused_variables)]fn adjust_window(&mut self, channel: ChannelId, current: u32) -> u32 {current}/// The client requests a pseudo-terminal with the given/// specifications.#[allow(unused_variables)]fn pty_request(self,channel: ChannelId,term: &str,col_width: u32,row_height: u32,pix_width: u32,pix_height: u32,modes: &[(Pty, u32)],session: Session,) -> Self::FutureUnit {self.finished(session)}/// The client requests an X11 connection.#[allow(unused_variables)]fn x11_request(self,channel: ChannelId,single_connection: bool,x11_auth_protocol: &str,x11_auth_cookie: &str,x11_screen_number: u32,session: Session,) -> Self::FutureUnit {self.finished(session)}/// The client wants to set the given environment variable. Check/// these carefully, as it is dangerous to allow any variable/// environment to be set.#[allow(unused_variables)]fn env_request(self,channel: ChannelId,variable_name: &str,variable_value: &str,session: Session,) -> Self::FutureUnit {self.finished(session)}/// The client requests a shell.#[allow(unused_variables)]fn shell_request(self, channel: ChannelId, session: Session) -> Self::FutureUnit {self.finished(session)}/// The client sends a command to execute, to be passed to a/// shell. Make sure to check the command before doing so.#[allow(unused_variables)]fn exec_request(self, channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit {self.finished(session)}/// The client asks to start the subsystem with the given name/// (such as sftp).#[allow(unused_variables)]fn subsystem_request(self,channel: ChannelId,name: &str,session: Session,) -> Self::FutureUnit {self.finished(session)}/// The client's pseudo-terminal window size has changed.#[allow(unused_variables)]fn window_change_request(self,channel: ChannelId,col_width: u32,row_height: u32,pix_width: u32,pix_height: u32,session: Session,) -> Self::FutureUnit {self.finished(session)}/// The client is sending a signal (usually to pass to the/// currently running process).#[allow(unused_variables)]fn signal(self, channel: ChannelId, signal_name: Sig, session: Session) -> Self::FutureUnit {self.finished(session)}/// Used for reverse-forwarding ports, see/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-7).#[allow(unused_variables)]fn tcpip_forward(self, address: &str, port: u32, session: Session) -> Self::FutureBool {self.finished_bool(false, session)}/// Used to stop the reverse-forwarding of a port, see/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-7).#[allow(unused_variables)]fn cancel_tcpip_forward(self, address: &str, port: u32, session: Session) -> Self::FutureBool {self.finished_bool(false, session)}}/// Trait used to create new handlers when clients connect.pub trait Server {/// The type of handlers.type Handler: Handler + Send;/// Called when a new client connects.fn new(&mut self, peer_addr: Option<std::net::SocketAddr>) -> Self::Handler;}/// Run a server./// Create a new `Connection` from the server's configuration, a/// stream and a [`Handler`](trait.Handler.html).pub async fn run<H: Server + Send + 'static>(config: Arc<Config>,addr: &str,mut server: H,) -> Result<(), std::io::Error> {let addr = addr.to_socket_addrs().unwrap().next().unwrap();let mut socket = TcpListener::bind(&addr).await?;if config.maximum_packet_size > 65535 {error!("Maximum packet size ({:?}) should not larger than a TCP packet (65535)",config.maximum_packet_size);}socket.incoming().try_for_each(move |socket| {let config = config.clone();let server = server.new(socket.peer_addr().ok());async move {tokio::spawn(run_stream(config, socket, server));Ok(())}}).await?;Ok(())}use std::cell::RefCell;thread_local! {static B1: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());static B2: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());}pub async fn timeout(delay: Option<std::time::Duration>) {if let Some(delay) = delay {tokio::time::delay_for(delay).await} else {futures::future::pending().await};}pub async fn run_stream<H: Handler, R>(config: Arc<Config>,mut stream: R,handler: H,) -> Result<H, anyhow::Error>whereR: AsyncRead + AsyncWrite + Unpin,{let mut handler = Some(handler);let delay = config.connection_timeout;// Writing SSH id.let mut decomp = CryptoVec::new();let mut write_buffer = SSHBuffer::new();write_buffer.send_ssh_id(config.as_ref().server_id.as_bytes());stream.write_all(&write_buffer.buffer[..]).await?;// Reading SSH id and allocating a session.let mut stream = SshRead::new(&mut stream);let common = read_ssh_id(config, &mut stream).await?;let (sender, receiver) = tokio::sync::mpsc::channel(10);let mut session = Session {target_window_size: common.config.window_size,common,receiver,sender: server::session::Handle { sender },};session.flush()?;stream.write_all(&session.common.write_buffer.buffer).await?;session.common.write_buffer.buffer.clear();let mut buffer = SSHBuffer::new();while !session.common.disconnected {tokio::select! {_ = cipher::read(&mut stream, &mut buffer, &session.common.cipher) => {if buffer.buffer.len() < 5 {break}let buf = if let Some(ref mut enc) = session.common.encrypted {let d = enc.decompress.decompress(&buffer.buffer[5..],&mut decomp,);if let Ok(buf) = d {buf} else {debug!("err = {:?}", d);break}} else {&buffer.buffer[5..]};if buf.is_empty() {continue}if buf[0] == crate::msg::DISCONNECT {debug!("break");break;} else if buf[0] <= 4 {continue;}match reply(session, &mut handler, &buf[..]).await {Ok(s) => session = s,Err(e) => {error!("{:?}", e);return Err(e)}}}_ = timeout(delay) => {debug!("timeout");break},msg = session.receiver.recv(), if !session.is_rekeying() => {match msg {Some((id, ChannelMsg::Data { data })) => {session.data(id, data);}Some((id, ChannelMsg::ExtendedData { ext, data })) => {session.extended_data(id, ext, data);}Some((id, ChannelMsg::Eof)) => {session.eof(id);}Some((id, ChannelMsg::XonXoff { client_can_do })) => {session.xon_xoff_request(id, client_can_do);}Some((id, ChannelMsg::ExitStatus { exit_status })) => {session.exit_status_request(id, exit_status);}Some((id, ChannelMsg::ExitSignal { signal_name, core_dumped, error_message, lang_tag })) => {session.exit_signal_request(id, signal_name, core_dumped, &error_message, &lang_tag);}Some((id, ChannelMsg::WindowAdjusted { new_size })) => {debug!("window adjusted to {:?} for channel {:?}", new_size, id);}Some((id, ChannelMsg::Success)) => {debug!("channel success {:?}", id);}None => {debug!("session.receiver: received None");}}}}session.flush()?;stream.write_all(&session.common.write_buffer.buffer).await?;buffer.buffer.clear();session.common.write_buffer.buffer.clear();}debug!("disconnected");// Shutdownstream.shutdown().await?;buffer.buffer.clear();while cipher::read(&mut stream, &mut buffer, &session.common.cipher).await? != 0 {buffer.buffer.clear();}Ok(handler.unwrap())}async fn read_ssh_id<R: AsyncRead + Unpin>(config: Arc<Config>,read: &mut SshRead<R>,) -> Result<CommonSession<Arc<Config>>, anyhow::Error> {let sshid = if let Some(t) = config.connection_timeout {tokio::time::timeout(t, read.read_ssh_id()).await??} else {read.read_ssh_id().await?};let mut exchange = Exchange::new();exchange.client_id.extend(sshid);// Preparing the responseexchange.server_id.extend(config.as_ref().server_id.as_bytes());let mut kexinit = KexInit {exchange: exchange,algo: None,sent: false,session_id: None,};let cipher = Arc::new(cipher::CLEAR_PAIR);let mut write_buffer = SSHBuffer::new();kexinit.server_write(config.as_ref(), cipher.as_ref(), &mut write_buffer)?;Ok(CommonSession {write_buffer,kex: Some(Kex::KexInit(kexinit)),auth_user: String::new(),auth_method: None, // Client only.cipher,encrypted: None,config: config,wants_reply: false,disconnected: false,buffer: CryptoVec::new(),})}async fn reply<H: Handler>(mut session: Session,handler: &mut Option<H>,buf: &[u8],) -> Result<Session, anyhow::Error> {// Handle key exchange/re-exchange.if session.common.encrypted.is_none() {match session.common.kex.take() {Some(Kex::KexInit(kexinit)) => {if kexinit.algo.is_some() || buf[0] == msg::KEXINIT {session.common.kex = Some(kexinit.server_parse(session.common.config.as_ref(),&session.common.cipher,&buf,&mut session.common.write_buffer,)?);return Ok(session);} else {// Else, i.e. if the other side has not started// the key exchange, process its packets by simple// not returning.session.common.kex = Some(Kex::KexInit(kexinit))}}Some(Kex::KexDh(kexdh)) => {session.common.kex = Some(kexdh.parse(session.common.config.as_ref(),&session.common.cipher,buf,&mut session.common.write_buffer,)?);return Ok(session);}Some(Kex::NewKeys(newkeys)) => {if buf[0] != msg::NEWKEYS {return Err(Error::Kex.into());}// Ok, NEWKEYS received, now encrypted.session.common.encrypted(EncryptedState::WaitingServiceRequest {sent: false,accepted: false,},newkeys,);return Ok(session);}Some(kex) => {session.common.kex = Some(kex);return Ok(session);}None => {}}Ok(session)} else {Ok(session.server_read_encrypted(handler, buf).await?)}}
use super::*;use crate::cipher::CipherPair;use crate::key::PubKey;use crate::negotiation::Select;use crate::{kex, msg, negotiation};use std::cell::RefCell;use thrussh_keys::encoding::{Encoding, Reader};thread_local! {static HASH_BUF: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());}impl KexInit {pub fn server_parse(mut self,config: &Config,cipher: &CipherPair,buf: &[u8],write_buffer: &mut SSHBuffer,) -> Result<Kex, anyhow::Error> {if buf[0] == msg::KEXINIT {let algo = {// read algorithms from packet.self.exchange.client_kex_init.extend(buf);super::negotiation::Server::read_kex(buf, &config.preferred)?};if !self.sent {self.server_write(config, cipher, write_buffer)?}let mut key = 0;while key < config.keys.len() && config.keys[key].name() != algo.key.as_ref() {key += 1}let next_kex = if key < config.keys.len() {Kex::KexDh(KexDh {exchange: self.exchange,key: key,names: algo,session_id: self.session_id,})} else {return Err(Error::UnknownKey.into());};Ok(next_kex)} else {Ok(Kex::KexInit(self))}}pub fn server_write(&mut self,config: &Config,cipher: &CipherPair,write_buffer: &mut SSHBuffer,) -> Result<(), anyhow::Error> {self.exchange.server_kex_init.clear();negotiation::write_kex(&config.preferred, &mut self.exchange.server_kex_init)?;debug!("server kex init: {:?}", &self.exchange.server_kex_init[..]);self.sent = true;cipher.write(&self.exchange.server_kex_init, write_buffer);Ok(())}}impl KexDh {pub fn parse(mut self,config: &Config,cipher: &CipherPair,buf: &[u8],write_buffer: &mut SSHBuffer,) -> Result<Kex, anyhow::Error> {if self.names.ignore_guessed {// If we need to ignore this packet.self.names.ignore_guessed = false;Ok(Kex::KexDh(self))} else {// Else, process it.assert!(buf[0] == msg::KEX_ECDH_INIT);let mut r = buf.reader(1);self.exchange.client_ephemeral.extend(r.read_string()?);let kex = kex::Algorithm::server_dh(self.names.kex, &mut self.exchange, buf)?;// Then, we fill the write buffer right away, so that we// can output it immediately when the time comes.let kexdhdone = KexDhDone {exchange: self.exchange,kex: kex,key: self.key,names: self.names,session_id: self.session_id,};let hash: Result<openssl::hash::DigestBytes, anyhow::Error> = HASH_BUF.with(|buffer| {let mut buffer = buffer.borrow_mut();buffer.clear();debug!("server kexdhdone.exchange = {:?}", kexdhdone.exchange);let hash = kexdhdone.kex.compute_exchange_hash(&config.keys[kexdhdone.key],&kexdhdone.exchange,&mut buffer,)?;debug!("exchange hash: {:?}", hash);buffer.clear();buffer.push(msg::KEX_ECDH_REPLY);config.keys[kexdhdone.key].push_to(&mut buffer);// Server ephemeralbuffer.extend_ssh_string(&kexdhdone.exchange.server_ephemeral);// Hash signaturedebug!("signing with key {:?}", kexdhdone.key);debug!("hash: {:?}", hash);debug!("key: {:?}", config.keys[kexdhdone.key]);config.keys[kexdhdone.key].add_signature(&mut buffer, &hash)?;cipher.write(&buffer, write_buffer);cipher.write(&[msg::NEWKEYS], write_buffer);Ok(hash)});Ok(Kex::NewKeys(kexdhdone.compute_keys(hash?, true)?))}}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use super::super::*;use super::*;use auth::*;use byteorder::{BigEndian, ByteOrder};use msg;use negotiation;use negotiation::Select;use std::cell::RefCell;use thrussh_keys::encoding::{Encoding, Position, Reader};use thrussh_keys::key;use thrussh_keys::key::Verify;use tokio::time::Instant;impl Session {/// Returns false iff a request was rejected.pub(in crate) async fn server_read_encrypted<H: Handler>(mut self,handler: &mut Option<H>,buf: &[u8],) -> Result<Self, anyhow::Error> {let instant = tokio::time::Instant::now() + self.common.config.auth_rejection_time;debug!("read_encrypted");// Either this packet is a KEXINIT, in which case we start a key re-exchange.let mut enc = self.common.encrypted.as_mut().unwrap();if buf[0] == msg::KEXINIT {debug!("Received rekeying request");// If we're not currently rekeying, but `buf` is a rekey requestif let Some(Kex::KexInit(kexinit)) = enc.rekey.take() {enc.rekey = Some(kexinit.server_parse(self.common.config.as_ref(),&self.common.cipher,buf,&mut self.common.write_buffer,)?);self.flush()?;} else if let Some(exchange) = enc.exchange.take() {let kexinit = KexInit::received_rekey(exchange,negotiation::Server::read_kex(buf, &self.common.config.as_ref().preferred)?,&enc.session_id,);enc.rekey = Some(kexinit.server_parse(self.common.config.as_ref(),&mut self.common.cipher,buf,&mut self.common.write_buffer,)?);}return Ok(self);}match enc.rekey.take() {Some(Kex::KexDh(kexdh)) => {enc.rekey = Some(kexdh.parse(self.common.config.as_ref(),&self.common.cipher,buf,&mut self.common.write_buffer,)?);return Ok(self);}Some(Kex::NewKeys(newkeys)) => {if buf[0] != msg::NEWKEYS {return Err(Error::Kex.into());}// Ok, NEWKEYS received, now encrypted.self.common.newkeys(newkeys);return Ok(self);}rek => enc.rekey = rek,}// If we've successfully read a packet.match enc.state {EncryptedState::WaitingServiceRequest {ref mut accepted, ..} if buf[0] == msg::SERVICE_REQUEST => {let mut r = buf.reader(1);let request = r.read_string()?;debug!("request: {:?}", std::str::from_utf8(request));if request == b"ssh-userauth" {let auth_request = server_accept_service(self.common.config.as_ref().auth_banner,self.common.config.as_ref().methods,&mut enc.write,);*accepted = true;enc.state = EncryptedState::WaitingAuthRequest(auth_request);}Ok(self)}EncryptedState::WaitingAuthRequest(_) if buf[0] == msg::USERAUTH_REQUEST => {enc.server_read_auth_request(instant, handler, buf, &mut self.common.auth_user).await?;if let EncryptedState::InitCompression = enc.state {enc.client_compression.init_decompress(&mut enc.decompress);}Ok(self)}EncryptedState::WaitingAuthRequest(ref mut auth)if buf[0] == msg::USERAUTH_INFO_RESPONSE =>{if read_userauth_info_response(instant,handler,&mut enc.write,auth,&mut self.common.auth_user,buf,).await?{if let EncryptedState::InitCompression = enc.state {enc.client_compression.init_decompress(&mut enc.decompress);}}Ok(self)}EncryptedState::InitCompression => {enc.server_compression.init_compress(&mut enc.compress);enc.state = EncryptedState::Authenticated;self.server_read_authenticated(handler, buf).await}EncryptedState::Authenticated => self.server_read_authenticated(handler, buf).await,_ => Ok(self),}}}fn server_accept_service(banner: Option<&str>,methods: MethodSet,buffer: &mut CryptoVec,) -> AuthRequest {push_packet!(buffer, {buffer.push(msg::SERVICE_ACCEPT);buffer.extend_ssh_string(b"ssh-userauth");});if let Some(ref banner) = banner {push_packet!(buffer, {buffer.push(msg::USERAUTH_BANNER);buffer.extend_ssh_string(banner.as_bytes());buffer.extend_ssh_string(b"");})}AuthRequest {methods: methods,partial_success: false, // not used immediately anway.current: None,rejection_count: 0,}}impl Encrypted {/// Returns false iff the request was rejected.async fn server_read_auth_request<H: Handler>(&mut self,until: Instant,handler: &mut Option<H>,buf: &[u8],auth_user: &mut String,) -> Result<(), anyhow::Error> {// https://tools.ietf.org/html/rfc4252#section-5let mut r = buf.reader(1);let user = r.read_string()?;let user = std::str::from_utf8(user)?;let service_name = r.read_string()?;let method = r.read_string()?;debug!("name: {:?} {:?} {:?}",user,std::str::from_utf8(service_name),std::str::from_utf8(method));if service_name == b"ssh-connection" {if method == b"password" {let auth_request = if let EncryptedState::WaitingAuthRequest(ref mut a) = self.state{a} else {unreachable!()};auth_user.clear();auth_user.push_str(user);r.read_byte()?;let password = r.read_string()?;let password = std::str::from_utf8(password)?;let handler_ = handler.take().unwrap();let (handler_, auth) = handler_.auth_password(user, password).await?;*handler = Some(handler_);if let Auth::Accept = auth {server_auth_request_success(&mut self.write);self.state = EncryptedState::InitCompression;} else {auth_user.clear();auth_request.methods = auth_request.methods - MethodSet::PASSWORD;auth_request.partial_success = false;reject_auth_request(until, &mut self.write, auth_request).await;}Ok(())} else if method == b"publickey" {self.server_read_auth_request_pk(until, handler, buf, auth_user, user, r).await} else if method == b"keyboard-interactive" {let auth_request = if let EncryptedState::WaitingAuthRequest(ref mut a) = self.state{a} else {unreachable!()};auth_user.clear();auth_user.push_str(user);let _ = r.read_string()?; // language_tag, deprecated.let submethods = std::str::from_utf8(r.read_string()?)?;debug!("{:?}", submethods);auth_request.current = Some(CurrentRequest::KeyboardInteractive {submethods: submethods.to_string(),});let h = handler.take().unwrap();let (h, auth) = h.auth_keyboard_interactive(user, submethods, None).await?;*handler = Some(h);if reply_userauth_info_response(until, auth_request, &mut self.write, auth).await? {self.state = EncryptedState::InitCompression}Ok(())} else {// Other methods of the base specification are insecure or optional.let auth_request = if let EncryptedState::WaitingAuthRequest(ref mut a) = self.state{a} else {unreachable!()};reject_auth_request(until, &mut self.write, auth_request).await;Ok(())}} else {// Unknown serviceErr(Error::Inconsistent.into())}}}thread_local! {static SIGNATURE_BUFFER: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());}impl Encrypted {async fn server_read_auth_request_pk<'a, H: Handler>(&mut self,until: Instant,handler: &mut Option<H>,buf: &[u8],auth_user: &mut String,user: &str,mut r: Position<'a>,) -> Result<(), anyhow::Error> {let auth_request = if let EncryptedState::WaitingAuthRequest(ref mut a) = self.state {a} else {unreachable!()};let is_real = r.read_byte()?;let pubkey_algo = r.read_string()?;let pubkey_key = r.read_string()?;debug!("algo: {:?}, key: {:?}", pubkey_algo, pubkey_key);match key::PublicKey::parse(pubkey_algo, pubkey_key) {Ok(pubkey) => {debug!("is_real = {:?}", is_real);if is_real != 0 {let pos0 = r.position;let sent_pk_ok = if let Some(CurrentRequest::PublicKey { sent_pk_ok, .. }) =auth_request.current{sent_pk_ok} else {false};let signature = r.read_string()?;debug!("signature = {:?}", signature);let mut s = signature.reader(0);let algo_ = s.read_string()?;debug!("algo_: {:?}", algo_);let sig = s.read_string()?;let init = &buf[0..pos0];let is_valid = if sent_pk_ok && user == auth_user {true} else if auth_user.len() == 0 {auth_user.clear();auth_user.push_str(user);let h = handler.take().unwrap();let (h, auth) = h.auth_publickey(user, &pubkey).await?;*handler = Some(h);auth == Auth::Accept} else {false};if is_valid {let session_id = self.session_id.as_ref();if SIGNATURE_BUFFER.with(|buf| {let mut buf = buf.borrow_mut();buf.clear();buf.extend_ssh_string(session_id);buf.extend(init);// Verify signature.pubkey.verify_client_auth(&buf, sig)}) {debug!("signature verified");server_auth_request_success(&mut self.write);self.state = EncryptedState::InitCompression;} else {debug!("signature wrong");reject_auth_request(until, &mut self.write, auth_request).await;}} else {reject_auth_request(until, &mut self.write, auth_request).await;}Ok(())} else {auth_user.clear();auth_user.push_str(user);let h = handler.take().unwrap();let (h, auth) = h.auth_publickey(user, &pubkey).await?;*handler = Some(h);if auth == Auth::Accept {let mut public_key = CryptoVec::new();public_key.extend(pubkey_key);let mut algo = CryptoVec::new();algo.extend(pubkey_algo);debug!("pubkey_key: {:?}", pubkey_key);push_packet!(self.write, {self.write.push(msg::USERAUTH_PK_OK);self.write.extend_ssh_string(&pubkey_algo);self.write.extend_ssh_string(&pubkey_key);});auth_request.current = Some(CurrentRequest::PublicKey {key: public_key,algo: algo,sent_pk_ok: true,});} else {debug!("signature wrong");auth_request.partial_success = false;auth_user.clear();reject_auth_request(until, &mut self.write, auth_request).await;}Ok(())}}Err(e) => {if let Some(thrussh_keys::Error::CouldNotReadKey) = e.downcast_ref() {reject_auth_request(until, &mut self.write, auth_request).await;Ok(())} else {Err(e)}}}}}async fn reject_auth_request(until: Instant,write: &mut CryptoVec,auth_request: &mut AuthRequest,) {debug!("rejecting {:?}", auth_request);push_packet!(write, {write.push(msg::USERAUTH_FAILURE);write.extend_list(auth_request.methods);write.push(if auth_request.partial_success { 1 } else { 0 });});auth_request.current = None;auth_request.rejection_count += 1;debug!("packet pushed");tokio::time::delay_until(until).await}fn server_auth_request_success(buffer: &mut CryptoVec) {push_packet!(buffer, {buffer.push(msg::USERAUTH_SUCCESS);})}async fn read_userauth_info_response<H: Handler>(until: Instant,handler: &mut Option<H>,write: &mut CryptoVec,auth_request: &mut AuthRequest,user: &mut String,b: &[u8],) -> Result<bool, anyhow::Error> {if let Some(CurrentRequest::KeyboardInteractive { ref submethods }) = auth_request.current {let mut r = b.reader(1);let n = r.read_u32()?;let response = Response { pos: r, n: n };let h = handler.take().unwrap();let (h, auth) = h.auth_keyboard_interactive(user, submethods, Some(response)).await?;*handler = Some(h);reply_userauth_info_response(until, auth_request, write, auth).await} else {reject_auth_request(until, write, auth_request).await;Ok(false)}}async fn reply_userauth_info_response(until: Instant,auth_request: &mut AuthRequest,write: &mut CryptoVec,auth: Auth,) -> Result<bool, anyhow::Error> {match auth {Auth::Accept => {server_auth_request_success(write);Ok(true)}Auth::Reject => {auth_request.partial_success = false;reject_auth_request(until, write, auth_request).await;Ok(false)}Auth::Partial {name,instructions,prompts,} => {push_packet!(write, {write.push(msg::USERAUTH_INFO_REQUEST);write.extend_ssh_string(name.as_bytes());write.extend_ssh_string(instructions.as_bytes());write.extend_ssh_string(b""); // lang, should be emptywrite.push_u32_be(prompts.len() as u32);for &(ref a, b) in prompts.iter() {write.extend_ssh_string(a.as_bytes());write.push(if b { 1 } else { 0 });}});Ok(false)}Auth::UnsupportedMethod => unreachable!(),}}impl Session {async fn server_read_authenticated<H: Handler>(mut self,handler: &mut Option<H>,buf: &[u8],) -> Result<Self, anyhow::Error> {debug!("authenticated buf = {:?}",&buf[..std::cmp::min(buf.len(), 100)]);match buf[0] {msg::CHANNEL_OPEN => self.server_handle_channel_open(handler, buf).await,msg::CHANNEL_CLOSE => {let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);if let Some(ref mut enc) = self.common.encrypted {enc.channels.remove(&channel_num);}debug!("handler.channel_close {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.channel_close(channel_num, self).await?;*handler = Some(h);Ok(s)}msg::CHANNEL_EOF => {let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);debug!("handler.channel_eof {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.channel_eof(channel_num, self).await?;*handler = Some(h);Ok(s)}msg::CHANNEL_EXTENDED_DATA | msg::CHANNEL_DATA => {let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let ext = if buf[0] == msg::CHANNEL_DATA {None} else {Some(r.read_u32()?)};debug!("handler.data {:?} {:?}", ext, channel_num);let data = r.read_string()?;let target = self.target_window_size;let mut h = handler.take().unwrap();if let Some(ref mut enc) = self.common.encrypted {if enc.adjust_window_size(channel_num, data, target) {let window = h.adjust_window(channel_num, self.target_window_size);if window > 0 {self.target_window_size = window}}}self.flush()?;let (h, s) = if let Some(ext) = ext {h.extended_data(channel_num, ext, &data, self).await?} else {h.data(channel_num, &data, self).await?};*handler = Some(h);Ok(s)}msg::CHANNEL_WINDOW_ADJUST => {let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let amount = r.read_u32()?;let mut new_value = 0;if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get_mut(&channel_num) {channel.recipient_window_size += amount;new_value = channel.recipient_window_size;} else {return Err(Error::WrongChannel.into());}}debug!("handler.window_adjusted {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.window_adjusted(channel_num, new_value as usize, self).await?;*handler = Some(h);Ok(s)}msg::CHANNEL_REQUEST => {let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let req_type = r.read_string()?;let wants_reply = r.read_byte()?;if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get_mut(&channel_num) {channel.wants_reply = wants_reply != 0;}}match req_type {b"pty-req" => {let term = std::str::from_utf8(r.read_string()?)?;let col_width = r.read_u32()?;let row_height = r.read_u32()?;let pix_width = r.read_u32()?;let pix_height = r.read_u32()?;let mut modes = [(Pty::TTY_OP_END, 0); 130];let mut i = 0;{let mode_string = r.read_string()?;while 5 * i < mode_string.len() {let code = mode_string[5 * i];if code == 0 {break;}let num = BigEndian::read_u32(&mode_string[5 * i + 1..]);debug!("code = {:?}", code);if let Some(code) = Pty::from_u8(code) {modes[i] = (code, num);} else {info!("pty-req: unknown pty code {:?}", code);}i += 1}}debug!("handler.pty_request {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.pty_request(channel_num,term,col_width,row_height,pix_width,pix_height,&modes[0..i],self,).await?;*handler = Some(h);Ok(s)}b"x11-req" => {let single_connection = r.read_byte()? != 0;let x11_auth_protocol = std::str::from_utf8(r.read_string()?)?;let x11_auth_cookie = std::str::from_utf8(r.read_string()?)?;let x11_screen_number = r.read_u32()?;debug!("handler.x11_request {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.x11_request(channel_num,single_connection,x11_auth_protocol,x11_auth_cookie,x11_screen_number,self,).await?;*handler = Some(h);Ok(s)}b"env" => {let env_variable = std::str::from_utf8(r.read_string()?)?;let env_value = std::str::from_utf8(r.read_string()?)?;debug!("handler.env_request {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.env_request(channel_num, env_variable, env_value, self).await?;*handler = Some(h);Ok(s)}b"shell" => {debug!("handler.shell_request {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.shell_request(channel_num, self).await?;*handler = Some(h);Ok(s)}b"exec" => {let req = r.read_string()?;debug!("handler.exec_request {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.exec_request(channel_num, req, self).await?;*handler = Some(h);debug!("executed");Ok(s)}b"subsystem" => {let name = std::str::from_utf8(r.read_string()?)?;debug!("handler.subsystem_request {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.subsystem_request(channel_num, name, self).await?;*handler = Some(h);Ok(s)}b"window-change" => {let col_width = r.read_u32()?;let row_height = r.read_u32()?;let pix_width = r.read_u32()?;let pix_height = r.read_u32()?;debug!("handler.window_change {:?}", channel_num);let h = handler.take().unwrap();let (h, s) = h.window_change_request(channel_num,col_width,row_height,pix_width,pix_height,self,).await?;*handler = Some(h);Ok(s)}b"signal" => {r.read_byte()?; // should be 0.let signal_name = Sig::from_name(r.read_string()?)?;debug!("handler.signal {:?} {:?}", channel_num, signal_name);let h = handler.take().unwrap();let (h, s) = h.signal(channel_num, signal_name, self).await?;*handler = Some(h);Ok(s)}x => {debug!("{:?}, line {:?} req_type = {:?}",file!(),line!(),std::str::from_utf8(x));if let Some(ref mut enc) = self.common.encrypted {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_FAILURE);});}Ok(self)}}}msg::GLOBAL_REQUEST => {let mut r = buf.reader(1);let req_type = r.read_string()?;self.common.wants_reply = r.read_byte()? != 0;match req_type {b"tcpip-forward" => {let address = std::str::from_utf8(r.read_string()?)?;let port = r.read_u32()?;debug!("handler.tcpip_forward {:?} {:?}", address, port);let h = handler.take().unwrap();let (h, mut s, result) = h.tcpip_forward(address, port, self).await?;*handler = Some(h);if let Some(ref mut enc) = s.common.encrypted {if result {push_packet!(enc.write, enc.write.push(msg::REQUEST_SUCCESS))} else {push_packet!(enc.write, enc.write.push(msg::REQUEST_FAILURE))}}Ok(s)}b"cancel-tcpip-forward" => {let address = std::str::from_utf8(r.read_string()?)?;let port = r.read_u32()?;debug!("handler.cancel_tcpip_forward {:?} {:?}", address, port);let h = handler.take().unwrap();let (h, mut s, result) =h.cancel_tcpip_forward(address, port, self).await?;*handler = Some(h);if let Some(ref mut enc) = s.common.encrypted {if result {push_packet!(enc.write, enc.write.push(msg::REQUEST_SUCCESS))} else {push_packet!(enc.write, enc.write.push(msg::REQUEST_FAILURE))}}Ok(s)}_ => {if let Some(ref mut enc) = self.common.encrypted {push_packet!(enc.write, {enc.write.push(msg::REQUEST_FAILURE);});}Ok(self)}}}m => {debug!("unknown message received: {:?}", m);Ok(self)}}}async fn server_handle_channel_open<H: Handler>(mut self,handler: &mut Option<H>,buf: &[u8],) -> Result<Self, anyhow::Error> {// https://tools.ietf.org/html/rfc4254#section-5.1let mut r = buf.reader(1);let typ = r.read_string()?;let sender = r.read_u32()?;let window = r.read_u32()?;let maxpacket = r.read_u32()?;let sender_channel = if let Some(ref mut enc) = self.common.encrypted {enc.new_channel_id()} else {unreachable!()};let channel = Channel {recipient_channel: sender,// "sender" is the local end, i.e. we're the sender, the remote is the recipient.sender_channel: sender_channel,recipient_window_size: window,sender_window_size: self.common.config.window_size,recipient_maximum_packet_size: maxpacket,sender_maximum_packet_size: self.common.config.maximum_packet_size,confirmed: true,wants_reply: false,pending_data: std::collections::VecDeque::new(),};match typ {b"session" => {self.confirm_channel_open(channel);let h = handler.take().unwrap();let (h, s) = h.channel_open_session(sender_channel, self).await?;*handler = Some(h);Ok(s)}b"x11" => {self.confirm_channel_open(channel);let a = std::str::from_utf8(r.read_string()?)?;let b = r.read_u32()?;let h = handler.take().unwrap();let (h, s) = h.channel_open_x11(sender_channel, a, b, self).await?;*handler = Some(h);Ok(s)}b"direct-tcpip" => {self.confirm_channel_open(channel);let a = std::str::from_utf8(r.read_string()?)?;let b = r.read_u32()?;let c = std::str::from_utf8(r.read_string()?)?;let d = r.read_u32()?;let h = handler.take().unwrap();let (h, s) = h.channel_open_direct_tcpip(sender_channel, a, b, c, d, self).await?;*handler = Some(h);Ok(s)}t => {debug!("unknown channel type: {:?}", t);if let Some(ref mut enc) = self.common.encrypted {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_OPEN_FAILURE);enc.write.push_u32_be(sender);enc.write.push_u32_be(3); // SSH_OPEN_UNKNOWN_CHANNEL_TYPEenc.write.extend_ssh_string(b"Unknown channel type");enc.write.extend_ssh_string(b"en");});}Ok(self)}}}fn confirm_channel_open(&mut self, channel: Channel) {if let Some(ref mut enc) = self.common.encrypted {server_confirm_channel_open(&mut enc.write, &channel, self.common.config.as_ref());enc.channels.insert(channel.sender_channel, channel);}}}fn server_confirm_channel_open(buffer: &mut CryptoVec, channel: &Channel, config: &Config) {push_packet!(buffer, {buffer.push(msg::CHANNEL_OPEN_CONFIRMATION);buffer.push_u32_be(channel.recipient_channel); // remote channel number.buffer.push_u32_be(channel.sender_channel.0); // our channel number.buffer.push_u32_be(config.window_size);buffer.push_u32_be(config.maximum_packet_size);});}
#[allow(non_camel_case_types, missing_docs)]#[derive(Debug, Copy, Clone, PartialEq)]/// Standard pseudo-terminal codes.pub enum Pty {TTY_OP_END = 0,VINTR = 1,VQUIT = 2,VERASE = 3,VKILL = 4,VEOF = 5,VEOL = 6,VEOL2 = 7,VSTART = 8,VSTOP = 9,VSUSP = 10,VDSUSP = 11,VREPRINT = 12,VWERASE = 13,VLNEXT = 14,VFLUSH = 15,VSWTCH = 16,VSTATUS = 17,VDISCARD = 18,IGNPAR = 30,PARMRK = 31,INPCK = 32,ISTRIP = 33,INLCR = 34,IGNCR = 35,ICRNL = 36,IUCLC = 37,IXON = 38,IXANY = 39,IXOFF = 40,IMAXBEL = 41,ISIG = 50,ICANON = 51,XCASE = 52,ECHO = 53,ECHOE = 54,ECHOK = 55,ECHONL = 56,NOFLSH = 57,TOSTOP = 58,IEXTEN = 59,ECHOCTL = 60,ECHOKE = 61,PENDIN = 62,OPOST = 70,OLCUC = 71,ONLCR = 72,OCRNL = 73,ONOCR = 74,ONLRET = 75,CS7 = 90,CS8 = 91,PARENB = 92,PARODD = 93,TTY_OP_ISPEED = 128,TTY_OP_OSPEED = 129,}impl Pty {#[doc(hidden)]pub fn from_u8(x: u8) -> Option<Pty> {match x {0 => None,1 => Some(Pty::VINTR),2 => Some(Pty::VQUIT),3 => Some(Pty::VERASE),4 => Some(Pty::VKILL),5 => Some(Pty::VEOF),6 => Some(Pty::VEOL),7 => Some(Pty::VEOL2),8 => Some(Pty::VSTART),9 => Some(Pty::VSTOP),10 => Some(Pty::VSUSP),11 => Some(Pty::VDSUSP),12 => Some(Pty::VREPRINT),13 => Some(Pty::VWERASE),14 => Some(Pty::VLNEXT),15 => Some(Pty::VFLUSH),16 => Some(Pty::VSWTCH),17 => Some(Pty::VSTATUS),18 => Some(Pty::VDISCARD),30 => Some(Pty::IGNPAR),31 => Some(Pty::PARMRK),32 => Some(Pty::INPCK),33 => Some(Pty::ISTRIP),34 => Some(Pty::INLCR),35 => Some(Pty::IGNCR),36 => Some(Pty::ICRNL),37 => Some(Pty::IUCLC),38 => Some(Pty::IXON),39 => Some(Pty::IXANY),40 => Some(Pty::IXOFF),41 => Some(Pty::IMAXBEL),50 => Some(Pty::ISIG),51 => Some(Pty::ICANON),52 => Some(Pty::XCASE),53 => Some(Pty::ECHO),54 => Some(Pty::ECHOE),55 => Some(Pty::ECHOK),56 => Some(Pty::ECHONL),57 => Some(Pty::NOFLSH),58 => Some(Pty::TOSTOP),59 => Some(Pty::IEXTEN),60 => Some(Pty::ECHOCTL),61 => Some(Pty::ECHOKE),62 => Some(Pty::PENDIN),70 => Some(Pty::OPOST),71 => Some(Pty::OLCUC),72 => Some(Pty::ONLCR),73 => Some(Pty::OCRNL),74 => Some(Pty::ONOCR),75 => Some(Pty::ONLRET),90 => Some(Pty::CS7),91 => Some(Pty::CS8),92 => Some(Pty::PARENB),93 => Some(Pty::PARODD),128 => Some(Pty::TTY_OP_ISPEED),129 => Some(Pty::TTY_OP_OSPEED),_ => None,}}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use crate::{cipher, kex, msg, Error};use std::str::from_utf8;use thrussh_keys::key;// use super::mac; // unimplementeduse crate::compression::*;use cryptovec::CryptoVec;use openssl::rand;use thrussh_keys::encoding::{Encoding, Reader};use thrussh_keys::key::{KeyPair, PublicKey};#[derive(Debug)]pub struct Names {pub kex: kex::Name,pub key: key::Name,pub cipher: cipher::Name,pub mac: Option<&'static str>,pub server_compression: Compression,pub client_compression: Compression,pub ignore_guessed: bool,}/// Lists of preferred algorithms. This is normally hard-coded into implementations.#[derive(Debug)]pub struct Preferred {/// Preferred key exchange algorithms.pub kex: &'static [kex::Name],/// Preferred public key algorithms.pub key: &'static [key::Name],/// Preferred symmetric ciphers.pub cipher: &'static [cipher::Name],/// Preferred MAC algorithms.pub mac: &'static [&'static str],/// Preferred compression algorithms.pub compression: &'static [&'static str],}impl Preferred {pub const DEFAULT: Preferred = Preferred {kex: &[kex::CURVE25519],key: &[key::ED25519, key::RSA_SHA2_256, key::RSA_SHA2_512],cipher: &[cipher::chacha20poly1305::NAME],mac: &["none"],compression: &["none", "zlib", "zlib@openssh.com"],};pub const COMPRESSED: Preferred = Preferred {kex: &[kex::CURVE25519],key: &[key::ED25519, key::RSA_SHA2_256, key::RSA_SHA2_512],cipher: &[cipher::chacha20poly1305::NAME],mac: &["none"],compression: &["zlib", "zlib@openssh.com", "none"],};}impl Default for Preferred {fn default() -> Preferred {Preferred::DEFAULT}}/// Named algorithms.pub trait Named {/// The name of this algorithm.fn name(&self) -> &'static str;}impl Named for () {fn name(&self) -> &'static str {""}}use thrussh_keys::key::{ED25519, SSH_RSA};impl Named for PublicKey {fn name(&self) -> &'static str {match self {&PublicKey::Ed25519(_) => ED25519.0,&PublicKey::RSA { .. } => SSH_RSA.0,}}}impl Named for KeyPair {fn name(&self) -> &'static str {match self {&KeyPair::Ed25519 { .. } => ED25519.0,&KeyPair::RSA { ref hash, .. } => hash.name().0,}}}pub trait Select {fn select<S: AsRef<str> + Copy>(a: &[S], b: &[u8]) -> Option<(bool, S)>;fn read_kex(buffer: &[u8], pref: &Preferred) -> Result<Names, anyhow::Error> {let mut r = buffer.reader(17);let kex_string = r.read_string()?;let (kex_both_first, kex_algorithm) = if let Some(x) = Self::select(pref.kex, kex_string) {x} else {debug!("Could not find common kex algorithm, other side only supports {:?}, we only support {:?}",from_utf8(kex_string),pref.kex);return Err(Error::NoCommonKexAlgo.into());};let key_string = r.read_string()?;let (key_both_first, key_algorithm) = if let Some(x) = Self::select(pref.key, key_string) {x} else {debug!("Could not find common key algorithm, other side only supports {:?}, we only support {:?}",from_utf8(key_string),pref.key);return Err(Error::NoCommonKeyAlgo.into());};let cipher_string = r.read_string()?;let cipher = Self::select(pref.cipher, cipher_string);if cipher.is_none() {debug!("Could not find common cipher, other side only supports {:?}, we only support {:?}",from_utf8(cipher_string),pref.cipher);return Err(Error::NoCommonCipher.into());}r.read_string()?; // cipher server-to-client.debug!("kex {}", line!());let mac = Self::select(pref.mac, r.read_string()?);let mac = mac.and_then(|(_, x)| Some(x));r.read_string()?; // mac server-to-client.debug!("kex {}", line!());// client-to-server compression.let client_compression =if let Some((_, c)) = Self::select(pref.compression, r.read_string()?) {Compression::from_string(c)} else {return Err(Error::NoCommonCompression.into());};debug!("kex {}", line!());// server-to-client compression.let server_compression =if let Some((_, c)) = Self::select(pref.compression, r.read_string()?) {Compression::from_string(c)} else {return Err(Error::NoCommonCompression.into());};debug!("client_compression = {:?}", client_compression);r.read_string()?; // languages client-to-serverr.read_string()?; // languages server-to-clientlet follows = r.read_byte()? != 0;match (cipher, mac, follows) {(Some((_, cipher)), mac, fol) => {Ok(Names {kex: kex_algorithm,key: key_algorithm,cipher,mac,client_compression,server_compression,// Ignore the next packet if (1) it follows and (2) it's not the correct guess.ignore_guessed: fol && !(kex_both_first && key_both_first),})}_ => Err(Error::KexInit.into()),}}}pub struct Server;pub struct Client;impl Select for Server {fn select<S: AsRef<str> + Copy>(server_list: &[S], client_list: &[u8]) -> Option<(bool, S)> {let mut both_first_choice = true;for c in client_list.split(|&x| x == b',') {for &s in server_list {if c == s.as_ref().as_bytes() {return Some((both_first_choice, s));}both_first_choice = false}}None}}impl Select for Client {fn select<S: AsRef<str> + Copy>(client_list: &[S], server_list: &[u8]) -> Option<(bool, S)> {let mut both_first_choice = true;for &c in client_list {for s in server_list.split(|&x| x == b',') {if s == c.as_ref().as_bytes() {return Some((both_first_choice, c));}both_first_choice = false}}None}}pub fn write_kex(prefs: &Preferred, buf: &mut CryptoVec) -> Result<(), anyhow::Error> {// buf.clear();buf.push(msg::KEXINIT);let mut cookie = [0; 16];rand::rand_bytes(&mut cookie)?;buf.extend(&cookie); // cookiebuf.extend_list(prefs.kex.iter()); // kex algobuf.extend_list(prefs.key.iter());buf.extend_list(prefs.cipher.iter()); // cipher client to serverbuf.extend_list(prefs.cipher.iter()); // cipher server to clientbuf.extend_list(prefs.mac.iter()); // mac client to serverbuf.extend_list(prefs.mac.iter()); // mac server to clientbuf.extend_list(prefs.compression.iter()); // compress client to serverbuf.extend_list(prefs.compression.iter()); // compress server to clientbuf.write_empty_list(); // languages client to serverbuf.write_empty_list(); // languagesserver to clientbuf.push(0); // doesn't followbuf.extend(&[0, 0, 0, 0]); // reservedOk(())}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//// https://tools.ietf.org/html/rfc4253#section-12pub const DISCONNECT: u8 = 1;#[allow(dead_code)]pub const IGNORE: u8 = 2;#[allow(dead_code)]pub const UNIMPLEMENTED: u8 = 3;#[allow(dead_code)]pub const DEBUG: u8 = 4;pub const SERVICE_REQUEST: u8 = 5;pub const SERVICE_ACCEPT: u8 = 6;pub const KEXINIT: u8 = 20;pub const NEWKEYS: u8 = 21;// http://tools.ietf.org/html/rfc5656#section-7.1pub const KEX_ECDH_INIT: u8 = 30;pub const KEX_ECDH_REPLY: u8 = 31;// https://tools.ietf.org/html/rfc4250#section-4.1.2pub const USERAUTH_REQUEST: u8 = 50;pub const USERAUTH_FAILURE: u8 = 51;pub const USERAUTH_SUCCESS: u8 = 52;pub const USERAUTH_BANNER: u8 = 53;pub const USERAUTH_PK_OK: u8 = 60;// https://tools.ietf.org/html/rfc4256#section-5pub const USERAUTH_INFO_REQUEST: u8 = 60;pub const USERAUTH_INFO_RESPONSE: u8 = 61;// https://tools.ietf.org/html/rfc4254#section-9pub const GLOBAL_REQUEST: u8 = 80;pub const REQUEST_SUCCESS: u8 = 81;pub const REQUEST_FAILURE: u8 = 82;pub const CHANNEL_OPEN: u8 = 90;pub const CHANNEL_OPEN_CONFIRMATION: u8 = 91;pub const CHANNEL_OPEN_FAILURE: u8 = 92;pub const CHANNEL_WINDOW_ADJUST: u8 = 93;pub const CHANNEL_DATA: u8 = 94;pub const CHANNEL_EXTENDED_DATA: u8 = 95;pub const CHANNEL_EOF: u8 = 96;pub const CHANNEL_CLOSE: u8 = 97;pub const CHANNEL_REQUEST: u8 = 98;pub const CHANNEL_SUCCESS: u8 = 99;pub const CHANNEL_FAILURE: u8 = 100;
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.////! Server and client SSH asynchronous library, based on tokio/futures.//!//! The normal way to use this library, both for clients and for//! servers, is by creating *handlers*, i.e. types that implement//! `client::Handler` for clients and `server::Handler` for//! servers.//!//! # Writing servers//!//! In the specific case of servers, a server must implement//! `server::Server`, a trait for creating new `server::Handler`. The//! main type to look at in the `server` module is `Session` (and//! `Config`, of course).//!//! Here is an example server, which forwards input from each client//! to all other clients://!//! ```//! extern crate thrussh;//! extern crate thrussh_keys;//! extern crate futures;//! extern crate tokio;//! use std::sync::{Mutex, Arc};//! use thrussh::*;//! use thrussh::server::{Auth, Session};//! use thrussh_keys::*;//! use std::collections::HashMap;//! use futures::Future;//!//! #[tokio::main]//! async fn main() {//! let client_key = thrussh_keys::key::KeyPair::generate_ed25519().unwrap();//! let client_pubkey = Arc::new(client_key.clone_public_key());//! let mut config = thrussh::server::Config::default();//! config.connection_timeout = Some(std::time::Duration::from_secs(3));//! config.auth_rejection_time = std::time::Duration::from_secs(3);//! config.keys.push(thrussh_keys::key::KeyPair::generate_ed25519().unwrap());//! let config = Arc::new(config);//! let sh = Server{//! client_pubkey,//! clients: Arc::new(Mutex::new(HashMap::new())),//! id: 0//! };//! tokio::time::timeout(//! std::time::Duration::from_secs(1),//! thrussh::server::run(config, "0.0.0.0:2222", sh)//! ).await.unwrap_or(Ok(()));//! }//!//! #[derive(Clone)]//! struct Server {//! client_pubkey: Arc<thrussh_keys::key::PublicKey>,//! clients: Arc<Mutex<HashMap<(usize, ChannelId), thrussh::server::Handle>>>,//! id: usize,//! }//!//! impl server::Server for Server {//! type Handler = Self;//! fn new(&mut self, _: Option<std::net::SocketAddr>) -> Self {//! let s = self.clone();//! self.id += 1;//! s//! }//! }//!//! impl server::Handler for Server {//! type FutureAuth = futures::future::Ready<Result<(Self, server::Auth), anyhow::Error>>;//! type FutureUnit = futures::future::Ready<Result<(Self, Session), anyhow::Error>>;//! type FutureBool = futures::future::Ready<Result<(Self, Session, bool), anyhow::Error>>;//!//! fn finished_auth(mut self, auth: Auth) -> Self::FutureAuth {//! futures::future::ready(Ok((self, auth)))//! }//! fn finished_bool(self, b: bool, s: Session) -> Self::FutureBool {//! futures::future::ready(Ok((self, s, b)))//! }//! fn finished(self, s: Session) -> Self::FutureUnit {//! futures::future::ready(Ok((self, s)))//! }//! fn channel_open_session(self, channel: ChannelId, session: Session) -> Self::FutureUnit {//! {//! let mut clients = self.clients.lock().unwrap();//! clients.insert((self.id, channel), session.handle());//! }//! self.finished(session)//! }//! fn auth_publickey(self, _: &str, _: &key::PublicKey) -> Self::FutureAuth {//! self.finished_auth(server::Auth::Accept)//! }//! fn data(self, channel: ChannelId, data: &[u8], mut session: Session) -> Self::FutureUnit {//! {//! let mut clients = self.clients.lock().unwrap();//! for ((id, channel), ref mut s) in clients.iter_mut() {//! if *id != self.id {//! s.data(*channel, CryptoVec::from_slice(data));//! }//! }//! }//! session.data(channel, CryptoVec::from_slice(data));//! self.finished(session)//! }//! }//! ```//!//! Note the call to `session.handle()`, which allows to keep a handle//! to a client outside the event loop. This feature is internally//! implemented using `futures::sync::mpsc` channels.//!//! Note that this is just a toy server. In particular://!//! - It doesn't handle errors when `s.data` returns an error,//! i.e. when the client has disappeared//!//! - Each new connection increments the `id` field. Even though we//! would need a lot of connections per second for a very long time to//! saturate it, there are probably better ways to handle this to//! avoid collisions.//!//!//! # Implementing clients//!//! Maybe surprisingly, the data types used by Thrussh to implement//! clients are relatively more complicated than for servers. This is//! mostly related to the fact that clients are generally used both in//! a synchronous way (in the case of SSH, we can think of sending a//! shell command), and asynchronously (because the server may send//! unsollicited messages), and hence need to handle multiple//! interfaces.//!//! The important types in the `client` module are `Session` and//! `Connection`. A `Connection` is typically used to send commands to//! the server and wait for responses, and contains a `Session`. The//! `Session` is passed to the `Handler` when the client receives//! data.//!//! ```//!extern crate thrussh;//!extern crate thrussh_keys;//!extern crate futures;//!extern crate tokio;//!extern crate env_logger;//!use std::sync::Arc;//!use thrussh::*;//!use thrussh::server::{Auth, Session};//!use thrussh_keys::*;//!use futures::Future;//!use std::io::Read;//!//!//!struct Client {//!}//!//!impl client::Handler for Client {//! type FutureUnit = futures::future::Ready<Result<(Self, client::Session), anyhow::Error>>;//! type FutureBool = futures::future::Ready<Result<(Self, bool), anyhow::Error>>;//!//! fn finished_bool(self, b: bool) -> Self::FutureBool {//! futures::future::ready(Ok((self, b)))//! }//! fn finished(self, session: client::Session) -> Self::FutureUnit {//! futures::future::ready(Ok((self, session)))//! }//! fn check_server_key(self, server_public_key: &key::PublicKey) -> Self::FutureBool {//! println!("check_server_key: {:?}", server_public_key);//! self.finished_bool(true)//! }//! fn channel_open_confirmation(self, channel: ChannelId, max_packet_size: u32, window_size: u32, session: client::Session) -> Self::FutureUnit {//! println!("channel_open_confirmation: {:?}", channel);//! self.finished(session)//! }//! fn data(self, channel: ChannelId, data: &[u8], session: client::Session) -> Self::FutureUnit {//! println!("data on channel {:?}: {:?}", channel, std::str::from_utf8(data));//! self.finished(session)//! }//!}//!//! #[tokio::main]//! async fn main() {//! let config = thrussh::client::Config::COMPRESSED;//! let config = Arc::new(config);//! let sh = Client{};//!//! let key = thrussh_keys::key::KeyPair::generate_ed25519().unwrap();//! let mut agent = thrussh_keys::agent::client::AgentClient::connect_env().await.unwrap();//! agent.add_identity(&key, &[]).await.unwrap();//! let mut session = thrussh::client::connect(config, "localhost:22", sh).await.unwrap();//! if session.authenticate_future(std::env::var("USER").unwrap(), key.clone_public_key(), agent).await.unwrap().1 {//! let mut channel = session.channel_open_session().await.unwrap();//! channel.data(b"Hello, world!").await.unwrap();//! if let Some(msg) = channel.wait().await {//! println!("{:?}", msg)//! }//! }//! }//! ```//! # Using non-socket IO / writing tunnels//!//! The easy way to implement SSH tunnels, like `ProxyCommand` for//! OpenSSH, is to use the `thrussh-config` crate, and use the//! `Stream::tcp_connect` or `Stream::proxy_command` methods of that//! crate. That crate is a very lightweight layer above Thrussh, only//! implementing for external commands the traits used for sockets.//!//! # The SSH protocol//!//! If we exclude the key exchange and authentication phases, handled//! by Thrussh behind the scenes, the rest of the SSH protocol is//! relatively simple: clients and servers open *channels*, which are//! just integers used to handle multiple requests in parallel in a//! single connection. Once a client has obtained a `ChannelId` by//! calling one the many `channel_open_…` methods of//! `client::Connection`, the client may send exec requests and data//! to the server.//!//! A simple client just asking the server to run one command will//! usually start by calling//! `client::Connection::channel_open_session`, then//! `client::Connection::exec`, then possibly//! `client::Connection::data` a number of times to send data to the//! command's standard input, and finally `Connection::channel_eof`//! and `Connection::channel_close`.//!//! # Design principles//!//! The main goal of this library is conciseness, and reduced size and//! readability of the library's code. Moreover, this library is split//! between Thrussh, which implements the main logic of SSH clients//! and servers, and Thrussh-keys, which implements calls to//! cryptographic primitives.//!//! One non-goal is to implement all possible cryptographic algorithms//! published since the initial release of SSH. Technical debt is//! easily acquired, and we would need a very strong reason to go//! against this principle. If you are designing a system from//! scratch, we urge you to consider recent cryptographic primitives//! such as Ed25519 for public key cryptography, and Chacha20-Poly1305//! for symmetric cryptography and MAC.//!//! # Internal details of the event loop//!//! It might seem a little odd that the read/write methods for server//! or client sessions often return neither `Result` nor//! `Future`. This is because the data sent to the remote side is//! buffered, because it needs to be encrypted first, and encryption//! works on buffers, and for many algorithms, not in place.//!//! Hence, the event loop keeps waiting for incoming packets, reacts//! to them by calling the provided `Handler`, which fills some//! buffers. If the buffers are non-empty, the event loop then sends//! them to the socket, flushes the socket, empties the buffers and//! starts again. In the special case of the server, unsollicited//! messages sent through a `server::Handle` are processed when there//! is no incoming packet to read.//!#[macro_use]extern crate bitflags;#[macro_use]extern crate log;extern crate thrussh_libsodium as sodium;#[macro_use]extern crate thiserror;pub use cryptovec::CryptoVec;mod auth;mod cipher;mod compression;mod kex;mod key;mod msg;mod negotiation;mod ssh_read;mod sshbuffer;pub use negotiation::{Named, Preferred};mod pty;pub use pty::Pty;macro_rules! push_packet {( $buffer:expr, $x:expr ) => {{use byteorder::{BigEndian, ByteOrder};let i0 = $buffer.len();$buffer.extend(b"\0\0\0\0");let x = $x;let i1 = $buffer.len();use std::ops::DerefMut;let buf = $buffer.deref_mut();BigEndian::write_u32(&mut buf[i0..], (i1 - i0 - 4) as u32);x}};}mod session;/// Server side of this library.pub mod server;/// Client side of this library.pub mod client;#[derive(Debug, Error)]/// anyhow::Errors.pub enum Error {/// The key file could not be parsed.#[error("Could not read key")]CouldNotReadKey,/// Unspecified problem with the beginning of key exchange.#[error("Key exchange init failed")]KexInit,/// No common key exchange algorithm.#[error("No common key exchange algorithm")]NoCommonKexAlgo,/// No common signature algorithm.#[error("No common key algorithm")]NoCommonKeyAlgo,/// No common cipher.#[error("No common key cipher")]NoCommonCipher,/// No common compression algorithm.#[error("No common compression algorithm")]NoCommonCompression,/// Invalid SSH version string.#[error("invalid SSH version string")]Version,/// anyhow::Error during key exchange.#[error("Key exchange failed")]Kex,/// Invalid packet authentication code.#[error("Wrong packet authentication code")]PacketAuth,/// The protocol is in an inconsistent state.#[error("Inconsistent state of the protocol")]Inconsistent,/// The client is not yet authenticated.#[error("Not yet authenticated")]NotAuthenticated,/// Index out of bounds.#[error("Index out of bounds")]IndexOutOfBounds,/// Unknown server key.#[error("Unknown server key")]UnknownKey,/// The server provided a wrong signature.#[error("Wrong server signature")]WrongServerSig,/// Message received/sent on unopened channel.#[error("Channel not open")]WrongChannel,/// Disconnected#[error("Disconnected")]Disconnect,/// No home directory found when trying to learn new host key.#[error("No home directory when saving host key")]NoHomeDir,/// Remote key changed, this could mean a man-in-the-middle attack/// is being performed on the connection.#[error("Key changed, line {}", line)]KeyChanged { line: usize },/// Connection closed by the remote side.#[error("Connection closed by the remote side")]HUP,/// Connection timeout.#[error("Connection timeout")]ConnectionTimeout,/// Missing authentication method.#[error("No authentication method")]NoAuthMethod,#[error("Channel send error")]SendError,}/// Since handlers are large, their associated future types must implement this trait to provide reasonable default implementations (basically, rejecting all requests).pub trait FromFinished<T>: futures::Future<Output = Result<T, anyhow::Error>> {/// Turns type `T` into `Self`, a future yielding `T`.fn finished(t: T) -> Self;}impl<T> FromFinished<T> for futures::future::Ready<Result<T, anyhow::Error>> {fn finished(t: T) -> Self {futures::future::ready(Ok(t))}}impl<T: 'static> FromFinished<T>for Box<dyn futures::Future<Output = Result<T, anyhow::Error>> + Unpin>{fn finished(t: T) -> Self {Box::new(futures::future::ready(Ok(t)))}}// mod mac;// use mac::*;// mod compression;/// The number of bytes read/written, and the number of seconds before a key re-exchange is requested.#[derive(Debug, Clone)]pub struct Limits {pub rekey_write_limit: usize,pub rekey_read_limit: usize,pub rekey_time_limit: std::time::Duration,}impl Limits {/// Create a new `Limits`, checking that the given bounds cannot lead to nonce reuse.pub fn new(write_limit: usize, read_limit: usize, time_limit: std::time::Duration) -> Limits {assert!(write_limit <= 1 << 30 && read_limit <= 1 << 30);Limits {rekey_write_limit: write_limit,rekey_read_limit: read_limit,rekey_time_limit: time_limit,}}}impl Default for Limits {fn default() -> Self {// Following the recommendations of// https://tools.ietf.org/html/rfc4253#section-9Limits {rekey_write_limit: 1 << 30, // 1 Gbrekey_read_limit: 1 << 30, // 1 Gbrekey_time_limit: std::time::Duration::from_secs(3600),}}}pub use auth::MethodSet;/// A reason for disconnection.#[allow(missing_docs)] // This should be relatively self-explanatory.#[derive(Debug)]pub enum Disconnect {HostNotAllowedToConnect = 1,ProtocolError = 2,KeyExchangeFailed = 3,#[doc(hidden)]Reserved = 4,MACError = 5,CompressionError = 6,ServiceNotAvailable = 7,ProtocolVersionNotSupported = 8,HostKeyNotVerifiable = 9,ConnectionLost = 10,ByApplication = 11,TooManyConnections = 12,AuthCancelledByUser = 13,NoMoreAuthMethodsAvailable = 14,IllegalUserName = 15,}/// The type of signals that can be sent to a remote process. If you/// plan to use custom signals, read [the/// RFC](https://tools.ietf.org/html/rfc4254#section-6.10) to/// understand the encoding.#[allow(missing_docs)]// This should be relatively self-explanatory.#[derive(Debug, Clone)]pub enum Sig {ABRT,ALRM,FPE,HUP,ILL,INT,KILL,PIPE,QUIT,SEGV,TERM,USR1,Custom(String),}impl Sig {fn name(&self) -> &str {match *self {Sig::ABRT => "ABRT",Sig::ALRM => "ALRM",Sig::FPE => "FPE",Sig::HUP => "HUP",Sig::ILL => "ILL",Sig::INT => "INT",Sig::KILL => "KILL",Sig::PIPE => "PIPE",Sig::QUIT => "QUIT",Sig::SEGV => "SEGV",Sig::TERM => "TERM",Sig::USR1 => "USR1",Sig::Custom(ref c) => c,}}fn from_name(name: &[u8]) -> Result<Sig, anyhow::Error> {match name {b"ABRT" => Ok(Sig::ABRT),b"ALRM" => Ok(Sig::ALRM),b"FPE" => Ok(Sig::FPE),b"HUP" => Ok(Sig::HUP),b"ILL" => Ok(Sig::ILL),b"INT" => Ok(Sig::INT),b"KILL" => Ok(Sig::KILL),b"PIPE" => Ok(Sig::PIPE),b"QUIT" => Ok(Sig::QUIT),b"SEGV" => Ok(Sig::SEGV),b"TERM" => Ok(Sig::TERM),b"USR1" => Ok(Sig::USR1),x => Ok(Sig::Custom(std::str::from_utf8(x)?.to_string())),}}}/// Reason for not being able to open a channel.#[derive(Debug, Copy, Clone, PartialEq)]#[allow(missing_docs)]pub enum ChannelOpenFailure {AdministrativelyProhibited = 1,ConnectFailed = 2,UnknownChannelType = 3,ResourceShortage = 4,}impl ChannelOpenFailure {fn from_u32(x: u32) -> Option<ChannelOpenFailure> {match x {1 => Some(ChannelOpenFailure::AdministrativelyProhibited),2 => Some(ChannelOpenFailure::ConnectFailed),3 => Some(ChannelOpenFailure::UnknownChannelType),4 => Some(ChannelOpenFailure::ResourceShortage),_ => None,}}}#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]/// The identifier of a channel.pub struct ChannelId(u32);/// The parameters of a channel.#[derive(Debug)]pub(crate) struct Channel {recipient_channel: u32,sender_channel: ChannelId,recipient_window_size: u32,sender_window_size: u32,recipient_maximum_packet_size: u32,sender_maximum_packet_size: u32,/// Has the other side confirmed the channel?pub confirmed: bool,wants_reply: bool,pending_data: std::collections::VecDeque<(CryptoVec, Option<u32>, usize)>,}#[derive(Debug)]pub enum ChannelMsg {Data {data: CryptoVec,},ExtendedData {data: CryptoVec,ext: u32,},Eof,XonXoff {client_can_do: bool,},ExitStatus {exit_status: u32,},ExitSignal {signal_name: Sig,core_dumped: bool,error_message: String,lang_tag: String,},WindowAdjusted {new_size: u32,},Success,}#[cfg(test)]mod test_compress {use super::server::{Auth, Session};use super::*;use std::collections::HashMap;use std::sync::{Arc, Mutex};#[tokio::test(threaded_scheduler)]async fn compress_local_test() {test_compress(true).await}#[tokio::test(threaded_scheduler)]async fn compress_test() {test_compress(false).await}async fn test_compress(local: bool) {env_logger::try_init().unwrap_or(());let (client_key, addr) = if local {let client_key = thrussh_keys::key::KeyPair::generate_ed25519().unwrap();let client_pubkey = Arc::new(client_key.clone_public_key());let mut config = super::server::Config::default();config.preferred = super::Preferred::COMPRESSED;config.auth_rejection_time = std::time::Duration::from_secs(0);config.connection_timeout = None; // Some(std::time::Duration::from_secs(3));config.auth_rejection_time = std::time::Duration::from_secs(3);config.keys.push(thrussh_keys::key::KeyPair::generate_ed25519().unwrap());let config = Arc::new(config);let sh = Server {client_pubkey,clients: Arc::new(Mutex::new(HashMap::new())),id: 0,};tokio::spawn(super::server::run(config, "0.0.0.0:2222", sh));std::thread::sleep(std::time::Duration::from_millis(100));(client_key, "127.0.0.1:2222")} else {let client_key = thrussh_keys::load_secret_key("id_ed25519", None).unwrap();(client_key, "127.0.0.1:2222")};let mut config = super::client::Config::default();config.preferred = super::Preferred::COMPRESSED;let config = Arc::new(config);let sh = Client {};let mut session = super::client::connect(config, addr, sh).await.unwrap();debug!("connected");if session.authenticate_publickey(std::env::var("USER").unwrap(), Arc::new(client_key)).await.unwrap(){debug!("authenticated");if let Ok(mut channel) = session.channel_open_session().await {channel.data(&b"Hello, world!"[..]).await.unwrap();if let Some(msg) = channel.wait().await {println!("{:?}", msg)}}}if local {std::thread::sleep(std::time::Duration::from_secs(40));}}#[derive(Clone)]struct Server {client_pubkey: Arc<thrussh_keys::key::PublicKey>,clients: Arc<Mutex<HashMap<(usize, ChannelId), super::server::Handle>>>,id: usize,}impl server::Server for Server {type Handler = Self;fn new(&mut self, _: Option<std::net::SocketAddr>) -> Self {let s = self.clone();self.id += 1;s}}impl server::Handler for Server {type FutureAuth = futures::future::Ready<Result<(Self, server::Auth), anyhow::Error>>;type FutureUnit = futures::future::Ready<Result<(Self, Session), anyhow::Error>>;type FutureBool = futures::future::Ready<Result<(Self, Session, bool), anyhow::Error>>;fn finished_auth(self, auth: Auth) -> Self::FutureAuth {futures::future::ready(Ok((self, auth)))}fn finished_bool(self, b: bool, s: Session) -> Self::FutureBool {futures::future::ready(Ok((self, s, b)))}fn finished(self, s: Session) -> Self::FutureUnit {futures::future::ready(Ok((self, s)))}fn channel_open_session(self, channel: ChannelId, session: Session) -> Self::FutureUnit {{let mut clients = self.clients.lock().unwrap();clients.insert((self.id, channel), session.handle());}self.finished(session)}fn auth_publickey(self, _: &str, _: &thrussh_keys::key::PublicKey) -> Self::FutureAuth {debug!("auth_publickey");self.finished_auth(server::Auth::Accept)}fn data(self, _channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit {debug!("server data = {:?}", std::str::from_utf8(data));self.finished(session)}}struct Client {}impl client::Handler for Client {type FutureUnit = futures::future::Ready<Result<(Self, client::Session), anyhow::Error>>;type FutureBool = futures::future::Ready<Result<(Self, bool), anyhow::Error>>;fn finished_bool(self, b: bool) -> Self::FutureBool {futures::future::ready(Ok((self, b)))}fn finished(self, session: client::Session) -> Self::FutureUnit {futures::future::ready(Ok((self, session)))}fn check_server_key(self,server_public_key: &thrussh_keys::key::PublicKey,) -> Self::FutureBool {println!("check_server_key: {:?}", server_public_key);self.finished_bool(true)}fn channel_open_confirmation(self,channel: ChannelId,_: u32,_: u32,session: client::Session,) -> Self::FutureUnit {println!("channel_open_confirmation: {:?}", channel);self.finished(session)}fn data(self,channel: ChannelId,data: &[u8],session: client::Session,) -> Self::FutureUnit {debug!("client data on channel {:?}: {:?}",channel,std::str::from_utf8(data));self.finished(session)}}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use cryptovec::CryptoVec;use thrussh_keys::encoding::*;use thrussh_keys::key::*;#[doc(hidden)]pub trait PubKey {fn push_to(&self, buffer: &mut CryptoVec);}impl PubKey for PublicKey {fn push_to(&self, buffer: &mut CryptoVec) {match self {&PublicKey::Ed25519(ref public) => {buffer.push_u32_be((ED25519.0.len() + public.key.len() + 8) as u32);buffer.extend_ssh_string(ED25519.0.as_bytes());buffer.extend_ssh_string(&public.key);}&PublicKey::RSA { ref key, .. } => {let rsa = key.0.rsa().unwrap();let e = rsa.e().to_vec();let n = rsa.n().to_vec();buffer.push_u32_be((4 + SSH_RSA.0.len() + mpint_len(&n) + mpint_len(&e)) as u32);buffer.extend_ssh_string(SSH_RSA.0.as_bytes());buffer.extend_ssh_mpint(&e);buffer.extend_ssh_mpint(&n);}}}}impl PubKey for KeyPair {fn push_to(&self, buffer: &mut CryptoVec) {match self {&KeyPair::Ed25519(ref key) => {let public = &key.key[32..];buffer.push_u32_be((ED25519.0.len() + public.len() + 8) as u32);buffer.extend_ssh_string(ED25519.0.as_bytes());buffer.extend_ssh_string(public);}&KeyPair::RSA { ref key, .. } => {let e = key.e().to_vec();let n = key.n().to_vec();buffer.push_u32_be((4 + SSH_RSA.0.len() + mpint_len(&n) + mpint_len(&e)) as u32);buffer.extend_ssh_string(SSH_RSA.0.as_bytes());buffer.extend_ssh_mpint(&e);buffer.extend_ssh_mpint(&n);}}}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use crate::{cipher, key, msg};use byteorder::{BigEndian, ByteOrder};use crate::session::Exchange;use cryptovec::CryptoVec;use openssl;use sodium;use std::cell::RefCell;use thrussh_keys::encoding::Encoding;#[doc(hidden)]pub struct Algorithm {local_secret: Option<sodium::scalarmult::Scalar>,shared_secret: Option<sodium::scalarmult::GroupElement>,}impl std::fmt::Debug for Algorithm {fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {write!(f,"Algorithm {{ local_secret: [hidden], shared_secret: [hidden] }}",)}}#[derive(Debug, PartialEq, Eq, Copy, Clone)]pub struct Name(&'static str);impl AsRef<str> for Name {fn as_ref(&self) -> &str {self.0}}pub const CURVE25519: Name = Name("curve25519-sha256@libssh.org");thread_local! {static KEY_BUF: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());static BUFFER: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());}// We used to support curve "NIST P-256" here, but the security of// that curve is controversial, see// http://safecurves.cr.yp.to/rigid.htmlimpl Algorithm {#[doc(hidden)]pub fn server_dh(_name: Name,exchange: &mut Exchange,payload: &[u8],) -> Result<Algorithm, anyhow::Error> {debug!("server_dh");assert_eq!(payload[0], msg::KEX_ECDH_INIT);let mut client_pubkey = GroupElement([0; 32]);{let pubkey_len = BigEndian::read_u32(&payload[1..]) as usize;client_pubkey.0.clone_from_slice(&payload[5..(5 + pubkey_len)])};debug!("client_pubkey: {:?}", client_pubkey);use openssl::rand::*;use sodium::scalarmult::*;let mut server_secret = Scalar([0; 32]);rand_bytes(&mut server_secret.0)?;let server_pubkey = scalarmult_base(&server_secret);// fill exchange.exchange.server_ephemeral.clear();exchange.server_ephemeral.extend(&server_pubkey.0);let shared = scalarmult(&server_secret, &client_pubkey);Ok(Algorithm {local_secret: None,shared_secret: Some(shared),})}#[doc(hidden)]pub fn client_dh(_name: Name,client_ephemeral: &mut CryptoVec,buf: &mut CryptoVec,) -> Result<Algorithm, anyhow::Error> {use openssl::rand::*;use sodium::scalarmult::*;let mut client_secret = Scalar([0; 32]);rand_bytes(&mut client_secret.0)?;let client_pubkey = scalarmult_base(&client_secret);// fill exchange.client_ephemeral.clear();client_ephemeral.extend(&client_pubkey.0);buf.push(msg::KEX_ECDH_INIT);buf.extend_ssh_string(&client_pubkey.0);Ok(Algorithm {local_secret: Some(client_secret),shared_secret: None,})}pub fn compute_shared_secret(&mut self, remote_pubkey_: &[u8]) -> Result<(), anyhow::Error> {let local_secret = std::mem::replace(&mut self.local_secret, None).unwrap();use sodium::scalarmult::*;let mut remote_pubkey = GroupElement([0; 32]);remote_pubkey.0.clone_from_slice(remote_pubkey_);let shared = scalarmult(&local_secret, &remote_pubkey);self.shared_secret = Some(shared);Ok(())}pub fn compute_exchange_hash<K: key::PubKey>(&self,key: &K,exchange: &Exchange,buffer: &mut CryptoVec,) -> Result<openssl::hash::DigestBytes, anyhow::Error> {// Computing the exchange hash, see page 7 of RFC 5656.buffer.clear();buffer.extend_ssh_string(&exchange.client_id);buffer.extend_ssh_string(&exchange.server_id);buffer.extend_ssh_string(&exchange.client_kex_init);buffer.extend_ssh_string(&exchange.server_kex_init);key.push_to(buffer);buffer.extend_ssh_string(&exchange.client_ephemeral);buffer.extend_ssh_string(&exchange.server_ephemeral);if let Some(ref shared) = self.shared_secret {buffer.extend_ssh_mpint(&shared.0);}use openssl::hash::*;let hash = {let mut hasher = Hasher::new(MessageDigest::sha256())?;hasher.update(&buffer)?;hasher.finish()?};Ok(hash)}pub fn compute_keys(&self,session_id: &openssl::hash::DigestBytes,exchange_hash: &openssl::hash::DigestBytes,cipher: cipher::Name,is_server: bool,) -> Result<super::cipher::CipherPair, anyhow::Error> {let cipher = match cipher {super::cipher::chacha20poly1305::NAME => &super::cipher::chacha20poly1305::CIPHER,_ => unreachable!(),};// https://tools.ietf.org/html/rfc4253#section-7.2BUFFER.with(|buffer| {KEY_BUF.with(|key| {let compute_key = |c, key: &mut CryptoVec, len| -> Result<(), anyhow::Error> {let mut buffer = buffer.borrow_mut();buffer.clear();key.clear();if let Some(ref shared) = self.shared_secret {buffer.extend_ssh_mpint(&shared.0);}buffer.extend(exchange_hash.as_ref());buffer.push(c);buffer.extend(session_id.as_ref());use openssl::hash::*;let hash = {let mut hasher = Hasher::new(MessageDigest::sha256())?;hasher.update(&buffer)?;hasher.finish()?};key.extend(hash.as_ref());while key.len() < len {// extend.buffer.clear();if let Some(ref shared) = self.shared_secret {buffer.extend_ssh_mpint(&shared.0);}buffer.extend(exchange_hash.as_ref());buffer.extend(key);let hash = {let mut hasher = Hasher::new(MessageDigest::sha256())?;hasher.update(&buffer)?;hasher.finish()?};key.extend(&hash.as_ref());}Ok(())};let (local_to_remote, remote_to_local) = if is_server {(b'D', b'C')} else {(b'C', b'D')};let mut key = key.borrow_mut();compute_key(local_to_remote, &mut key, cipher.key_len)?;let local_to_remote = (cipher.make_sealing_cipher)(&key);compute_key(remote_to_local, &mut key, cipher.key_len)?;let remote_to_local = (cipher.make_opening_cipher)(&key);Ok(super::cipher::CipherPair {local_to_remote: local_to_remote,remote_to_local: remote_to_local,})})})}}
#[derive(Debug)]pub enum Compression {None,#[cfg(feature = "flate2")]Zlib,}#[derive(Debug)]pub enum Compress {None,#[cfg(feature = "flate2")]Zlib(flate2::Compress),}#[derive(Debug)]pub enum Decompress {None,#[cfg(feature = "flate2")]Zlib(flate2::Decompress),}#[cfg(feature = "flate2")]impl Compression {pub fn from_string(s: &str) -> Self {if s == "zlib" || s == "zlib@openssh.com" {Compression::Zlib} else {Compression::None}}pub fn init_compress(&self, comp: &mut Compress) {if let Compression::Zlib = *self {if let Compress::Zlib(ref mut c) = *comp {c.reset()} else {*comp = Compress::Zlib(flate2::Compress::new(flate2::Compression::fast(), true))}} else {*comp = Compress::None}}pub fn init_decompress(&self, comp: &mut Decompress) {if let Compression::Zlib = *self {if let Decompress::Zlib(ref mut c) = *comp {c.reset(true)} else {*comp = Decompress::Zlib(flate2::Decompress::new(true))}} else {*comp = Decompress::None}}}#[cfg(not(feature = "flate2"))]impl Compression {pub fn from_string(_: &str) -> Self {Compression::None}pub fn init_compress(&self, _: &mut Compress) {}pub fn init_decompress(&self, _: &mut Decompress) {}}#[cfg(not(feature = "flate2"))]impl Compress {pub fn compress<'a>(&mut self,input: &'a [u8],_: &'a mut cryptovec::CryptoVec,) -> Result<&'a [u8], anyhow::Error> {Ok(input)}}#[cfg(not(feature = "flate2"))]impl Decompress {pub fn decompress<'a>(&mut self,input: &'a [u8],_: &'a mut cryptovec::CryptoVec,) -> Result<&'a [u8], anyhow::Error> {Ok(input)}}#[cfg(feature = "flate2")]impl Compress {pub fn compress<'a>(&mut self,input: &'a [u8],output: &'a mut cryptovec::CryptoVec,) -> Result<&'a [u8], anyhow::Error> {match *self {Compress::None => Ok(input),Compress::Zlib(ref mut z) => {output.clear();let n_in = z.total_in() as usize;let n_out = z.total_out() as usize;output.resize(input.len() + 10);let flush = flate2::FlushCompress::Partial;loop {let n_in_ = z.total_in() as usize - n_in;let n_out_ = z.total_out() as usize - n_out;let c = z.compress(&input[n_in_..], &mut output[n_out_..], flush)?;match c {flate2::Status::BufError => {output.resize(output.len() * 2);}_ => break,}}let n_out_ = z.total_out() as usize - n_out;Ok(&output[..n_out_])}}}}#[cfg(feature = "flate2")]impl Decompress {pub fn decompress<'a>(&mut self,input: &'a [u8],output: &'a mut cryptovec::CryptoVec,) -> Result<&'a [u8], anyhow::Error> {match *self {Decompress::None => Ok(input),Decompress::Zlib(ref mut z) => {output.clear();let n_in = z.total_in() as usize;let n_out = z.total_out() as usize;output.resize(input.len());let flush = flate2::FlushDecompress::None;loop {let n_in_ = z.total_in() as usize - n_in;let n_out_ = z.total_out() as usize - n_out;let d = z.decompress(&input[n_in_..], &mut output[n_out_..], flush);match d? {flate2::Status::Ok => {output.resize(output.len() * 2);}_ => break,}}let n_out_ = z.total_out() as usize - n_out;Ok(&output[..n_out_])}}}}
use super::*;impl Session {pub fn channel_open_session(&mut self) -> Result<ChannelId, anyhow::Error> {let result = if let Some(ref mut enc) = self.common.encrypted {match enc.state {EncryptedState::Authenticated => {let sender_channel = enc.new_channel(self.common.config.window_size,self.common.config.maximum_packet_size,);push_packet!(enc.write, {enc.write.push(msg::CHANNEL_OPEN);enc.write.extend_ssh_string(b"session");// sender channel id.enc.write.push_u32_be(sender_channel.0);// window.enc.write.push_u32_be(self.common.config.as_ref().window_size);// max packet size.enc.write.push_u32_be(self.common.config.as_ref().maximum_packet_size);});sender_channel}_ => return Err(Error::NotAuthenticated.into()),}} else {return Err(Error::Inconsistent.into());};Ok(result)}pub fn channel_open_x11(&mut self,originator_address: &str,originator_port: u32,) -> Result<ChannelId, anyhow::Error> {let result = if let Some(ref mut enc) = self.common.encrypted {match enc.state {EncryptedState::Authenticated => {let sender_channel = enc.new_channel(self.common.config.window_size,self.common.config.maximum_packet_size,);push_packet!(enc.write, {enc.write.push(msg::CHANNEL_OPEN);enc.write.extend_ssh_string(b"x11");// sender channel id.enc.write.push_u32_be(sender_channel.0);// window.enc.write.push_u32_be(self.common.config.as_ref().window_size);// max packet size.enc.write.push_u32_be(self.common.config.as_ref().maximum_packet_size);enc.write.extend_ssh_string(originator_address.as_bytes());enc.write.push_u32_be(originator_port); // sender channel id.});sender_channel}_ => return Err(Error::NotAuthenticated.into()),}} else {return Err(Error::Inconsistent.into());};Ok(result)}pub fn channel_open_direct_tcpip(&mut self,host_to_connect: &str,port_to_connect: u32,originator_address: &str,originator_port: u32,) -> Result<ChannelId, anyhow::Error> {let result = if let Some(ref mut enc) = self.common.encrypted {match enc.state {EncryptedState::Authenticated => {let sender_channel = enc.new_channel(self.common.config.window_size,self.common.config.maximum_packet_size,);push_packet!(enc.write, {enc.write.push(msg::CHANNEL_OPEN);enc.write.extend_ssh_string(b"direct-tcpip");// sender channel id.enc.write.push_u32_be(sender_channel.0);// window.enc.write.push_u32_be(self.common.config.as_ref().window_size);// max packet size.enc.write.push_u32_be(self.common.config.as_ref().maximum_packet_size);enc.write.extend_ssh_string(host_to_connect.as_bytes());enc.write.push_u32_be(port_to_connect); // sender channel id.enc.write.extend_ssh_string(originator_address.as_bytes());enc.write.push_u32_be(originator_port); // sender channel id.});sender_channel}_ => return Err(Error::NotAuthenticated.into()),}} else {return Err(Error::Inconsistent.into());};Ok(result)}pub fn request_pty(&mut self,channel: ChannelId,want_reply: bool,term: &str,col_width: u32,row_height: u32,pix_width: u32,pix_height: u32,terminal_modes: &[(Pty, u32)],) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"pty-req");enc.write.push(if want_reply { 1 } else { 0 });enc.write.extend_ssh_string(term.as_bytes());enc.write.push_u32_be(col_width);enc.write.push_u32_be(row_height);enc.write.push_u32_be(pix_width);enc.write.push_u32_be(pix_height);enc.write.push_u32_be((1 + 5 * terminal_modes.len()) as u32);for &(code, value) in terminal_modes {enc.write.push(code as u8);enc.write.push_u32_be(value)}// 0 code (to terminate the list)enc.write.push(0);});}}}pub fn request_x11(&mut self,channel: ChannelId,want_reply: bool,single_connection: bool,x11_authentication_protocol: &str,x11_authentication_cookie: &str,x11_screen_number: u32,) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"x11-req");enc.write.push(if want_reply { 1 } else { 0 });enc.write.push(if single_connection { 1 } else { 0 });enc.write.extend_ssh_string(x11_authentication_protocol.as_bytes());enc.write.extend_ssh_string(x11_authentication_cookie.as_bytes());enc.write.push_u32_be(x11_screen_number);});}}}pub fn set_env(&mut self,channel: ChannelId,want_reply: bool,variable_name: &str,variable_value: &str,) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"env");enc.write.push(if want_reply { 1 } else { 0 });enc.write.extend_ssh_string(variable_name.as_bytes());enc.write.extend_ssh_string(variable_value.as_bytes());});}}}pub fn request_shell(&mut self, want_reply: bool, channel: ChannelId) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"shell");enc.write.push(if want_reply { 1 } else { 0 });});}}}pub fn exec(&mut self, channel: ChannelId, want_reply: bool, command: &str) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"exec");enc.write.push(if want_reply { 1 } else { 0 });enc.write.extend_ssh_string(command.as_bytes());});return;}}error!("exec");}pub fn signal(&mut self, channel: ChannelId, signal: Sig) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"signal");enc.write.push(0);enc.write.extend_ssh_string(signal.name().as_bytes());});}}}pub fn request_subsystem(&mut self, want_reply: bool, channel: ChannelId, name: &str) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"subsystem");enc.write.push(if want_reply { 1 } else { 0 });enc.write.extend_ssh_string(name.as_bytes());});}}}pub fn window_change(&mut self,channel: ChannelId,col_width: u32,row_height: u32,pix_width: u32,pix_height: u32,) {if let Some(ref mut enc) = self.common.encrypted {if let Some(channel) = enc.channels.get(&channel) {push_packet!(enc.write, {enc.write.push(msg::CHANNEL_REQUEST);enc.write.push_u32_be(channel.recipient_channel);enc.write.extend_ssh_string(b"window-change");enc.write.push(0); // this packet never wants replyenc.write.push_u32_be(col_width);enc.write.push_u32_be(row_height);enc.write.push_u32_be(pix_width);enc.write.push_u32_be(pix_height);});}}}pub fn tcpip_forward(&mut self, want_reply: bool, address: &str, port: u32) {if let Some(ref mut enc) = self.common.encrypted {push_packet!(enc.write, {enc.write.push(msg::GLOBAL_REQUEST);enc.write.extend_ssh_string(b"tcpip-forward");enc.write.push(if want_reply { 1 } else { 0 });enc.write.extend_ssh_string(address.as_bytes());enc.write.push_u32_be(port);});}}pub fn cancel_tcpip_forward(&mut self, want_reply: bool, address: &str, port: u32) {if let Some(ref mut enc) = self.common.encrypted {push_packet!(enc.write, {enc.write.push(msg::GLOBAL_REQUEST);enc.write.extend_ssh_string(b"cancel-tcpip-forward");enc.write.push(if want_reply { 1 } else { 0 });enc.write.extend_ssh_string(address.as_bytes());enc.write.push_u32_be(port);});}}pub fn data(&mut self, channel: ChannelId, data: CryptoVec) {if let Some(ref mut enc) = self.common.encrypted {enc.data(channel, data)} else {unreachable!()}}pub fn eof(&mut self, channel: ChannelId) {if let Some(ref mut enc) = self.common.encrypted {enc.eof(channel)} else {unreachable!()}}pub fn extended_data(&mut self, channel: ChannelId, ext: u32, data: CryptoVec) {if let Some(ref mut enc) = self.common.encrypted {enc.extended_data(channel, ext, data)} else {unreachable!()}}pub fn disconnect(&mut self, reason: Disconnect, description: &str, language_tag: &str) {self.common.disconnect(reason, description, language_tag);}pub fn has_pending_data(&self, channel: ChannelId) -> bool {if let Some(ref enc) = self.common.encrypted {enc.has_pending_data(channel)} else {false}}pub fn sender_window_size(&self, channel: ChannelId) -> usize {if let Some(ref enc) = self.common.encrypted {enc.sender_window_size(channel)} else {0}}}
use futures::task::{Context, Poll};use std::io::{Read, Write};use std::net::SocketAddr;use std::pin::Pin;use std::process::Command;use tokio;use tokio::io::{AsyncRead, AsyncWrite};use tokio::net::TcpStream;/// A type to implement either a TCP socket, or proxying through an external command.pub enum Stream {#[allow(missing_docs)]Child(std::process::Child),#[allow(missing_docs)]Tcp(TcpStream),}impl Stream {/// Connect a direct TCP stream (as opposed to a proxied one).pub async fn tcp_connect(addr: &SocketAddr) -> Result<Stream, tokio::io::Error> {TcpStream::connect(addr).await.map(Stream::Tcp)}/// Connect through a proxy command.pub fn proxy_connect(cmd: &str, args: &[&str]) -> Result<Stream, anyhow::Error> {Ok(Stream::Child(Command::new(cmd).args(args).spawn()?))}}impl AsyncRead for Stream {fn poll_read(mut self: Pin<&mut Self>,cx: &mut Context,buf: &mut [u8],) -> Poll<Result<usize, tokio::io::Error>> {match *self {Stream::Child(ref mut c) => Poll::Ready(c.stdout.as_mut().unwrap().read(buf)),Stream::Tcp(ref mut t) => AsyncRead::poll_read(Pin::new(t), cx, buf),}}}impl AsyncWrite for Stream {fn poll_write(mut self: Pin<&mut Self>,cx: &mut Context,buf: &[u8],) -> Poll<Result<usize, tokio::io::Error>> {match *self {Stream::Child(ref mut c) => Poll::Ready(c.stdin.as_mut().unwrap().write(buf)),Stream::Tcp(ref mut t) => AsyncWrite::poll_write(Pin::new(t), cx, buf),}}fn poll_flush(mut self: Pin<&mut Self>,cx: &mut Context,) -> Poll<Result<(), tokio::io::Error>> {match *self {Stream::Child(_) => Poll::Ready(Ok(())),Stream::Tcp(ref mut t) => AsyncWrite::poll_flush(Pin::new(t), cx),}}fn poll_shutdown(mut self: Pin<&mut Self>,cx: &mut Context,) -> Poll<Result<(), tokio::io::Error>> {match *self {Stream::Child(_) => Poll::Ready(Ok(())),Stream::Tcp(ref mut t) => AsyncWrite::poll_shutdown(Pin::new(t), cx),}}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use crate::auth;use crate::negotiation;use crate::pty::Pty;use crate::session::*;use crate::ssh_read::SshRead;use crate::sshbuffer::*;use crate::{ChannelId, ChannelMsg, ChannelOpenFailure, Disconnect, Limits, Sig};use cryptovec::CryptoVec;use futures::task::{Context, Poll};use futures::Future;use std::cell::RefCell;use std::collections::HashMap;use std::pin::Pin;use std::sync::Arc;use thrussh_keys::encoding::{Encoding, Reader};use thrussh_keys::key;use thrussh_keys::key::parse_public_key;use tokio;use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};use tokio::net::TcpStream;mod kex;use crate::cipher;use crate::{msg, Error};mod encrypted;mod session;use tokio::sync::mpsc::*;pub mod proxy;pub struct Session {common: CommonSession<Arc<Config>>,receiver: Receiver<Msg>,sender: UnboundedSender<Reply>,channels: HashMap<ChannelId, UnboundedSender<OpenChannelMsg>>,target_window_size: u32,}impl Drop for Session {fn drop(&mut self) {debug!("drop session")}}#[derive(Debug)]enum Reply {AuthSuccess,AuthFailure,ChannelOpenFailure,SignRequest {key: thrussh_keys::key::PublicKey,data: CryptoVec,},}#[derive(Debug)]enum Msg {Authenticate {user: String,method: auth::Method,},Signed {data: CryptoVec,},ChannelOpenSession {sender: UnboundedSender<OpenChannelMsg>,},ChannelOpenX11 {originator_address: String,originator_port: u32,sender: UnboundedSender<OpenChannelMsg>,},ChannelOpenDirectTcpIp {host_to_connect: String,port_to_connect: u32,originator_address: String,originator_port: u32,sender: UnboundedSender<OpenChannelMsg>,},TcpIpForward {want_reply: bool,address: String,port: u32,},CancelTcpIpForward {want_reply: bool,address: String,port: u32,},Disconnect {reason: Disconnect,description: String,language_tag: String,},Data {id: ChannelId,data: CryptoVec,},ExtendedData {id: ChannelId,data: CryptoVec,ext: u32,},Eof {id: ChannelId,},RequestPty {id: ChannelId,want_reply: bool,term: String,col_width: u32,row_height: u32,pix_width: u32,pix_height: u32,terminal_modes: Vec<(Pty, u32)>,},RequestShell {id: ChannelId,want_reply: bool,},Exec {id: ChannelId,want_reply: bool,command: String,},Signal {id: ChannelId,signal: Sig,},RequestSubsystem {id: ChannelId,want_reply: bool,name: String,},RequestX11 {id: ChannelId,want_reply: bool,single_connection: bool,x11_authentication_protocol: String,x11_authentication_cookie: String,x11_screen_number: u32,},SetEnv {id: ChannelId,want_reply: bool,variable_name: String,variable_value: String,},WindowChange {id: ChannelId,col_width: u32,row_height: u32,pix_width: u32,pix_height: u32,},}#[derive(Debug)]enum OpenChannelMsg {Open {id: ChannelId,max_packet_size: u32,window_size: u32,},Msg(ChannelMsg),}/// Handle to a session, used to send messages to a client outside of/// the request/response cycle.pub struct Handle {sender: Sender<Msg>,receiver: UnboundedReceiver<Reply>,join: tokio::task::JoinHandle<Result<(), anyhow::Error>>,}impl Drop for Handle {fn drop(&mut self) {debug!("drop handle")}}#[derive(Clone)]pub struct ChannelSender {sender: Sender<Msg>,id: ChannelId,}pub struct Channel {sender: ChannelSender,receiver: UnboundedReceiver<OpenChannelMsg>,max_packet_size: u32,window_size: u32,}impl Handle {pub async fn authenticate_password<U: Into<String>, P: Into<String>>(&mut self,user: U,password: P,) -> Result<bool, anyhow::Error> {let user = user.into();self.sender.send(Msg::Authenticate {user,method: auth::Method::Password {password: password.into(),},}).await.map_err(|_| Error::SendError)?;loop {match self.receiver.recv().await {Some(Reply::AuthSuccess) => return Ok(true),Some(Reply::AuthFailure) => return Ok(false),None => return Ok(false),_ => {}}}}pub async fn authenticate_publickey<U: Into<String>>(&mut self,user: U,key: Arc<key::KeyPair>,) -> Result<bool, anyhow::Error> {let user = user.into();self.sender.send(Msg::Authenticate {user,method: auth::Method::PublicKey { key },}).await.map_err(|_| Error::SendError)?;loop {match self.receiver.recv().await {Some(Reply::AuthSuccess) => return Ok(true),Some(Reply::AuthFailure) => return Ok(false),None => return Ok(false),_ => {}}}}pub async fn authenticate_future<U: Into<String>, S: auth::Signer>(&mut self,user: U,key: key::PublicKey,mut future: S,) -> Result<(S, bool), anyhow::Error> {let user = user.into();self.sender.send(Msg::Authenticate {user,method: auth::Method::FuturePublicKey { key },}).await.map_err(|_| Error::SendError)?;loop {let reply = self.receiver.recv().await;match reply {Some(Reply::AuthSuccess) => return Ok((future, true)),Some(Reply::AuthFailure) => return Ok((future, false)),Some(Reply::SignRequest { key, data }) => {let (f, data) = future.auth_publickey_sign(&key, data).await;future = f;let data = data?;self.sender.send(Msg::Signed { data }).await.map_err(|_| Error::SendError)?;}None => return Ok((future, false)),_ => {}}}}async fn wait_channel_confirmation(&self,mut receiver: UnboundedReceiver<OpenChannelMsg>,) -> Result<Channel, anyhow::Error> {loop {match receiver.recv().await {Some(OpenChannelMsg::Open {id,max_packet_size,window_size,}) => {return Ok(Channel {sender: ChannelSender {sender: self.sender.clone(),id,},receiver,max_packet_size,window_size,});}None => {return Err(Error::Disconnect.into());}msg => {debug!("msg = {:?}", msg);}}}}/// Request a session channel (the most basic type of/// channel). This function returns `Some(..)` immediately if the/// connection is authenticated, but the channel only becomes/// usable when it's confirmed by the server, as indicated by the/// `confirmed` field of the corresponding `Channel`.pub async fn channel_open_session(&mut self) -> Result<Channel, anyhow::Error> {let (sender, receiver) = unbounded_channel();self.sender.send(Msg::ChannelOpenSession { sender }).await.map_err(|_| Error::SendError)?;self.wait_channel_confirmation(receiver).await}/// Request an X11 channel, on which the X11 protocol may be tunneled.pub async fn channel_open_x11<A: Into<String>>(&mut self,originator_address: A,originator_port: u32,) -> Result<Channel, anyhow::Error> {let (sender, receiver) = unbounded_channel();self.sender.send(Msg::ChannelOpenX11 {originator_address: originator_address.into(),originator_port,sender,}).await.map_err(|_| Error::SendError)?;self.wait_channel_confirmation(receiver).await}/// Open a TCP/IP forwarding channel. This is usually done when a/// connection comes to a locally forwarded TCP/IP port. See/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-7). The/// TCP/IP packets can then be tunneled through the channel using/// `.data()`.pub async fn channel_open_direct_tcpip<A: Into<String>, B: Into<String>>(&mut self,host_to_connect: A,port_to_connect: u32,originator_address: B,originator_port: u32,) -> Result<Channel, anyhow::Error> {let (sender, receiver) = unbounded_channel();self.sender.send(Msg::ChannelOpenDirectTcpIp {host_to_connect: host_to_connect.into(),port_to_connect,originator_address: originator_address.into(),originator_port,sender,}).await.map_err(|_| Error::SendError)?;self.wait_channel_confirmation(receiver).await}/// Sends a disconnect message.pub async fn disconnect(&mut self,reason: Disconnect,description: &str,language_tag: &str,) -> Result<(), anyhow::Error> {self.sender.send(Msg::Disconnect {reason,description: description.into(),language_tag: language_tag.into(),}).await.map_err(|_| Error::SendError)?;Ok(())}}impl Channel {pub fn id(&self) -> ChannelId {self.sender.id}/// Returns the min between the maximum packet size and the/// remaining window size in the channel.pub fn writable_packet_size(&self) -> usize {self.max_packet_size.min(self.window_size) as usize}/// Request a pseudo-terminal with the given characteristics.pub async fn request_pty(&mut self,want_reply: bool,term: &str,col_width: u32,row_height: u32,pix_width: u32,pix_height: u32,terminal_modes: &[(Pty, u32)],) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::RequestPty {id: self.sender.id,want_reply,term: term.to_string(),col_width,row_height,pix_width,pix_height,terminal_modes: terminal_modes.to_vec(),}).await.map_err(|_| Error::SendError)?;Ok(())}/// Request a remote shell.pub async fn request_shell(&mut self, want_reply: bool) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::RequestShell {id: self.sender.id,want_reply,}).await.map_err(|_| Error::SendError)?;Ok(())}/// Execute a remote program (will be passed to a shell). This can/// be used to implement scp (by calling a remote scp and/// tunneling to its standard input).pub async fn exec<A: Into<String>>(&mut self,want_reply: bool,command: A,) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::Exec {id: self.sender.id,want_reply,command: command.into(),}).await.map_err(|e| {debug!("e = {:?}", e);Error::SendError})?;Ok(())}/// Signal a remote process.pub async fn signal(&mut self, signal: Sig) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::Signal {id: self.sender.id,signal,}).await.map_err(|_| Error::SendError)?;Ok(())}/// Request the start of a subsystem with the given name.pub async fn request_subsystem<A: Into<String>>(&mut self,want_reply: bool,name: A,) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::RequestSubsystem {id: self.sender.id,want_reply,name: name.into(),}).await.map_err(|_| Error::SendError)?;Ok(())}/// Request the forwarding of a remote port to the client. The/// server will then open forwarding channels (which cause the/// client to call `.channel_open_forwarded_tcpip()`).pub async fn tcpip_forward<A: Into<String>>(&mut self,want_reply: bool,address: A,port: u32,) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::TcpIpForward {want_reply,address: address.into(),port,}).await.map_err(|_| Error::SendError)?;Ok(())}/// Cancel a previous forwarding request.pub async fn cancel_tcpip_forward<A: Into<String>>(&mut self,want_reply: bool,address: A,port: u32,) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::CancelTcpIpForward {want_reply,address: address.into(),port,}).await.map_err(|_| Error::SendError)?;Ok(())}/// Request X11 forwarding through an already opened X11/// channel. See/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-6.3.1)/// for security issues related to cookies.pub async fn request_x11<A: Into<String>, B: Into<String>>(&mut self,want_reply: bool,single_connection: bool,x11_authentication_protocol: A,x11_authentication_cookie: B,x11_screen_number: u32,) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::RequestX11 {id: self.sender.id,want_reply,single_connection,x11_authentication_protocol: x11_authentication_protocol.into(),x11_authentication_cookie: x11_authentication_cookie.into(),x11_screen_number,}).await.map_err(|_| Error::SendError)?;Ok(())}/// Set a remote environment variable.pub async fn set_env<A: Into<String>, B: Into<String>>(&mut self,want_reply: bool,variable_name: A,variable_value: B,) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::SetEnv {id: self.sender.id,want_reply,variable_name: variable_name.into(),variable_value: variable_value.into(),}).await.map_err(|_| Error::SendError)?;Ok(())}/// Inform the server that our window size has changed.pub async fn window_change(&mut self,col_width: u32,row_height: u32,pix_width: u32,pix_height: u32,) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::WindowChange {id: self.sender.id,col_width,row_height,pix_width,pix_height,}).await.map_err(|_| Error::SendError)?;Ok(())}/// Send data to a channel.pub async fn data<R: tokio::io::AsyncReadExt + std::marker::Unpin>(&mut self,data: R,) -> Result<(), anyhow::Error> {self.send_data(None, data).await}/// Send data to a channel. The number of bytes added to the/// "sending pipeline" (to be processed by the event loop) is/// returned.pub async fn extended_data<R: tokio::io::AsyncReadExt + std::marker::Unpin>(&mut self,ext: u32,data: R,) -> Result<(), anyhow::Error> {self.send_data(Some(ext), data).await}async fn send_data<R: tokio::io::AsyncReadExt + std::marker::Unpin>(&mut self,ext: Option<u32>,mut data: R,) -> Result<(), anyhow::Error> {let mut total = 0;loop {// wait for the window to be restored.while self.window_size == 0 {match self.receiver.recv().await {Some(OpenChannelMsg::Msg(ChannelMsg::WindowAdjusted { new_size })) => {self.window_size = new_size;break;}Some(OpenChannelMsg::Msg(msg)) => {debug!("unexpected channel msg: {:?}", msg);}Some(_) => debug!("unexpected channel msg"),None => break,}}debug!("sending data, self.window_size = {:?}, self.max_packet_size = {:?}, total = {:?}",self.window_size, self.max_packet_size, total);let sendable = self.window_size.min(self.max_packet_size) as usize;let mut c = CryptoVec::new_zeroed(sendable);let n = data.read(&mut c[..]).await?;total += n;c.resize(n);self.window_size -= n as u32;self.send_data_packet(ext, c).await?;if n == 0 {break;} else if self.window_size > 0 {continue;}}Ok(())}async fn send_data_packet(&mut self,ext: Option<u32>,data: CryptoVec,) -> Result<(), anyhow::Error> {self.sender.sender.send(if let Some(ext) = ext {Msg::ExtendedData {id: self.sender.id,ext,data,}} else {Msg::Data {id: self.sender.id,data,}}).await.map_err(|e| {error!("{:?}", e);Error::SendError})?;Ok(())}pub async fn eof(&mut self) -> Result<(), anyhow::Error> {self.sender.sender.send(Msg::Eof { id: self.sender.id }).await.map_err(|_| Error::SendError)?;Ok(())}/// Wait for data to come.pub async fn wait(&mut self) -> Option<ChannelMsg> {loop {match self.receiver.recv().await {Some(OpenChannelMsg::Msg(ChannelMsg::WindowAdjusted { new_size })) => {self.window_size += new_size;return Some(ChannelMsg::WindowAdjusted { new_size });}Some(OpenChannelMsg::Msg(msg)) => return Some(msg),None => return None,_ => {}}}}}impl Future for Handle {type Output = Result<(), anyhow::Error>;fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {match Future::poll(Pin::new(&mut self.join), cx) {Poll::Ready(r) => Poll::Ready(match r {Ok(Ok(x)) => Ok(x),Err(e) => Err(e.into()),Ok(Err(e)) => Err(e),}),Poll::Pending => Poll::Pending,}}}use std::net::ToSocketAddrs;pub async fn connect<H: Handler + Send + 'static, T: ToSocketAddrs>(config: Arc<Config>,addr: T,handler: H,) -> Result<Handle, anyhow::Error> {let addr = addr.to_socket_addrs()?.next().unwrap();let socket = TcpStream::connect(addr).await?;connect_stream(config, socket, handler).await}async fn connect_stream<H, R>(config: Arc<Config>,mut stream: R,handler: H,) -> Result<Handle, anyhow::Error>whereH: Handler + Send + 'static,R: AsyncRead + AsyncWrite + Unpin + Send + 'static,{// Writing SSH id.let mut write_buffer = SSHBuffer::new();write_buffer.send_ssh_id(config.as_ref().client_id.as_bytes());stream.write_all(&write_buffer.buffer).await?;// Reading SSH id and allocating a session if correct.let mut stream = SshRead::new(stream);let sshid = stream.read_ssh_id().await?;let (sender, receiver) = channel(10);let (sender2, receiver2) = unbounded_channel();if config.maximum_packet_size > 65535 {error!("Maximum packet size ({:?}) should not larger than a TCP packet (65535)",config.maximum_packet_size);}let mut session = Session {target_window_size: config.window_size,common: CommonSession {write_buffer,kex: None,auth_user: String::new(),auth_method: None, // Client only.cipher: Arc::new(cipher::CLEAR_PAIR),encrypted: None,config,wants_reply: false,disconnected: false,buffer: CryptoVec::new(),},receiver,sender: sender2,channels: HashMap::new(),};session.read_ssh_id(sshid)?;Ok(Handle {sender,receiver: receiver2,join: tokio::spawn(session.run(stream, handler)),})}impl Session {async fn run<H: Handler + Send, R: AsyncRead + AsyncWrite + Unpin + Send>(mut self,mut stream: R,handler: H,) -> Result<(), anyhow::Error> {self.flush()?;if !self.common.write_buffer.buffer.is_empty() {debug!("writing {:?} bytes", self.common.write_buffer.buffer.len());stream.write_all(&self.common.write_buffer.buffer).await?;stream.flush().await?;}self.common.write_buffer.buffer.clear();let mut buffer = SSHBuffer::new();let mut decomp = CryptoVec::new();let mut handler = Some(handler);while !self.common.disconnected {tokio::select! {n = cipher::read(&mut stream, &mut buffer, &self.common.cipher) => {if n.is_err() || buffer.buffer.len() < 5 {break}let buf = if let Some(ref mut enc) = self.common.encrypted {if let Ok(buf) = enc.decompress.decompress(&buffer.buffer[5..],&mut decomp,) {buf} else {break}} else {&buffer.buffer[5..]};if buf.is_empty() {continue}if buf[0] == crate::msg::DISCONNECT {break;} else if buf[0] <= 4 {continue;}self = reply(self, &mut handler, &buf[..]).await?;}msg = self.receiver.recv(), if !self.is_rekeying() => {match msg {Some(Msg::Authenticate { user, method }) => {self.write_auth_request_if_needed(&user, method);}Some(Msg::Signed { .. }) => {},Some(Msg::ChannelOpenSession { sender }) => {let id = self.channel_open_session()?;self.channels.insert(id, sender);}Some(Msg::ChannelOpenX11 { originator_address, originator_port, sender }) => {let id = self.channel_open_x11(&originator_address, originator_port)?;self.channels.insert(id, sender);}Some(Msg::ChannelOpenDirectTcpIp { host_to_connect, port_to_connect, originator_address, originator_port, sender }) => {let id = self.channel_open_direct_tcpip(&host_to_connect, port_to_connect, &originator_address, originator_port)?;self.channels.insert(id, sender);}Some(Msg::TcpIpForward { want_reply, address, port }) => {self.tcpip_forward(want_reply, &address, port)},Some(Msg::CancelTcpIpForward { want_reply, address, port }) => {self.cancel_tcpip_forward(want_reply, &address, port)},Some(Msg::Disconnect { reason, description, language_tag }) => {self.disconnect(reason, &description, &language_tag)},Some(Msg::Data { data, id }) => { self.data(id, data) },Some(Msg::Eof { id }) => { self.eof(id); },Some(Msg::ExtendedData { data, ext, id }) => { self.extended_data(id, ext, data); },Some(Msg::RequestPty { id, want_reply, term, col_width, row_height, pix_width, pix_height, terminal_modes }) => {self.request_pty(id, want_reply, &term, col_width, row_height, pix_width, pix_height, &terminal_modes)},Some(Msg::WindowChange { id, col_width, row_height, pix_width, pix_height }) => {self.window_change(id, col_width, row_height, pix_width, pix_height)},Some(Msg::RequestX11 { id, want_reply, single_connection, x11_authentication_protocol, x11_authentication_cookie, x11_screen_number }) => {self.request_x11(id, want_reply, single_connection, &x11_authentication_protocol, &x11_authentication_cookie, x11_screen_number)},Some(Msg::SetEnv { id, want_reply, variable_name, variable_value }) => {self.set_env(id, want_reply, &variable_name, &variable_value)},Some(Msg::RequestShell { id, want_reply }) => {self.request_shell(want_reply, id)},Some(Msg::Exec { id, want_reply, command }) => {self.exec(id, want_reply, &command)},Some(Msg::Signal { id, signal }) => {self.signal(id, signal)},Some(Msg::RequestSubsystem { id, want_reply, name }) => {self.request_subsystem(want_reply, id, &name)},None => {self.common.disconnected = true;break}}}}self.flush()?;debug!("writing {:?} bytes", self.common.write_buffer.buffer.len());if !self.common.write_buffer.buffer.is_empty() {stream.write_all(&self.common.write_buffer.buffer).await?;stream.flush().await?;}buffer.buffer.clear();self.common.write_buffer.buffer.clear();if let Some(ref mut enc) = self.common.encrypted {if let EncryptedState::InitCompression = enc.state {enc.client_compression.init_compress(&mut enc.compress);enc.state = EncryptedState::Authenticated;}}}debug!("disconnected");if self.common.disconnected {stream.shutdown().await?;// Shutdownbuffer.buffer.clear();while cipher::read(&mut stream, &mut buffer, &self.common.cipher).await? != 0 {buffer.buffer.clear();}}Ok(())}fn is_rekeying(&self) -> bool {if let Some(ref enc) = self.common.encrypted {enc.rekey.is_some()} else {true}}}impl Session {fn read_ssh_id(&mut self, sshid: &[u8]) -> Result<(), anyhow::Error> {// self.read_buffer.bytes += sshid.bytes_read + 2;let mut exchange = Exchange::new();exchange.server_id.extend(sshid);// Preparing the responseexchange.client_id.extend(self.common.config.as_ref().client_id.as_bytes());let mut kexinit = KexInit {exchange: exchange,algo: None,sent: false,session_id: None,};self.common.write_buffer.buffer.clear();kexinit.client_write(self.common.config.as_ref(),&mut self.common.cipher,&mut self.common.write_buffer,)?;self.common.kex = Some(Kex::KexInit(kexinit));Ok(())}/// Flush the temporary cleartext buffer into the encryption/// buffer. This does *not* flush to the socket.fn flush(&mut self) -> Result<(), anyhow::Error> {if let Some(ref mut enc) = self.common.encrypted {if enc.flush(&self.common.config.as_ref().limits,&mut self.common.cipher,&mut self.common.write_buffer,) {info!("Re-exchanging keys");if enc.rekey.is_none() {if let Some(exchange) = std::mem::replace(&mut enc.exchange, None) {let mut kexinit = KexInit::initiate_rekey(exchange, &enc.session_id);kexinit.client_write(&self.common.config.as_ref(),&mut self.common.cipher,&mut self.common.write_buffer,)?;enc.rekey = Some(Kex::KexInit(kexinit))}}}}Ok(())}}thread_local! {static HASH_BUFFER: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());}impl KexDhDone {async fn server_key_check<H: Handler>(mut self,rekey: bool,handler: &mut Option<H>,buf: &[u8],) -> Result<Kex, anyhow::Error> {let mut reader = buf.reader(1);let pubkey = reader.read_string()?; // server public key.let pubkey = parse_public_key(pubkey)?;debug!("server_public_Key: {:?}", pubkey);if !rekey {let h = handler.take().unwrap();let (h, check) = h.check_server_key(&pubkey).await?;*handler = Some(h);if !check {return Err(Error::UnknownKey.into());}}HASH_BUFFER.with(|buffer| {let mut buffer = buffer.borrow_mut();buffer.clear();let hash = {let server_ephemeral = reader.read_string()?;self.exchange.server_ephemeral.extend(server_ephemeral);let signature = reader.read_string()?;self.kex.compute_shared_secret(&self.exchange.server_ephemeral)?;debug!("kexdhdone.exchange = {:?}", self.exchange);let hash = self.kex.compute_exchange_hash(&pubkey, &self.exchange, &mut buffer)?;debug!("exchange hash: {:?}", hash);let signature = {let mut sig_reader = signature.reader(0);let sig_type = sig_reader.read_string()?;debug!("sig_type: {:?}", sig_type);sig_reader.read_string()?};use thrussh_keys::key::Verify;debug!("signature: {:?}", signature);if !pubkey.verify_server_auth(hash.as_ref(), signature) {debug!("wrong server sig");return Err(Error::WrongServerSig.into());}hash};let mut newkeys = self.compute_keys(hash, false)?;newkeys.sent = true;Ok(Kex::NewKeys(newkeys))})}}async fn reply<H: Handler>(mut session: Session,handler: &mut Option<H>,buf: &[u8],) -> Result<Session, anyhow::Error> {match session.common.kex.take() {Some(Kex::KexInit(kexinit)) => {if kexinit.algo.is_some()|| buf[0] == msg::KEXINIT|| session.common.encrypted.is_none(){session.common.kex = Some(Kex::KexDhDone(kexinit.client_parse(session.common.config.as_ref(),&session.common.cipher,buf,&mut session.common.write_buffer,)?));session.flush()?;}Ok(session)}Some(Kex::KexDhDone(mut kexdhdone)) => {if kexdhdone.names.ignore_guessed {kexdhdone.names.ignore_guessed = false;session.common.kex = Some(Kex::KexDhDone(kexdhdone));Ok(session)} else if buf[0] == msg::KEX_ECDH_REPLY {// We've sent ECDH_INIT, waiting for ECDH_REPLYsession.common.kex = Some(kexdhdone.server_key_check(false, handler, buf).await?);session.common.cipher.write(&[msg::NEWKEYS], &mut session.common.write_buffer);session.flush()?;Ok(session)} else {error!("Wrong packet received");Err(Error::Inconsistent.into())}}Some(Kex::NewKeys(newkeys)) => {debug!("newkeys received");if buf[0] != msg::NEWKEYS {return Err(Error::Kex.into());}let is_first_time = session.common.encrypted.is_none();session.common.encrypted(EncryptedState::WaitingServiceRequest {accepted: false,sent: is_first_time,},newkeys,);if is_first_time {debug!("sending ssh-userauth service requset");let p = b"\x05\0\0\0\x0Cssh-userauth";session.common.cipher.write(p, &mut session.common.write_buffer);}// Ok, NEWKEYS received, now encrypted.Ok(session)}Some(kex) => {session.common.kex = Some(kex);Ok(session)}None => session.client_read_encrypted(handler, buf).await,}}/// The configuration of clients.#[derive(Debug)]pub struct Config {/// The client ID string sent at the beginning of the protocol.pub client_id: String,/// The bytes and time limits before key re-exchange.pub limits: Limits,/// The initial size of a channel (used for flow control).pub window_size: u32,/// The maximal size of a single packet.pub maximum_packet_size: u32,/// Lists of preferred algorithms.pub preferred: negotiation::Preferred,/// Time after which the connection is garbage-collected.pub connection_timeout: Option<std::time::Duration>,}impl Default for Config {fn default() -> Config {Config {client_id: format!("SSH-2.0-{}_{}",env!("CARGO_PKG_NAME"),env!("CARGO_PKG_VERSION")),limits: Limits::default(),window_size: 2097152,maximum_packet_size: 32768,preferred: Default::default(),connection_timeout: None,}}}/// A client handler. Note that messages can be received from the/// server at any time during a session.pub trait Handler: Sized {/// A future ultimately resolving into a boolean, which can be/// returned by some parts of this handler.type FutureBool: Future<Output = Result<(Self, bool), anyhow::Error>> + Send;/// A future ultimately resolving into unit, which can be/// returned by some parts of this handler.type FutureUnit: Future<Output = Result<(Self, Session), anyhow::Error>> + Send;/// Convert a `bool` to `Self::FutureBool`. This is used to/// produce the default handlers.fn finished_bool(self, b: bool) -> Self::FutureBool;/// Produce a `Self::FutureUnit`. This is used to produce the/// default handlers.fn finished(self, session: Session) -> Self::FutureUnit;/// Called when the server sends us an authentication banner. This/// is usually meant to be shown to the user, see/// [RFC4252](https://tools.ietf.org/html/rfc4252#section-5.4) for/// more details.////// The returned Boolean is ignored.#[allow(unused_variables)]fn auth_banner(self, banner: &str, session: Session) -> Self::FutureUnit {self.finished(session)}/// Called to check the server's public key. This is a very important/// step to help prevent man-in-the-middle attacks. The default/// implementation rejects all keys.#[allow(unused_variables)]fn check_server_key(self, server_public_key: &key::PublicKey) -> Self::FutureBool {self.finished_bool(false)}/// Called when the server confirmed our request to open a/// channel. A channel can only be written to after receiving this/// message (this library panics otherwise).#[allow(unused_variables)]fn channel_open_confirmation(self,id: ChannelId,max_packet_size: u32,window_size: u32,session: Session,) -> Self::FutureUnit {if let Some(channel) = session.channels.get(&id) {channel.send(OpenChannelMsg::Open {id,max_packet_size,window_size,}).unwrap_or(());} else {error!("no channel for id {:?}", id);}self.finished(session)}/// Called when the server signals success.#[allow(unused_variables)]fn channel_success(self, channel: ChannelId, session: Session) -> Self::FutureUnit {if let Some(chan) = session.channels.get(&channel) {chan.send(OpenChannelMsg::Msg(ChannelMsg::Success)).unwrap_or(())}self.finished(session)}/// Called when the server closes a channel.#[allow(unused_variables)]fn channel_close(self, channel: ChannelId, mut session: Session) -> Self::FutureUnit {session.channels.remove(&channel);self.finished(session)}/// Called when the server sends EOF to a channel.#[allow(unused_variables)]fn channel_eof(self, channel: ChannelId, session: Session) -> Self::FutureUnit {if let Some(chan) = session.channels.get(&channel) {chan.send(OpenChannelMsg::Msg(ChannelMsg::Eof)).unwrap_or(())}self.finished(session)}/// Called when the server rejected our request to open a channel.#[allow(unused_variables)]fn channel_open_failure(self,channel: ChannelId,reason: ChannelOpenFailure,description: &str,language: &str,mut session: Session,) -> Self::FutureUnit {session.channels.remove(&channel);session.sender.send(Reply::ChannelOpenFailure).unwrap_or(());self.finished(session)}/// Called when a new channel is created.#[allow(unused_variables)]fn channel_open_forwarded_tcpip(self,channel: ChannelId,connected_address: &str,connected_port: u32,originator_address: &str,originator_port: u32,session: Session,) -> Self::FutureUnit {self.finished(session)}/// Called when the server sends us data. The `extended_code`/// parameter is a stream identifier, `None` is usually the/// standard output, and `Some(1)` is the standard error. See/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-5.2).#[allow(unused_variables)]fn data(self, channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit {if let Some(chan) = session.channels.get(&channel) {chan.send(OpenChannelMsg::Msg(ChannelMsg::Data {data: CryptoVec::from_slice(data),})).unwrap_or(())}self.finished(session)}/// Called when the server sends us data. The `extended_code`/// parameter is a stream identifier, `None` is usually the/// standard output, and `Some(1)` is the standard error. See/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-5.2).#[allow(unused_variables)]fn extended_data(self,channel: ChannelId,ext: u32,data: &[u8],session: Session,) -> Self::FutureUnit {if let Some(chan) = session.channels.get(&channel) {chan.send(OpenChannelMsg::Msg(ChannelMsg::ExtendedData {ext,data: CryptoVec::from_slice(data),})).unwrap_or(())}self.finished(session)}/// The server informs this client of whether the client may/// perform control-S/control-Q flow control. See/// [RFC4254](https://tools.ietf.org/html/rfc4254#section-6.8).#[allow(unused_variables)]fn xon_xoff(self,channel: ChannelId,client_can_do: bool,session: Session,) -> Self::FutureUnit {if let Some(chan) = session.channels.get(&channel) {chan.send(OpenChannelMsg::Msg(ChannelMsg::XonXoff { client_can_do })).unwrap_or(())}self.finished(session)}/// The remote process has exited, with the given exit status.#[allow(unused_variables)]fn exit_status(self,channel: ChannelId,exit_status: u32,session: Session,) -> Self::FutureUnit {if let Some(chan) = session.channels.get(&channel) {chan.send(OpenChannelMsg::Msg(ChannelMsg::ExitStatus { exit_status })).unwrap_or(())}self.finished(session)}/// The remote process exited upon receiving a signal.#[allow(unused_variables)]fn exit_signal(self,channel: ChannelId,signal_name: Sig,core_dumped: bool,error_message: &str,lang_tag: &str,session: Session,) -> Self::FutureUnit {if let Some(chan) = session.channels.get(&channel) {chan.send(OpenChannelMsg::Msg(ChannelMsg::ExitSignal {signal_name,core_dumped,error_message: error_message.to_string(),lang_tag: lang_tag.to_string(),})).unwrap_or(())}self.finished(session)}/// Called when the network window is adjusted, meaning that we/// can send more bytes. This is useful if this client wants to/// send huge amounts of data, for instance if we have called/// `Session::data` before, and it returned less than the/// full amount of data.#[allow(unused_variables)]fn window_adjusted(self,channel: ChannelId,mut new_size: u32,mut session: Session,) -> Self::FutureUnit {if let Some(ref mut enc) = session.common.encrypted {new_size -= enc.flush_pending(channel) as u32;}if let Some(chan) = session.channels.get(&channel) {chan.send(OpenChannelMsg::Msg(ChannelMsg::WindowAdjusted { new_size })).unwrap_or(())}self.finished(session)}/// Called when this client adjusts the network window. Return the/// next target window and maximum packet size.#[allow(unused_variables)]fn adjust_window(&mut self, channel: ChannelId, window: u32) -> u32 {window}}
use super::*;use crate::cipher::CipherPair;use crate::negotiation::Select;use crate::{negotiation, Error};use crate::kex;impl KexInit {pub fn client_parse(mut self,config: &Config,cipher: &CipherPair,buf: &[u8],write_buffer: &mut SSHBuffer,) -> Result<KexDhDone, anyhow::Error> {debug!("client parse {:?} {:?}", buf.len(), buf);let algo = if self.algo.is_none() {// read algorithms from packet.debug!("extending {:?}", &self.exchange.server_kex_init[..]);self.exchange.server_kex_init.extend(buf);super::negotiation::Client::read_kex(buf, &config.preferred)?} else {return Err(Error::Kex.into());};debug!("algo = {:?}", algo);debug!("write = {:?}", &write_buffer.buffer[..]);if !self.sent {self.client_write(config, cipher, write_buffer)?}// This function is called from the public API.//// In order to simplify the public API, we reuse the// self.exchange.client_kex buffer to send an extra packet,// then truncate that buffer. Without that, we would need an// extra buffer.let i0 = self.exchange.client_kex_init.len();debug!("i0 = {:?}", i0);let kex = kex::Algorithm::client_dh(algo.kex,&mut self.exchange.client_ephemeral,&mut self.exchange.client_kex_init,)?;cipher.write(&self.exchange.client_kex_init[i0..], write_buffer);self.exchange.client_kex_init.resize(i0);debug!("moving to kexdhdone, exchange = {:?}", self.exchange);Ok(KexDhDone {exchange: self.exchange,names: algo,kex: kex,key: 0,session_id: self.session_id,})}pub fn client_write(&mut self,config: &Config,cipher: &CipherPair,write_buffer: &mut SSHBuffer,) -> Result<(), anyhow::Error> {self.exchange.client_kex_init.clear();negotiation::write_kex(&config.preferred, &mut self.exchange.client_kex_init)?;self.sent = true;cipher.write(&self.exchange.client_kex_init, write_buffer);Ok(())}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use super::{Msg, Reply};use crate::auth;use crate::key::PubKey;use crate::msg;use crate::negotiation;use crate::negotiation::Named;use crate::negotiation::Select;use crate::session::*;use crate::{ChannelId, ChannelOpenFailure, Error, Sig};use cryptovec::CryptoVec;use std::cell::RefCell;use thrussh_keys::encoding::{Encoding, Reader};thread_local! {static SIGNATURE_BUFFER: RefCell<CryptoVec> = RefCell::new(CryptoVec::new());}impl super::Session {pub(crate) async fn client_read_encrypted<C: super::Handler>(mut self,client: &mut Option<C>,buf: &[u8],) -> Result<Self, anyhow::Error> {debug!("client_read_encrypted, buf = {:?}",&buf[..buf.len().min(100)]);// Either this packet is a KEXINIT, in which case we start a key re-exchange.if buf[0] == msg::KEXINIT {// Now, if we're encrypted:if let Some(ref mut enc) = self.common.encrypted {// If we're not currently rekeying, but buf is a rekey requestif let Some(Kex::KexInit(kexinit)) = enc.rekey.take() {enc.rekey = Some(Kex::KexDhDone(kexinit.client_parse(self.common.config.as_ref(),&self.common.cipher,buf,&mut self.common.write_buffer,)?));self.flush()?;} else if let Some(exchange) = std::mem::replace(&mut enc.exchange, None) {let kexinit = KexInit::received_rekey(exchange,negotiation::Client::read_kex(buf, &self.common.config.as_ref().preferred)?,&enc.session_id,);enc.rekey = Some(Kex::KexDhDone(kexinit.client_parse(self.common.config.as_ref(),&mut self.common.cipher,buf,&mut self.common.write_buffer,)?));}} else {unreachable!()}return Ok(self);}if let Some(ref mut enc) = self.common.encrypted {match enc.rekey.take() {Some(Kex::KexDhDone(mut kexdhdone)) => {if kexdhdone.names.ignore_guessed {kexdhdone.names.ignore_guessed = false;enc.rekey = Some(Kex::KexDhDone(kexdhdone));return Ok(self);} else if buf[0] == msg::KEX_ECDH_REPLY {// We've sent ECDH_INIT, waiting for ECDH_REPLYenc.rekey = Some(kexdhdone.server_key_check(true, client, buf).await?);self.common.cipher.write(&[msg::NEWKEYS], &mut self.common.write_buffer);self.flush()?;return Ok(self);} else {error!("Wrong packet received");return Err(Error::Inconsistent.into());}}Some(Kex::NewKeys(newkeys)) => {if buf[0] != msg::NEWKEYS {return Err(Error::Kex.into());}self.common.write_buffer.bytes = 0;enc.last_rekey = std::time::Instant::now();// Ok, NEWKEYS received, now encrypted.self.common.newkeys(newkeys);return Ok(self);}rek => enc.rekey = rek,}}// If we've successfully read a packet.debug!("buf = {:?} bytes", buf.len());trace!("buf = {:?}", buf);let mut is_authenticated = false;if let Some(ref mut enc) = self.common.encrypted {match enc.state {EncryptedState::WaitingServiceRequest {ref mut accepted, ..} => {debug!("waiting service request, {:?} {:?}",buf[0],msg::SERVICE_ACCEPT);if buf[0] == msg::SERVICE_ACCEPT {let mut r = buf.reader(1);if r.read_string()? == b"ssh-userauth" {*accepted = true;if let Some(ref meth) = self.common.auth_method {let auth_request = auth::AuthRequest {methods: auth::MethodSet::all(),partial_success: false,current: None,rejection_count: 0,};let len = enc.write.len();if enc.write_auth_request(&self.common.auth_user, meth) {debug!("enc: {:?}", &enc.write[len..]);enc.state = EncryptedState::WaitingAuthRequest(auth_request)}} else {debug!("no auth method")}}} else {debug!("unknown message: {:?}", buf);return Err(Error::Inconsistent.into());}}EncryptedState::WaitingAuthRequest(ref mut auth_request) => {if buf[0] == msg::USERAUTH_SUCCESS {debug!("userauth_success");self.sender.send(Reply::AuthSuccess).map_err(|_| Error::SendError)?;enc.state = EncryptedState::InitCompression;enc.server_compression.init_decompress(&mut enc.decompress);return Ok(self);} else if buf[0] == msg::USERAUTH_BANNER {let mut r = buf.reader(1);let banner = r.read_string()?;if let Ok(banner) = std::str::from_utf8(banner) {let c = client.take().unwrap();let (c, s) = c.auth_banner(banner, self).await?;*client = Some(c);return Ok(s);} else {return Ok(self);}} else if buf[0] == msg::USERAUTH_FAILURE {debug!("userauth_failure");let mut r = buf.reader(1);let remaining_methods = r.read_string()?;debug!("remaining methods {:?}",std::str::from_utf8(remaining_methods));auth_request.methods = auth::MethodSet::empty();for method in remaining_methods.split(|&c| c == b',') {if let Some(m) = auth::MethodSet::from_bytes(method) {auth_request.methods |= m}}let no_more_methods = auth_request.methods.is_empty();self.common.auth_method = None;self.sender.send(Reply::AuthFailure).map_err(|_| Error::SendError)?;// If no other authentication method is allowed by the server, give up.if no_more_methods {return Err(Error::NoAuthMethod.into());}} else if buf[0] == msg::USERAUTH_PK_OK {debug!("userauth_pk_ok");if let Some(auth::CurrentRequest::PublicKey {ref mut sent_pk_ok, ..}) = auth_request.current{*sent_pk_ok = true;}match self.common.auth_method.take() {Some(auth_method @ auth::Method::PublicKey { .. }) => {self.common.buffer.clear();enc.client_send_signature(&self.common.auth_user,&auth_method,&mut self.common.buffer,)?}Some(auth::Method::FuturePublicKey { key }) => {debug!("public key");self.common.buffer.clear();let i = enc.client_make_to_sign(&self.common.auth_user,&key,&mut self.common.buffer,);let len = self.common.buffer.len();let buf =std::mem::replace(&mut self.common.buffer, CryptoVec::new());self.sender.send(Reply::SignRequest { key, data: buf }).map_err(|_| Error::SendError)?;self.common.buffer = loop {match self.receiver.recv().await {Some(Msg::Signed { data }) => break data,_ => {}}};if self.common.buffer.len() != len {// The buffer was modified.push_packet!(enc.write, {enc.write.extend(&self.common.buffer[i..]);})}}_ => {}}} else {debug!("unknown message: {:?}", buf);return Err(Error::Inconsistent.into());}}EncryptedState::InitCompression => unreachable!(),EncryptedState::Authenticated => is_authenticated = true,}}if is_authenticated {self.client_read_authenticated(client, buf).await} else {Ok(self)}}async fn client_read_authenticated<C: super::Handler>(mut self,client: &mut Option<C>,buf: &[u8],) -> Result<Self, anyhow::Error> {match buf[0] {msg::CHANNEL_OPEN_CONFIRMATION => {debug!("channel_open_confirmation");let mut reader = buf.reader(1);let id_send = ChannelId(reader.read_u32()?);let id_recv = reader.read_u32()?;let window = reader.read_u32()?;let max_packet = reader.read_u32()?;if let Some(ref mut enc) = self.common.encrypted {if let Some(parameters) = enc.channels.get_mut(&id_send) {parameters.recipient_channel = id_recv;parameters.recipient_window_size = window;parameters.recipient_maximum_packet_size = max_packet;parameters.confirmed = true;} else {// We've not requested this channel, close connection.return Err(Error::Inconsistent.into());}} else {return Err(Error::Inconsistent.into());};let c = client.take().unwrap();let (c, s) = c.channel_open_confirmation(id_send, max_packet, window, self).await?;*client = Some(c);Ok(s)}msg::CHANNEL_CLOSE => {debug!("channel_close");let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);if let Some(ref mut enc) = self.common.encrypted {enc.channels.remove(&channel_num);}let c = client.take().unwrap();let (c, s) = c.channel_close(channel_num, self).await?;*client = Some(c);Ok(s)}msg::CHANNEL_EOF => {debug!("channel_close");let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let c = client.take().unwrap();let (c, s) = c.channel_eof(channel_num, self).await?;*client = Some(c);Ok(s)}msg::CHANNEL_OPEN_FAILURE => {debug!("channel_open_failure");let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let reason_code = ChannelOpenFailure::from_u32(r.read_u32()?).unwrap();let descr = std::str::from_utf8(r.read_string()?)?;let language = std::str::from_utf8(r.read_string()?)?;if let Some(ref mut enc) = self.common.encrypted {enc.channels.remove(&channel_num);}let c = client.take().unwrap();let (c, s) = c.channel_open_failure(channel_num, reason_code, descr, language, self).await?;*client = Some(c);Ok(s)}msg::CHANNEL_DATA => {debug!("channel_data");let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let data = r.read_string()?;let target = self.common.config.window_size;let mut c = client.take().unwrap();if let Some(ref mut enc) = self.common.encrypted {if enc.adjust_window_size(channel_num, data, target) {let next_window = c.adjust_window(channel_num, self.target_window_size);if next_window > 0 {self.target_window_size = next_window}}}let (c, s) = c.data(channel_num, &data, self).await?;*client = Some(c);Ok(s)}msg::CHANNEL_EXTENDED_DATA => {debug!("channel_extended_data");let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let extended_code = r.read_u32()?;let data = r.read_string()?;let target = self.common.config.window_size;let mut c = client.take().unwrap();if let Some(ref mut enc) = self.common.encrypted {if enc.adjust_window_size(channel_num, data, target) {let next_window = c.adjust_window(channel_num, self.target_window_size);if next_window > 0 {self.target_window_size = next_window}}}let (c, s) = c.extended_data(channel_num, extended_code, &data, self).await?;*client = Some(c);Ok(s)}msg::CHANNEL_REQUEST => {let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let req = r.read_string()?;debug!("channel_request: {:?} {:?}",channel_num,std::str::from_utf8(req));let cl = client.take().unwrap();let (c, s) = match req {b"forwarded_tcpip" => {let a = std::str::from_utf8(r.read_string()?)?;let b = r.read_u32()?;let c = std::str::from_utf8(r.read_string()?)?;let d = r.read_u32()?;cl.channel_open_forwarded_tcpip(channel_num, a, b, c, d, self).await?}b"xon-xoff" => {r.read_byte()?; // should be 0.let client_can_do = r.read_byte()?;cl.xon_xoff(channel_num, client_can_do != 0, self).await?}b"exit-status" => {r.read_byte()?; // should be 0.let exit_status = r.read_u32()?;cl.exit_status(channel_num, exit_status, self).await?}b"exit-signal" => {r.read_byte()?; // should be 0.let signal_name = Sig::from_name(r.read_string()?)?;let core_dumped = r.read_byte()?;let error_message = std::str::from_utf8(r.read_string()?)?;let lang_tag = std::str::from_utf8(r.read_string()?)?;cl.exit_signal(channel_num,signal_name,core_dumped != 0,error_message,lang_tag,self,).await?}_ => {info!("Unknown channel request {:?}", std::str::from_utf8(req));(cl, self)}};*client = Some(c);Ok(s)}msg::CHANNEL_WINDOW_ADJUST => {debug!("channel_window_adjust");let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let amount = r.read_u32()?;let mut new_value = 0;debug!("amount: {:?}", amount);if let Some(ref mut enc) = self.common.encrypted {if let Some(ref mut channel) = enc.channels.get_mut(&channel_num) {channel.recipient_window_size += amount;new_value = channel.recipient_window_size;} else {return Err(Error::WrongChannel.into());}}let c = client.take().unwrap();let (c, s) = c.window_adjusted(channel_num, new_value, self).await?;*client = Some(c);Ok(s)}msg::GLOBAL_REQUEST => {let mut r = buf.reader(1);let req = r.read_string()?;info!("Unhandled global request: {:?}", std::str::from_utf8(req));Ok(self)}msg::CHANNEL_SUCCESS => {let mut r = buf.reader(1);let channel_num = ChannelId(r.read_u32()?);let c = client.take().unwrap();let (c, s) = c.channel_success(channel_num, self).await?;*client = Some(c);Ok(s)}_ => {info!("Unhandled packet: {:?}", buf);Ok(self)}}}pub(crate) fn write_auth_request_if_needed(&mut self, user: &str, meth: auth::Method) -> bool {let mut is_waiting = false;if let Some(ref mut enc) = self.common.encrypted {is_waiting = match enc.state {EncryptedState::WaitingAuthRequest(_) => true,EncryptedState::WaitingServiceRequest {accepted,ref mut sent,} => {debug!("sending ssh-userauth service requset");if !*sent {let p = b"\x05\0\0\0\x0Cssh-userauth";self.common.cipher.write(p, &mut self.common.write_buffer);*sent = true}accepted}EncryptedState::InitCompression | EncryptedState::Authenticated => false,};debug!("write_auth_request_if_needed: is_waiting = {:?}",is_waiting);if is_waiting {enc.write_auth_request(user, &meth);}}self.common.auth_user.clear();self.common.auth_user.push_str(user);self.common.auth_method = Some(meth);is_waiting}}impl Encrypted {fn write_auth_request(&mut self, user: &str, auth_method: &auth::Method) -> bool {// The server is waiting for our USERAUTH_REQUEST.push_packet!(self.write, {self.write.push(msg::USERAUTH_REQUEST);match *auth_method {auth::Method::Password { ref password } => {self.write.extend_ssh_string(user.as_bytes());self.write.extend_ssh_string(b"ssh-connection");self.write.extend_ssh_string(b"password");self.write.push(1);self.write.extend_ssh_string(password.as_bytes());true}auth::Method::PublicKey { ref key } => {self.write.extend_ssh_string(user.as_bytes());self.write.extend_ssh_string(b"ssh-connection");self.write.extend_ssh_string(b"publickey");self.write.push(0); // This is a probedebug!("write_auth_request: {:?}", key.name());self.write.extend_ssh_string(key.name().as_bytes());key.push_to(&mut self.write);true}auth::Method::FuturePublicKey { ref key, .. } => {self.write.extend_ssh_string(user.as_bytes());self.write.extend_ssh_string(b"ssh-connection");self.write.extend_ssh_string(b"publickey");self.write.push(0); // This is a probeself.write.extend_ssh_string(key.name().as_bytes());key.push_to(&mut self.write);true}}})}fn client_make_to_sign<Key: Named + PubKey>(&mut self,user: &str,key: &Key,buffer: &mut CryptoVec,) -> usize {buffer.clear();buffer.extend_ssh_string(self.session_id.as_ref());let i0 = buffer.len();buffer.push(msg::USERAUTH_REQUEST);buffer.extend_ssh_string(user.as_bytes());buffer.extend_ssh_string(b"ssh-connection");buffer.extend_ssh_string(b"publickey");buffer.push(1);buffer.extend_ssh_string(key.name().as_bytes());key.push_to(buffer);i0}fn client_send_signature(&mut self,user: &str,method: &auth::Method,buffer: &mut CryptoVec,) -> Result<(), anyhow::Error> {match method {&auth::Method::PublicKey { ref key } => {let i0 = self.client_make_to_sign(user, key.as_ref(), buffer);// Extend with self-signature.key.add_self_signature(buffer)?;push_packet!(self.write, {self.write.extend(&buffer[i0..]);})}_ => {}}Ok(())}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use crate::sshbuffer::SSHBuffer;use crate::Error;use byteorder::{BigEndian, ByteOrder};use std::num::Wrapping;use tokio::io::AsyncRead;pub mod chacha20poly1305;pub mod clear;use tokio::prelude::*;pub struct Cipher {pub name: Name,pub key_len: usize,pub make_opening_cipher: fn(key: &[u8]) -> OpeningCipher,pub make_sealing_cipher: fn(key: &[u8]) -> SealingCipher,}pub enum OpeningCipher {Clear(clear::Key),Chacha20Poly1305(chacha20poly1305::OpeningKey),}impl<'a> OpeningCipher {fn as_opening_key(&self) -> &dyn OpeningKey {match *self {OpeningCipher::Clear(ref key) => key,OpeningCipher::Chacha20Poly1305(ref key) => key,}}}pub enum SealingCipher {Clear(clear::Key),Chacha20Poly1305(chacha20poly1305::SealingKey),}impl<'a> SealingCipher {fn as_sealing_key(&'a self) -> &'a dyn SealingKey {match *self {SealingCipher::Clear(ref key) => key,SealingCipher::Chacha20Poly1305(ref key) => key,}}}#[derive(Debug, PartialEq, Eq, Copy, Clone)]pub struct Name(&'static str);impl AsRef<str> for Name {fn as_ref(&self) -> &str {self.0}}pub struct CipherPair {pub local_to_remote: SealingCipher,pub remote_to_local: OpeningCipher,}impl std::fmt::Debug for CipherPair {fn fmt(&self, _: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {Ok(())}}pub const CLEAR_PAIR: CipherPair = CipherPair {local_to_remote: SealingCipher::Clear(clear::Key),remote_to_local: OpeningCipher::Clear(clear::Key),};pub trait OpeningKey {fn decrypt_packet_length(&self, seqn: u32, encrypted_packet_length: [u8; 4]) -> [u8; 4];fn tag_len(&self) -> usize;fn open<'a>(&self,seqn: u32,ciphertext_in_plaintext_out: &'a mut [u8],tag: &[u8],) -> Result<&'a [u8], Error>;}pub trait SealingKey {fn padding_length(&self, plaintext: &[u8]) -> usize;fn fill_padding(&self, padding_out: &mut [u8]);fn tag_len(&self) -> usize;fn seal(&self, seqn: u32, plaintext_in_ciphertext_out: &mut [u8], tag_out: &mut [u8]);}pub async fn read<'a, R: AsyncRead + Unpin>(stream: &'a mut R,buffer: &'a mut SSHBuffer,pair: &'a CipherPair,) -> Result<usize, anyhow::Error> {let mut len = [0; 4];stream.read_exact(&mut len).await?;debug!("len = {:?}", len);{let key = pair.remote_to_local.as_opening_key();let seqn = buffer.seqn.0;buffer.buffer.clear();buffer.buffer.extend(&len);let len = key.decrypt_packet_length(seqn, len);let len = BigEndian::read_u32(&len) as usize + key.tag_len();debug!("clear len = {:?}", len);buffer.buffer.resize(len + 4);}stream.read_exact(&mut buffer.buffer[4..]).await?;let key = pair.remote_to_local.as_opening_key();let seqn = buffer.seqn.0;let ciphertext_len = buffer.buffer.len() - key.tag_len();let (ciphertext, tag) = buffer.buffer.split_at_mut(ciphertext_len);let plaintext = key.open(seqn, ciphertext, tag)?;let padding_length = plaintext[0] as usize;debug!("padding_length {:?}", padding_length);let plaintext_end = plaintext.len().checked_sub(padding_length).ok_or(Error::IndexOutOfBounds)?;// Sequence numbers are on 32 bits and wrap.// https://tools.ietf.org/html/rfc4253#section-6.4buffer.seqn += Wrapping(1);buffer.len = 0;// Remove the paddingbuffer.buffer.resize(plaintext_end + 4);Ok(plaintext_end + 4)}impl CipherPair {pub fn write(&self, payload: &[u8], buffer: &mut SSHBuffer) {// https://tools.ietf.org/html/rfc4253#section-6//// The variables `payload`, `packet_length` and `padding_length` refer// to the protocol fields of the same names.let key = self.local_to_remote.as_sealing_key();let padding_length = key.padding_length(payload);debug!("padding length {:?}", padding_length);let packet_length = PADDING_LENGTH_LEN + payload.len() + padding_length;debug!("packet_length {:?}", packet_length);let offset = buffer.buffer.len();// Maximum packet length:// https://tools.ietf.org/html/rfc4253#section-6.1assert!(packet_length <= std::u32::MAX as usize);buffer.buffer.push_u32_be(packet_length as u32);assert!(padding_length <= std::u8::MAX as usize);buffer.buffer.push(padding_length as u8);buffer.buffer.extend(payload);key.fill_padding(buffer.buffer.resize_mut(padding_length));buffer.buffer.resize_mut(key.tag_len());let (plaintext, tag) =buffer.buffer[offset..].split_at_mut(PACKET_LENGTH_LEN + packet_length);key.seal(buffer.seqn.0, plaintext, tag);buffer.bytes += payload.len();// Sequence numbers are on 32 bits and wrap.// https://tools.ietf.org/html/rfc4253#section-6.4buffer.seqn += Wrapping(1);}}pub const PACKET_LENGTH_LEN: usize = 4;const MINIMUM_PACKET_LEN: usize = 16;const PADDING_LENGTH_LEN: usize = 1;
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use crate::Error;#[derive(Debug)]pub struct Key;impl super::OpeningKey for Key {fn decrypt_packet_length(&self, _seqn: u32, packet_length: [u8; 4]) -> [u8; 4] {packet_length}fn tag_len(&self) -> usize {0}fn open<'a>(&self,_seqn: u32,ciphertext_in_plaintext_out: &'a mut [u8],tag: &[u8],) -> Result<&'a [u8], Error> {debug_assert_eq!(tag.len(), 0); // self.tag_len());Ok(&ciphertext_in_plaintext_out[4..])}}impl super::SealingKey for Key {// Cleartext packets (including lengths) must be multiple of 8 in// length.fn padding_length(&self, payload: &[u8]) -> usize {let block_size = 8;let padding_len = block_size - ((5 + payload.len()) % block_size);if padding_len < 4 {padding_len + block_size} else {padding_len}}fn fill_padding(&self, padding_out: &mut [u8]) {// Since the packet is unencrypted anyway, there's no advantage to// randomizing the padding, so avoid possibly leaking extra RNG state// by padding with zeros.for padding_byte in padding_out {*padding_byte = 0;}}fn tag_len(&self) -> usize {0}fn seal(&self, _seqn: u32, _plaintext_in_ciphertext_out: &mut [u8], tag_out: &mut [u8]) {debug_assert_eq!(tag_out.len(), self.tag_len());}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//// http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEADuse super::super::Error;use byteorder::{BigEndian, ByteOrder};use sodium::chacha20::*;pub struct OpeningKey {k1: Key,k2: Key,}pub struct SealingKey {k1: Key,k2: Key,}const TAG_LEN: usize = 16;pub static CIPHER: super::Cipher = super::Cipher {name: NAME,key_len: 64,make_sealing_cipher,make_opening_cipher,};pub const NAME: super::Name = super::Name("chacha20-poly1305@openssh.com");fn make_sealing_cipher(k: &[u8]) -> super::SealingCipher {let mut k1 = Key([0; KEY_BYTES]);let mut k2 = Key([0; KEY_BYTES]);k1.0.clone_from_slice(&k[KEY_BYTES..]);k2.0.clone_from_slice(&k[..KEY_BYTES]);super::SealingCipher::Chacha20Poly1305(SealingKey { k1, k2 })}fn make_opening_cipher(k: &[u8]) -> super::OpeningCipher {let mut k1 = Key([0; KEY_BYTES]);let mut k2 = Key([0; KEY_BYTES]);k1.0.clone_from_slice(&k[KEY_BYTES..]);k2.0.clone_from_slice(&k[..KEY_BYTES]);super::OpeningCipher::Chacha20Poly1305(OpeningKey { k1, k2 })}fn make_counter(sequence_number: u32) -> Nonce {let mut nonce = Nonce([0; NONCE_BYTES]);let i0 = NONCE_BYTES - 4;BigEndian::write_u32(&mut nonce.0[i0..], sequence_number);nonce}impl super::OpeningKey for OpeningKey {fn decrypt_packet_length(&self,sequence_number: u32,mut encrypted_packet_length: [u8; 4],) -> [u8; 4] {let nonce = make_counter(sequence_number);chacha20_xor(&mut encrypted_packet_length, &nonce, &self.k1);encrypted_packet_length}fn tag_len(&self) -> usize {TAG_LEN}fn open<'a>(&self,sequence_number: u32,ciphertext_in_plaintext_out: &'a mut [u8],tag: &[u8],) -> Result<&'a [u8], Error> {let nonce = make_counter(sequence_number);{use sodium::poly1305::*;let mut poly_key = Key([0; 32]);chacha20_xor(&mut poly_key.0, &nonce, &self.k2);// let mut tag_ = Tag([0; 16]);// tag_.0.clone_from_slice(tag);if !poly1305_verify(&tag, ciphertext_in_plaintext_out, &poly_key) {return Err(Error::PacketAuth);}}chacha20_xor_ic(&mut ciphertext_in_plaintext_out[4..], &nonce, 1, &self.k2);Ok(&ciphertext_in_plaintext_out[4..])}}impl super::SealingKey for SealingKey {fn padding_length(&self, payload: &[u8]) -> usize {let block_size = 8;let extra_len = super::PACKET_LENGTH_LEN + super::PADDING_LENGTH_LEN;let padding_len = if payload.len() + extra_len <= super::MINIMUM_PACKET_LEN {super::MINIMUM_PACKET_LEN - payload.len() - super::PADDING_LENGTH_LEN} else {block_size - ((super::PADDING_LENGTH_LEN + payload.len()) % block_size)};if padding_len < super::PACKET_LENGTH_LEN {padding_len + block_size} else {padding_len}}// As explained in "SSH via CTR mode with stateful decryption" in// https://openvpn.net/papers/ssh-security.pdf, the padding doesn't need to// be random because we're doing stateful counter-mode encryption. Use// fixed padding to avoid PRNG overhead.fn fill_padding(&self, padding_out: &mut [u8]) {for padding_byte in padding_out {*padding_byte = 0;}}fn tag_len(&self) -> usize {TAG_LEN}/// Append an encrypted packet with contents `packet_content` at the end of `buffer`.fn seal(&self,sequence_number: u32,plaintext_in_ciphertext_out: &mut [u8],tag_out: &mut [u8],) {let mut nonce = make_counter(sequence_number);{let (a, b) = plaintext_in_ciphertext_out.split_at_mut(4);chacha20_xor(a, &nonce, &self.k1);chacha20_xor_ic(b, &nonce, 1, &self.k2);}nonce.0[0] = 0;use sodium::poly1305::*;let mut poly_key = Key([0; 32]);chacha20_xor(&mut poly_key.0, &nonce, &self.k2);let tag = poly1305_auth(plaintext_in_ciphertext_out, &poly_key);tag_out.clone_from_slice(&tag.0);}}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//use cryptovec::CryptoVec;use std::sync::Arc;use thrussh_keys::encoding;use thrussh_keys::key;use tokio::io::{AsyncRead, AsyncWrite};bitflags! {/// Set of methods, represented by bit flags.pub struct MethodSet: u32 {/// The SSH `none` method (no authentication).const NONE = 1;/// The SSH `password` method (plaintext passwords).const PASSWORD = 2;/// The SSH `publickey` method (sign a challenge sent by the/// server).const PUBLICKEY = 4;/// The SSH `hostbased` method (certain hostnames are allowed/// by the server).const HOSTBASED = 8;/// The SSH `keyboard-interactive` method (answer to a/// challenge, where the "challenge" can be a password prompt,/// a bytestring to sign with a smartcard, or something else).const KEYBOARD_INTERACTIVE = 16;}}macro_rules! iter {( $y:expr, $x:expr ) => {{if $y.contains($x) {$y.remove($x);return Some($x);}}};}impl Iterator for MethodSet {type Item = MethodSet;fn next(&mut self) -> Option<MethodSet> {iter!(self, MethodSet::NONE);iter!(self, MethodSet::PASSWORD);iter!(self, MethodSet::PUBLICKEY);iter!(self, MethodSet::HOSTBASED);iter!(self, MethodSet::KEYBOARD_INTERACTIVE);None}}pub trait Signer: Sized {fn auth_publickey_sign(self,key: &key::PublicKey,to_sign: CryptoVec,) -> std::pin::Pin<Box<dyn futures::Future<Output = (Self, Result<CryptoVec, anyhow::Error>)> + Send>,>;}impl<R: AsyncRead + AsyncWrite + Unpin + Send + 'static> Signerfor thrussh_keys::agent::client::AgentClient<R>{fn auth_publickey_sign(self,key: &key::PublicKey,to_sign: CryptoVec,) -> std::pin::Pin<Box<dyn futures::Future<Output = (Self, Result<CryptoVec, anyhow::Error>)> + Send>,> {let fut = self.sign_request(key, to_sign);futures::FutureExt::boxed(async move { fut.await })}}#[derive(Debug)]pub enum Method {// None,Password { password: String },PublicKey { key: Arc<key::KeyPair> },FuturePublicKey { key: key::PublicKey },// Hostbased,}impl encoding::Bytes for MethodSet {fn bytes(&self) -> &'static [u8] {match *self {MethodSet::NONE => b"none",MethodSet::PASSWORD => b"password",MethodSet::PUBLICKEY => b"publickey",MethodSet::HOSTBASED => b"hostbased",MethodSet::KEYBOARD_INTERACTIVE => b"keyboard-interactive",_ => b"",}}}impl MethodSet {pub(crate) fn from_bytes(b: &[u8]) -> Option<MethodSet> {match b {b"none" => Some(MethodSet::NONE),b"password" => Some(MethodSet::PASSWORD),b"publickey" => Some(MethodSet::PUBLICKEY),b"hostbased" => Some(MethodSet::HOSTBASED),b"keyboard-interactive" => Some(MethodSet::KEYBOARD_INTERACTIVE),_ => None,}}}#[doc(hidden)]#[derive(Debug)]pub struct AuthRequest {pub methods: MethodSet,pub partial_success: bool,pub current: Option<CurrentRequest>,pub rejection_count: usize,}#[doc(hidden)]#[derive(Debug)]pub enum CurrentRequest {PublicKey {key: CryptoVec,algo: CryptoVec,sent_pk_ok: bool,},KeyboardInteractive {submethods: String,},}
[package]name = "thrussh"description = "A client and server SSH library."keywords = ["ssh"]version = "0.29.4"authors = ["Pierre-Étienne Meunier <pe@pijul.org>"]repository = "https://nest.pijul.com/pijul_org/thrussh"homepage = "https://pijul.org/thrussh"documentation = "https://docs.rs/thrussh"license = "Apache-2.0"include = ["Cargo.toml","src/auth.rs","src/compression.rs","src/kex.rs","src/key.rs","src/lib.rs","src/msg.rs","src/negotiation.rs","src/pty.rs","src/session.rs","src/sshbuffer.rs","src/ssh_read.rs","src/cipher/chacha20poly1305.rs","src/cipher/clear.rs","src/cipher/mod.rs","src/client/mod.rs","src/client/session.rs","src/client/encrypted.rs","src/client/kex.rs","src/client/proxy.rs","src/server/mod.rs","src/server/encrypted.rs","src/server/kex.rs","src/server/session.rs","src/sodium.rs",]edition = "2018"[features]default = [ "flate2" ][dependencies]byteorder = "1.3"bitflags = "1.2"log = "0.4"thrussh-keys = "0.18.3"openssl = "0.10"thrussh-libsodium = "0.2"cryptovec = "0.6.0"tokio = { version = "0.2", features = [ "io-util", "rt-threaded", "time", "stream", "tcp", "sync", "macros" ] }futures = "0.3"thiserror = "1.0"anyhow = "1.0"flate2 = { version = "1.0", optional = true }[dev-dependencies]env_logger = "0.7"tokio = { version = "0.2", features = [ "io-util", "rt-threaded", "time", "stream", "tcp", "sync", "macros" ] }
with import <nixpkgs> {};let src = fetchFromGitHub {owner = "mozilla";repo = "nixpkgs-mozilla";rev = "8c007b60731c07dd7a052cce508de3bb1ae849b4";hash = "sha256-RsNPnEKd7BcogwkqhaV5kI/HuNC4flH/OQCC/4W5y/8=";};inwith import "${src.out}/rust-overlay.nix" pkgs pkgs;stdenv.mkDerivation {name = "rust-pijul";buildInputs = [ rustChannels.stable.rust libsodium pkgconfig openssl ];}
// Copyright 2016 Pierre-Étienne Meunier//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.//extern crate libc;extern crate winapi;use libc::c_void;#[cfg(not(windows))]use libc::size_t;use std::ops::{Deref, DerefMut, Index, IndexMut, Range, RangeFrom, RangeFull, RangeTo};/// A buffer which zeroes its memory on `.clear()`, `.resize()` and/// reallocations, to avoid copying secrets around.#[derive(Debug)]pub struct CryptoVec {p: *mut u8,size: usize,capacity: usize,zero: u8,}impl Unpin for CryptoVec {}unsafe impl Send for CryptoVec {}unsafe impl Sync for CryptoVec {}impl AsRef<[u8]> for CryptoVec {fn as_ref(&self) -> &[u8] {self.deref()}}impl AsMut<[u8]> for CryptoVec {fn as_mut(&mut self) -> &mut [u8] {self.deref_mut()}}impl Deref for CryptoVec {type Target = [u8];fn deref(&self) -> &[u8] {unsafe { std::slice::from_raw_parts(self.p, self.size) }}}impl DerefMut for CryptoVec {fn deref_mut(&mut self) -> &mut [u8] {unsafe { std::slice::from_raw_parts_mut(self.p, self.size) }}}impl From<String> for CryptoVec {fn from(e: String) -> Self {CryptoVec::from(e.into_bytes())}}impl From<Vec<u8>> for CryptoVec {fn from(e: Vec<u8>) -> Self {let mut c = CryptoVec::new_zeroed(e.len());c.clone_from_slice(&e[..]);c}}impl Index<RangeFrom<usize>> for CryptoVec {type Output = [u8];fn index(&self, index: RangeFrom<usize>) -> &[u8] {self.deref().index(index)}}impl Index<RangeTo<usize>> for CryptoVec {type Output = [u8];fn index(&self, index: RangeTo<usize>) -> &[u8] {self.deref().index(index)}}impl Index<Range<usize>> for CryptoVec {type Output = [u8];fn index(&self, index: Range<usize>) -> &[u8] {self.deref().index(index)}}impl Index<RangeFull> for CryptoVec {type Output = [u8];fn index(&self, _: RangeFull) -> &[u8] {self.deref()}}impl IndexMut<RangeFull> for CryptoVec {fn index_mut(&mut self, _: RangeFull) -> &mut [u8] {self.deref_mut()}}impl IndexMut<RangeFrom<usize>> for CryptoVec {fn index_mut(&mut self, index: RangeFrom<usize>) -> &mut [u8] {self.deref_mut().index_mut(index)}}impl IndexMut<RangeTo<usize>> for CryptoVec {fn index_mut(&mut self, index: RangeTo<usize>) -> &mut [u8] {self.deref_mut().index_mut(index)}}impl IndexMut<Range<usize>> for CryptoVec {fn index_mut(&mut self, index: Range<usize>) -> &mut [u8] {self.deref_mut().index_mut(index)}}impl Index<usize> for CryptoVec {type Output = u8;fn index(&self, index: usize) -> &u8 {self.deref().index(index)}}impl std::io::Write for CryptoVec {fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {self.extend(buf);Ok(buf.len())}fn flush(&mut self) -> Result<(), std::io::Error> {Ok(())}}impl Default for CryptoVec {fn default() -> Self {let mut buf = CryptoVec {p: std::ptr::null_mut(),size: 0,capacity: 0,zero: 0,};// This avoids potential problems in as_slice().buf.p = &mut buf.zero;//buf}}#[cfg(not(windows))]unsafe fn mlock(ptr: *const u8, len: usize) {libc::mlock(ptr as *const c_void, len as size_t);}#[cfg(not(windows))]unsafe fn munlock(ptr: *const u8, len: usize) {libc::munlock(ptr as *const c_void, len as size_t);}#[cfg(windows)]use winapi::shared::basetsd::SIZE_T;#[cfg(windows)]use winapi::shared::minwindef::LPVOID;#[cfg(windows)]use winapi::um::memoryapi::{VirtualLock, VirtualUnlock};#[cfg(windows)]unsafe fn mlock(ptr: *const u8, len: usize) {VirtualLock(ptr as LPVOID, len as SIZE_T);}#[cfg(windows)]unsafe fn munlock(ptr: *const u8, len: usize) {VirtualUnlock(ptr as LPVOID, len as SIZE_T);}impl Clone for CryptoVec {fn clone(&self) -> Self {let mut v = Self::new();v.extend(self);v}}impl CryptoVec {/// Creates a new `CryptoVec`.pub fn new() -> CryptoVec {CryptoVec::default()}/// Creates a new `CryptoVec` with `n` zeros.pub fn new_zeroed(size: usize) -> CryptoVec {unsafe {let capacity = size.next_power_of_two();let layout = std::alloc::Layout::from_size_align_unchecked(capacity, 1);let p = std::alloc::alloc_zeroed(layout);mlock(p, capacity);CryptoVec {p,capacity,zero: 0,size,}}}/// Creates a new `CryptoVec` with capacity `capacity`.pub fn with_capacity(capacity: usize) -> CryptoVec {unsafe {let capacity = capacity.next_power_of_two();let layout = std::alloc::Layout::from_size_align_unchecked(capacity, 1);let p = std::alloc::alloc_zeroed(layout);mlock(p, capacity);CryptoVec {p,capacity,zero: 0,size: 0,}}}/// Length of this `CryptoVec`.////// ```/// assert_eq!(cryptovec::CryptoVec::new().len(), 0)/// ```pub fn len(&self) -> usize {self.size}/// Returns `true` if and only if this CryptoVec is empty.////// ```/// assert!(cryptovec::CryptoVec::new().is_empty())/// ```pub fn is_empty(&self) -> bool {self.len() == 0}/// Resize this CryptoVec, appending zeros at the end. This may/// perform at most one reallocation, overwriting the previous/// version with zeros.pub fn resize(&mut self, size: usize) {if size <= self.capacity && size > self.size {// If this is an expansion, just resize.self.size = size} else if size <= self.size {// If this is a truncation, resize and erase the extra memory.unsafe {libc::memset(self.p.offset(size as isize) as *mut c_void,0,self.size - size,);}self.size = size;} else {// realloc ! and erase the previous memory.unsafe {let next_capacity = size.next_power_of_two();let old_ptr = self.p;let next_layout = std::alloc::Layout::from_size_align_unchecked(next_capacity, 1);self.p = std::alloc::alloc_zeroed(next_layout);mlock(self.p, next_capacity);if self.capacity > 0 {std::ptr::copy_nonoverlapping(old_ptr, self.p, self.size);for i in 0..self.size {std::ptr::write_volatile(old_ptr.offset(i as isize), 0)}munlock(old_ptr, self.capacity);let layout = std::alloc::Layout::from_size_align_unchecked(self.capacity, 1);std::alloc::dealloc(old_ptr, layout);}if self.p.is_null() {panic!("Realloc failed, pointer = {:?} {:?}", self, size)} else {self.capacity = next_capacity;self.size = size;}}}}/// Clear this CryptoVec (retaining the memory).////// ```/// let mut v = cryptovec::CryptoVec::new();/// v.extend(b"blabla");/// v.clear();/// assert!(v.is_empty())/// ```pub fn clear(&mut self) {self.resize(0);}/// Append a new byte at the end of this CryptoVec.pub fn push(&mut self, s: u8) {let size = self.size;self.resize(size + 1);unsafe { *(self.p.offset(size as isize)) = s }}/// Append a new u32, big endian-encoded, at the end of this CryptoVec.////// ```/// let mut v = cryptovec::CryptoVec::new();/// let n = 43554;/// v.push_u32_be(n);/// assert_eq!(n, v.read_u32_be(0))/// ```pub fn push_u32_be(&mut self, s: u32) {let s = s.to_be();let x: [u8; 4] = unsafe { std::mem::transmute(s) };self.extend(&x)}/// Read a big endian-encoded u32 from this CryptoVec, with the/// first byte at position `i`.////// ```/// let mut v = cryptovec::CryptoVec::new();/// let n = 99485710;/// v.push_u32_be(n);/// assert_eq!(n, v.read_u32_be(0))/// ```pub fn read_u32_be(&self, i: usize) -> u32 {assert!(i + 4 <= self.size);let mut x: u32 = 0;unsafe {libc::memcpy((&mut x) as *mut u32 as *mut c_void,self.p.offset(i as isize) as *const c_void,4,);}u32::from_be(x)}/// Read `n_bytes` from `r`, and append them at the end of this/// `CryptoVec`. Returns the number of bytes read (and appended).pub fn read<R: std::io::Read>(&mut self,n_bytes: usize,mut r: R,) -> Result<usize, std::io::Error> {let cur_size = self.size;self.resize(cur_size + n_bytes);let s =unsafe { std::slice::from_raw_parts_mut(self.p.offset(cur_size as isize), n_bytes) };// Resize the buffer to its appropriate size.match r.read(s) {Ok(n) => {self.resize(cur_size + n);Ok(n)}Err(e) => {self.resize(cur_size);Err(e)}}}/// Write all this CryptoVec to the provided `Write`. Returns the/// number of bytes actually written.////// ```/// let mut v = cryptovec::CryptoVec::new();/// v.extend(b"blabla");/// let mut s = std::io::stdout();/// v.write_all_from(0, &mut s).unwrap();/// ```pub fn write_all_from<W: std::io::Write>(&self,offset: usize,mut w: W,) -> Result<usize, std::io::Error> {assert!(offset < self.size);// if we're past this point, self.p cannot be null.unsafe {let s = std::slice::from_raw_parts(self.p.offset(offset as isize), self.size - offset);w.write(s)}}/// Resize this CryptoVec, returning a mutable borrow to the extra bytes.////// ```/// let mut v = cryptovec::CryptoVec::new();/// v.resize_mut(4).clone_from_slice(b"test");/// ```pub fn resize_mut(&mut self, n: usize) -> &mut [u8] {let size = self.size;self.resize(size + n);unsafe { std::slice::from_raw_parts_mut(self.p.offset(size as isize), n) }}/// Append a slice at the end of this CryptoVec.////// ```/// let mut v = cryptovec::CryptoVec::new();/// v.extend(b"test");/// ```pub fn extend(&mut self, s: &[u8]) {let size = self.size;self.resize(size + s.len());unsafe {std::ptr::copy_nonoverlapping(s.as_ptr(), self.p.offset(size as isize), s.len());}}/// Create a `CryptoVec` from a slice////// ```/// CryptoVec::from_slice(b"test");/// ```pub fn from_slice(s: &[u8]) -> CryptoVec {let mut v = CryptoVec::new();v.resize(s.len());unsafe {std::ptr::copy_nonoverlapping(s.as_ptr(), v.p, s.len());}v}}impl Drop for CryptoVec {fn drop(&mut self) {if self.capacity > 0 {unsafe {for i in 0..self.size {std::ptr::write_volatile(self.p.offset(i as isize), 0)}munlock(self.p, self.capacity);let layout = std::alloc::Layout::from_size_align_unchecked(self.capacity, 1);std::alloc::dealloc(self.p, layout);}}}}
[package]name = "cryptovec"description = "A vector which zeroes its memory on clears and reallocations."version = "0.6.0"authors = ["Pierre-Étienne Meunier <pe@pijul.org>"]repository = "https://pijul.org/cryptovec"documentation = "https://pijul.org/cryptovec/doc/cryptovec"license = "Apache-2.0"include = ["Cargo.toml", "src/lib.rs"][dependencies]libc = "0.2"winapi = { version = "0.3", features = ["basetsd", "minwindef", "memoryapi"] }
# ThrusshA full implementation of the SSH 2 protocol, both server-side and client-side.Thrussh is completely asynchronous, and can be combined with other protocols using [Tokio](//tokio.rs).## ContributingWe welcome contributions. Currently, the main areas where we need help are:- Handling SSH keys correctly on all platforms. In particular, interactions with agents, PGP, and password-protected/encrypted keys are not yet implemented.- Auditing our code, and writing tests. The code is written so that the protocol can be entirely run inside vectors (instead of network sockets).By contributing, you agree to license all your contributions under the Apache 2.0 license.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.## Issue ReportingPlease report bugs on the [issue page of this repository](//nest.pijul.com/pijul_org/thrussh).Thrussh has a full disclosure vulnerability policy.Please do NOT attempt to report any security vulnerability in this code privately to anybody.
Apache LicenseVersion 2.0, January 2004http://www.apache.org/licenses/TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION1. Definitions."License" shall mean the terms and conditions for use, reproduction,and distribution as defined by Sections 1 through 9 of this document."Licensor" shall mean the copyright owner or entity authorized bythe copyright owner that is granting the License."Legal Entity" shall mean the union of the acting entity and allother entities that control, are controlled by, or are under commoncontrol with that entity. For the purposes of this definition,"control" means (i) the power, direct or indirect, to cause thedirection or management of such entity, whether by contract orotherwise, or (ii) ownership of fifty percent (50%) or more of theoutstanding shares, or (iii) beneficial ownership of such entity."You" (or "Your") shall mean an individual or Legal Entityexercising permissions granted by this License."Source" form shall mean the preferred form for making modifications,including but not limited to software source code, documentationsource, and configuration files."Object" form shall mean any form resulting from mechanicaltransformation or translation of a Source form, including butnot limited to compiled object code, generated documentation,and conversions to other media types."Work" shall mean the work of authorship, whether in Source orObject form, made available under the License, as indicated by acopyright notice that is included in or attached to the work(an example is provided in the Appendix below)."Derivative Works" shall mean any work, whether in Source or Objectform, that is based on (or derived from) the Work and for which theeditorial revisions, annotations, elaborations, or other modificationsrepresent, as a whole, an original work of authorship. For the purposesof this License, Derivative Works shall not include works that remainseparable from, or merely link (or bind by name) to the interfaces of,the Work and Derivative Works thereof."Contribution" shall mean any work of authorship, includingthe original version of the Work and any modifications or additionsto that Work or Derivative Works thereof, that is intentionallysubmitted to Licensor for inclusion in the Work by the copyright owneror by an individual or Legal Entity authorized to submit on behalf ofthe copyright owner. For the purposes of this definition, "submitted"means any form of electronic, verbal, or written communication sentto the Licensor or its representatives, including but not limited tocommunication on electronic mailing lists, source code control systems,and issue tracking systems that are managed by, or on behalf of, theLicensor for the purpose of discussing and improving the Work, butexcluding communication that is conspicuously marked or otherwisedesignated in writing by the copyright owner as "Not a Contribution.""Contributor" shall mean Licensor and any individual or Legal Entityon behalf of whom a Contribution has been received by Licensor andsubsequently incorporated within the Work.2. Grant of Copyright License. Subject to the terms and conditions ofthis License, each Contributor hereby grants to You a perpetual,worldwide, non-exclusive, no-charge, royalty-free, irrevocablecopyright license to reproduce, prepare Derivative Works of,publicly display, publicly perform, sublicense, and distribute theWork and such Derivative Works in Source or Object form.3. Grant of Patent License. Subject to the terms and conditions ofthis License, each Contributor hereby grants to You a perpetual,worldwide, non-exclusive, no-charge, royalty-free, irrevocable(except as stated in this section) patent license to make, have made,use, offer to sell, sell, import, and otherwise transfer the Work,where such license applies only to those patent claims licensableby such Contributor that are necessarily infringed by theirContribution(s) alone or by combination of their Contribution(s)with the Work to which such Contribution(s) was submitted. If Youinstitute patent litigation against any entity (including across-claim or counterclaim in a lawsuit) alleging that the Workor a Contribution incorporated within the Work constitutes director contributory patent infringement, then any patent licensesgranted to You under this License for that Work shall terminateas of the date such litigation is filed.4. Redistribution. You may reproduce and distribute copies of theWork or Derivative Works thereof in any medium, with or withoutmodifications, and in Source or Object form, provided that Youmeet the following conditions:(a) You must give any other recipients of the Work orDerivative Works a copy of this License; and(b) You must cause any modified files to carry prominent noticesstating that You changed the files; and(c) You must retain, in the Source form of any Derivative Worksthat You distribute, all copyright, patent, trademark, andattribution notices from the Source form of the Work,excluding those notices that do not pertain to any part ofthe Derivative Works; and(d) If the Work includes a "NOTICE" text file as part of itsdistribution, then any Derivative Works that You distribute mustinclude a readable copy of the attribution notices containedwithin such NOTICE file, excluding those notices that do notpertain to any part of the Derivative Works, in at least oneof the following places: within a NOTICE text file distributedas part of the Derivative Works; within the Source form ordocumentation, if provided along with the Derivative Works; or,within a display generated by the Derivative Works, if andwherever such third-party notices normally appear. The contentsof the NOTICE file are for informational purposes only anddo not modify the License. You may add Your own attributionnotices within Derivative Works that You distribute, alongsideor as an addendum to the NOTICE text from the Work, providedthat such additional attribution notices cannot be construedas modifying the License.You may add Your own copyright statement to Your modifications andmay provide additional or different license terms and conditionsfor use, reproduction, or distribution of Your modifications, orfor any such Derivative Works as a whole, provided Your use,reproduction, and distribution of the Work otherwise complies withthe conditions stated in this License.5. Submission of Contributions. Unless You explicitly state otherwise,any Contribution intentionally submitted for inclusion in the Workby You to the Licensor shall be under the terms and conditions ofthis License, without any additional terms or conditions.Notwithstanding the above, nothing herein shall supersede or modifythe terms of any separate license agreement you may have executedwith Licensor regarding such Contributions.6. Trademarks. This License does not grant permission to use the tradenames, trademarks, service marks, or product names of the Licensor,except as required for reasonable and customary use in describing theorigin of the Work and reproducing the content of the NOTICE file.7. Disclaimer of Warranty. Unless required by applicable law oragreed to in writing, Licensor provides the Work (and eachContributor provides its Contributions) on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied, including, without limitation, any warranties or conditionsof TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR APARTICULAR PURPOSE. You are solely responsible for determining theappropriateness of using or redistributing the Work and assume anyrisks associated with Your exercise of permissions under this License.8. Limitation of Liability. In no event and under no legal theory,whether in tort (including negligence), contract, or otherwise,unless required by applicable law (such as deliberate and grosslynegligent acts) or agreed to in writing, shall any Contributor beliable to You for damages, including any direct, indirect, special,incidental, or consequential damages of any character arising as aresult of this License or out of the use or inability to use theWork (including but not limited to damages for loss of goodwill,work stoppage, computer failure or malfunction, or any and allother commercial damages or losses), even if such Contributorhas been advised of the possibility of such damages.9. Accepting Warranty or Additional Liability. While redistributingthe Work or Derivative Works thereof, You may choose to offer,and charge a fee for, acceptance of support, warranty, indemnity,or other liability obligations and/or rights consistent with thisLicense. However, in accepting such obligations, You may act onlyon Your own behalf and on Your sole responsibility, not on behalfof any other Contributor, and only if You agree to indemnify,defend, and hold each Contributor harmless for any liabilityincurred by, or claims asserted against, such Contributor by reasonof your accepting any such warranty or additional liability.END OF TERMS AND CONDITIONSAPPENDIX: How to apply the Apache License to your work.To apply the Apache License to your work, attach the followingboilerplate notice, with the fields enclosed by brackets "[]"replaced with your own identifying information. (Don't includethe brackets!) The text should be enclosed in the appropriatecomment syntax for the file format. We also recommend that afile or class name and description of purpose be included on thesame "printed page" as the copyright notice for easieridentification within third-party archives.Copyright [yyyy] [name of copyright owner]Licensed under the Apache License, Version 2.0 (the "License");you may not use this file except in compliance with the License.You may obtain a copy of the License athttp://www.apache.org/licenses/LICENSE-2.0Unless required by applicable law or agreed to in writing, softwaredistributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions andlimitations under the License.
[workspace]members = [ "thrussh-keys", "thrussh", "thrussh-libsodium", "thrussh-config", "cryptovec" ][patch.crates-io]thrussh = { path = "thrussh" }thrussh-keys = { path = "thrussh-keys" }thrussh-libsodium = { path = "thrussh-libsodium" }cryptovec = { path = "cryptovec" }thrussh-config = { path = "thrussh-config" }
# This file is automatically @generated by Cargo.# It is not intended for manual editing.[[package]]name = "adler"version = "0.2.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"[[package]]name = "aho-corasick"version = "0.7.14"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d"dependencies = ["memchr",][[package]]name = "anyhow"version = "1.0.33"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a1fd36ffbb1fb7c834eac128ea8d0e310c5aeb635548f9d58861e1308d46e71c"[[package]]name = "arrayref"version = "0.3.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"[[package]]name = "arrayvec"version = "0.5.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"[[package]]name = "atty"version = "0.2.14"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"dependencies = ["hermit-abi","libc","winapi 0.3.9",][[package]]name = "autocfg"version = "1.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"[[package]]name = "base64"version = "0.9.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"dependencies = ["byteorder","safemem",][[package]]name = "base64"version = "0.12.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"[[package]]name = "bit-vec"version = "0.4.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"[[package]]name = "bit-vec"version = "0.6.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3"[[package]]name = "bitflags"version = "1.2.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"[[package]]name = "blake2b_simd"version = "0.5.10"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"dependencies = ["arrayref","arrayvec","constant_time_eq",][[package]]name = "byteorder"version = "1.3.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"[[package]]name = "bytes"version = "0.4.12"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"dependencies = ["byteorder","iovec",][[package]]name = "bytes"version = "0.5.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"[[package]]name = "cc"version = "1.0.61"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"[[package]]name = "cfg-if"version = "0.1.10"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"[[package]]name = "cfg-if"version = "1.0.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"[[package]]name = "cloudabi"version = "0.0.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"dependencies = ["bitflags",][[package]]name = "constant_time_eq"version = "0.1.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"[[package]]name = "crc32fast"version = "1.2.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"dependencies = ["cfg-if 1.0.0",][[package]]name = "crossbeam-deque"version = "0.7.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"dependencies = ["crossbeam-epoch","crossbeam-utils","maybe-uninit",][[package]]name = "crossbeam-epoch"version = "0.8.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"dependencies = ["autocfg","cfg-if 0.1.10","crossbeam-utils","lazy_static","maybe-uninit","memoffset","scopeguard",][[package]]name = "crossbeam-queue"version = "0.2.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"dependencies = ["cfg-if 0.1.10","crossbeam-utils","maybe-uninit",][[package]]name = "crossbeam-utils"version = "0.7.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"dependencies = ["autocfg","cfg-if 0.1.10","lazy_static",][[package]]name = "cryptovec"version = "0.4.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e412d5c27b9048893c168870a91ce9df1ce55b780153fa11d1bd89e5606d08d9"dependencies = ["kernel32-sys","libc","winapi 0.2.8",][[package]]name = "cryptovec"version = "0.6.0"dependencies = ["libc","winapi 0.3.9",][[package]]name = "data-encoding"version = "2.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d4d0e2d24e5ee3b23a01de38eefdcd978907890701f08ffffd4cb457ca4ee8d6"[[package]]name = "dirs"version = "1.0.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"dependencies = ["libc","redox_users","winapi 0.3.9",][[package]]name = "dirs"version = "2.0.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3"dependencies = ["cfg-if 0.1.10","dirs-sys",][[package]]name = "dirs-sys"version = "0.3.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a"dependencies = ["libc","redox_users","winapi 0.3.9",][[package]]name = "env_logger"version = "0.7.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"dependencies = ["atty","humantime","log","regex","termcolor",][[package]]name = "flate2"version = "1.0.18"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "da80be589a72651dcda34d8b35bcdc9b7254ad06325611074d9cc0fbb19f60ee"dependencies = ["cfg-if 0.1.10","crc32fast","libc","miniz_oxide",][[package]]name = "fnv"version = "1.0.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"[[package]]name = "foreign-types"version = "0.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"dependencies = ["foreign-types-shared",][[package]]name = "foreign-types-shared"version = "0.1.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"[[package]]name = "fuchsia-cprng"version = "0.1.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"[[package]]name = "fuchsia-zircon"version = "0.3.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"dependencies = ["bitflags","fuchsia-zircon-sys",][[package]]name = "fuchsia-zircon-sys"version = "0.3.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"[[package]]name = "futures"version = "0.1.30"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed"[[package]]name = "futures"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "95314d38584ffbfda215621d723e0a3906f032e03ae5551e650058dac83d4797"dependencies = ["futures-channel","futures-core","futures-executor","futures-io","futures-sink","futures-task","futures-util",][[package]]name = "futures-channel"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0448174b01148032eed37ac4aed28963aaaa8cfa93569a08e5b479bbc6c2c151"dependencies = ["futures-core","futures-sink",][[package]]name = "futures-core"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "18eaa56102984bed2c88ea39026cff3ce3b4c7f508ca970cedf2450ea10d4e46"[[package]]name = "futures-executor"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f5f8e0c9258abaea85e78ebdda17ef9666d390e987f006be6080dfe354b708cb"dependencies = ["futures-core","futures-task","futures-util",][[package]]name = "futures-io"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6e1798854a4727ff944a7b12aa999f58ce7aa81db80d2dfaaf2ba06f065ddd2b"[[package]]name = "futures-macro"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e36fccf3fc58563b4a14d265027c627c3b665d7fed489427e88e7cc929559efe"dependencies = ["proc-macro-hack","proc-macro2","quote","syn",][[package]]name = "futures-sink"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0e3ca3f17d6e8804ae5d3df7a7d35b2b3a6fe89dac84b31872720fc3060a0b11"[[package]]name = "futures-task"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "96d502af37186c4fef99453df03e374683f8a1eec9dcc1e66b3b82dc8278ce3c"dependencies = ["once_cell",][[package]]name = "futures-util"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "abcb44342f62e6f3e8ac427b8aa815f724fd705dfad060b18ac7866c15bb8e34"dependencies = ["futures-channel","futures-core","futures-io","futures-macro","futures-sink","futures-task","memchr","pin-project","pin-utils","proc-macro-hack","proc-macro-nested","slab",][[package]]name = "getrandom"version = "0.1.15"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"dependencies = ["cfg-if 0.1.10","libc","wasi",][[package]]name = "hermit-abi"version = "0.1.17"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"dependencies = ["libc",][[package]]name = "hex"version = "0.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"[[package]]name = "humantime"version = "1.3.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"dependencies = ["quick-error",][[package]]name = "iovec"version = "0.1.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"dependencies = ["libc",][[package]]name = "kernel32-sys"version = "0.2.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"dependencies = ["winapi 0.2.8","winapi-build",][[package]]name = "lazy_static"version = "1.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"[[package]]name = "libc"version = "0.2.80"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"[[package]]name = "libsodium-sys"version = "0.2.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a685b64f837b339074115f2e7f7b431ac73681d08d75b389db7498b8892b8a58"dependencies = ["cc","libc","pkg-config",][[package]]name = "lock_api"version = "0.3.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"dependencies = ["scopeguard",][[package]]name = "log"version = "0.4.11"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"dependencies = ["cfg-if 0.1.10",][[package]]name = "maybe-uninit"version = "2.0.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"[[package]]name = "memchr"version = "2.3.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"[[package]]name = "memoffset"version = "0.5.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"dependencies = ["autocfg",][[package]]name = "miniz_oxide"version = "0.4.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"dependencies = ["adler","autocfg",][[package]]name = "mio"version = "0.6.22"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"dependencies = ["cfg-if 0.1.10","fuchsia-zircon","fuchsia-zircon-sys","iovec","kernel32-sys","libc","log","miow","net2","slab","winapi 0.2.8",][[package]]name = "mio-uds"version = "0.6.8"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"dependencies = ["iovec","libc","mio",][[package]]name = "miow"version = "0.2.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"dependencies = ["kernel32-sys","net2","winapi 0.2.8","ws2_32-sys",][[package]]name = "net2"version = "0.2.35"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853"dependencies = ["cfg-if 0.1.10","libc","winapi 0.3.9",][[package]]name = "num"version = "0.1.42"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"dependencies = ["num-bigint 0.1.44","num-complex","num-integer","num-iter","num-rational","num-traits",][[package]]name = "num-bigint"version = "0.1.44"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"dependencies = ["num-integer","num-traits","rand","rustc-serialize",][[package]]name = "num-bigint"version = "0.2.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"dependencies = ["autocfg","num-integer","num-traits",][[package]]name = "num-complex"version = "0.1.43"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656"dependencies = ["num-traits","rustc-serialize",][[package]]name = "num-integer"version = "0.1.43"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"dependencies = ["autocfg","num-traits",][[package]]name = "num-iter"version = "0.1.42"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59"dependencies = ["autocfg","num-integer","num-traits",][[package]]name = "num-rational"version = "0.1.42"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ee314c74bd753fc86b4780aa9475da469155f3848473a261d2d18e35245a784e"dependencies = ["num-bigint 0.1.44","num-integer","num-traits","rustc-serialize",][[package]]name = "num-traits"version = "0.2.12"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"dependencies = ["autocfg",][[package]]name = "num_cpus"version = "1.13.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"dependencies = ["hermit-abi","libc",][[package]]name = "once_cell"version = "1.4.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"[[package]]name = "openssl"version = "0.10.30"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8d575eff3665419f9b83678ff2815858ad9d11567e082f5ac1814baba4e2bcb4"dependencies = ["bitflags","cfg-if 0.1.10","foreign-types","lazy_static","libc","openssl-sys",][[package]]name = "openssl-sys"version = "0.9.58"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"dependencies = ["autocfg","cc","libc","pkg-config","vcpkg",][[package]]name = "parking_lot"version = "0.9.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"dependencies = ["lock_api","parking_lot_core","rustc_version",][[package]]name = "parking_lot_core"version = "0.6.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"dependencies = ["cfg-if 0.1.10","cloudabi","libc","redox_syscall","rustc_version","smallvec","winapi 0.3.9",][[package]]name = "pin-project"version = "1.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ee41d838744f60d959d7074e3afb6b35c7456d0f61cad38a24e35e6553f73841"dependencies = ["pin-project-internal",][[package]]name = "pin-project-internal"version = "1.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "81a4ffa594b66bff340084d4081df649a7dc049ac8d7fc458d8e628bfbbb2f86"dependencies = ["proc-macro2","quote","syn",][[package]]name = "pin-project-lite"version = "0.1.11"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b"[[package]]name = "pin-utils"version = "0.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"[[package]]name = "pkg-config"version = "0.3.19"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"[[package]]name = "proc-macro-hack"version = "0.5.19"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"[[package]]name = "proc-macro-nested"version = "0.1.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"[[package]]name = "proc-macro2"version = "1.0.24"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"dependencies = ["unicode-xid",][[package]]name = "quick-error"version = "1.2.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"[[package]]name = "quote"version = "1.0.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"dependencies = ["proc-macro2",][[package]]name = "rand"version = "0.4.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"dependencies = ["fuchsia-cprng","libc","rand_core 0.3.1","rdrand","winapi 0.3.9",][[package]]name = "rand_core"version = "0.3.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"dependencies = ["rand_core 0.4.2",][[package]]name = "rand_core"version = "0.4.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"[[package]]name = "rdrand"version = "0.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"dependencies = ["rand_core 0.3.1",][[package]]name = "redox_syscall"version = "0.1.57"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"[[package]]name = "redox_users"version = "0.3.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d"dependencies = ["getrandom","redox_syscall","rust-argon2",][[package]]name = "regex"version = "1.4.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b"dependencies = ["aho-corasick","memchr","regex-syntax","thread_local",][[package]]name = "regex-syntax"version = "0.6.20"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c"[[package]]name = "remove_dir_all"version = "0.5.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"dependencies = ["winapi 0.3.9",][[package]]name = "rust-argon2"version = "0.8.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "9dab61250775933275e84053ac235621dfb739556d5c54a2f2e9313b7cf43a19"dependencies = ["base64 0.12.3","blake2b_simd","constant_time_eq","crossbeam-utils",][[package]]name = "rustc-serialize"version = "0.3.24"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"[[package]]name = "rustc_version"version = "0.2.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"dependencies = ["semver",][[package]]name = "safemem"version = "0.3.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"[[package]]name = "scopeguard"version = "1.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"[[package]]name = "semver"version = "0.9.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"dependencies = ["semver-parser",][[package]]name = "semver-parser"version = "0.7.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"[[package]]name = "serde"version = "1.0.117"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a"[[package]]name = "serde_derive"version = "1.0.117"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e"dependencies = ["proc-macro2","quote","syn",][[package]]name = "slab"version = "0.4.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"[[package]]name = "smallvec"version = "0.6.13"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"dependencies = ["maybe-uninit",][[package]]name = "syn"version = "1.0.48"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"dependencies = ["proc-macro2","quote","unicode-xid",][[package]]name = "tempdir"version = "0.3.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"dependencies = ["rand","remove_dir_all",][[package]]name = "termcolor"version = "1.1.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"dependencies = ["winapi-util",][[package]]name = "thiserror"version = "1.0.21"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "318234ffa22e0920fe9a40d7b8369b5f649d490980cf7aadcf1eb91594869b42"dependencies = ["thiserror-impl",][[package]]name = "thiserror-impl"version = "1.0.21"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cae2447b6282786c3493999f40a9be2a6ad20cb8bd268b0a0dbf5a065535c0ab"dependencies = ["proc-macro2","quote","syn",][[package]]name = "thread_local"version = "1.0.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"dependencies = ["lazy_static",][[package]]name = "thrussh"version = "0.21.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1452876d42918bfe7b42a983c9bcd19cb5ce14d2e84438f0b4db60d4e7800a4d"dependencies = ["bitflags","byteorder","cryptovec 0.4.6","futures 0.1.30","log","openssl","thrussh-keys 0.11.9","thrussh-libsodium 0.1.4","tokio 0.1.22","tokio-io",][[package]]name = "thrussh"version = "0.29.4"dependencies = ["anyhow","bitflags","byteorder","cryptovec 0.6.0","env_logger","flate2","futures 0.3.7","log","openssl","thiserror","thrussh-keys 0.18.3","thrussh-libsodium 0.2.1","tokio 0.2.22",][[package]]name = "thrussh-config"version = "0.2.1"dependencies = ["dirs 1.0.5","futures 0.1.30","lazy_static","log","regex","thrussh 0.21.5","tokio 0.1.22",][[package]]name = "thrussh-keys"version = "0.11.9"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5e72f93d9f96aa5ce3f38c1e105c668ce06867b6cdbffe68c261cfb4d59c7066"dependencies = ["base64 0.9.3","bit-vec 0.4.4","byteorder","cryptovec 0.4.6","dirs 1.0.5","futures 0.1.30","hex","log","num-bigint 0.1.44","num-integer","openssl","serde","serde_derive","thrussh-libsodium 0.1.4","tokio 0.1.22","yasna 0.1.3",][[package]]name = "thrussh-keys"version = "0.18.3"dependencies = ["anyhow","bit-vec 0.6.2","byteorder","cryptovec 0.6.0","data-encoding","dirs 2.0.2","env_logger","futures 0.3.7","log","num-bigint 0.2.6","num-integer","openssl","serde","serde_derive","tempdir","thiserror","thrussh-libsodium 0.2.1","tokio 0.2.22","yasna 0.3.2",][[package]]name = "thrussh-libsodium"version = "0.1.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "1bdeecb0f876ad8187bd0bd9234fd23e70e6c03cf48c29d5967d55cba3d45a3a"dependencies = ["libc","pkg-config",][[package]]name = "thrussh-libsodium"version = "0.2.1"dependencies = ["lazy_static","libc","libsodium-sys","pkg-config","vcpkg",][[package]]name = "tokio"version = "0.1.22"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"dependencies = ["bytes 0.4.12","futures 0.1.30","mio","num_cpus","tokio-codec","tokio-current-thread","tokio-executor","tokio-fs","tokio-io","tokio-reactor","tokio-sync","tokio-tcp","tokio-threadpool","tokio-timer","tokio-udp","tokio-uds",][[package]]name = "tokio"version = "0.2.22"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd"dependencies = ["bytes 0.5.6","fnv","futures-core","iovec","lazy_static","libc","memchr","mio","mio-uds","num_cpus","pin-project-lite","slab","tokio-macros",][[package]]name = "tokio-codec"version = "0.1.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b"dependencies = ["bytes 0.4.12","futures 0.1.30","tokio-io",][[package]]name = "tokio-current-thread"version = "0.1.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e"dependencies = ["futures 0.1.30","tokio-executor",][[package]]name = "tokio-executor"version = "0.1.10"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671"dependencies = ["crossbeam-utils","futures 0.1.30",][[package]]name = "tokio-fs"version = "0.1.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4"dependencies = ["futures 0.1.30","tokio-io","tokio-threadpool",][[package]]name = "tokio-io"version = "0.1.13"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"dependencies = ["bytes 0.4.12","futures 0.1.30","log",][[package]]name = "tokio-macros"version = "0.2.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"dependencies = ["proc-macro2","quote","syn",][[package]]name = "tokio-reactor"version = "0.1.12"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351"dependencies = ["crossbeam-utils","futures 0.1.30","lazy_static","log","mio","num_cpus","parking_lot","slab","tokio-executor","tokio-io","tokio-sync",][[package]]name = "tokio-sync"version = "0.1.8"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee"dependencies = ["fnv","futures 0.1.30",][[package]]name = "tokio-tcp"version = "0.1.4"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72"dependencies = ["bytes 0.4.12","futures 0.1.30","iovec","mio","tokio-io","tokio-reactor",][[package]]name = "tokio-threadpool"version = "0.1.18"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89"dependencies = ["crossbeam-deque","crossbeam-queue","crossbeam-utils","futures 0.1.30","lazy_static","log","num_cpus","slab","tokio-executor",][[package]]name = "tokio-timer"version = "0.2.13"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296"dependencies = ["crossbeam-utils","futures 0.1.30","slab","tokio-executor",][[package]]name = "tokio-udp"version = "0.1.6"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82"dependencies = ["bytes 0.4.12","futures 0.1.30","log","mio","tokio-codec","tokio-io","tokio-reactor",][[package]]name = "tokio-uds"version = "0.2.7"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0"dependencies = ["bytes 0.4.12","futures 0.1.30","iovec","libc","log","mio","mio-uds","tokio-codec","tokio-io","tokio-reactor",][[package]]name = "unicode-xid"version = "0.2.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"[[package]]name = "vcpkg"version = "0.2.10"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"[[package]]name = "wasi"version = "0.9.0+wasi-snapshot-preview1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"[[package]]name = "winapi"version = "0.2.8"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"[[package]]name = "winapi"version = "0.3.9"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"dependencies = ["winapi-i686-pc-windows-gnu","winapi-x86_64-pc-windows-gnu",][[package]]name = "winapi-build"version = "0.1.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"[[package]]name = "winapi-i686-pc-windows-gnu"version = "0.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"[[package]]name = "winapi-util"version = "0.1.5"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"dependencies = ["winapi 0.3.9",][[package]]name = "winapi-x86_64-pc-windows-gnu"version = "0.4.0"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"[[package]]name = "ws2_32-sys"version = "0.2.1"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"dependencies = ["winapi 0.2.8","winapi-build",][[package]]name = "yasna"version = "0.1.3"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "3a31ff5b5f5fcc70c5da4fcbdcbd91d541bb95b0b2517a3ca5cdc49fd3692cbb"dependencies = ["bit-vec 0.4.4","num",][[package]]name = "yasna"version = "0.3.2"source = "registry+https://github.com/rust-lang/crates.io-index"checksum = "0de7bff972b4f2a06c85f6d8454b09df153af7e3a4ec2aac81db1b105b684ddb"dependencies = ["bit-vec 0.6.2","num-bigint 0.2.6",]