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 key
pub 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 key
pub 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 signature
Ed25519(SignatureBytes),
/// An RSA signature
RSA { 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>
where
D: 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>
where
S: 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/rfc5208
fn 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 parameters
let 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()))
}
})?;
// Ciphertext
let 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-CBC
ring::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 parameters
writer.next().write_sequence(|writer| {
writer.next().write_oid(&ObjectIdentifier::from_slice(PBES2));
asn1_write_pbes2(writer.next(), rounds as u64, &salt, &iv)
});
// Ciphertext
writer.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 algorithm
writer.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 algorithm
let 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 \n
let 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-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABDLGyfA39
J2FcJygtYqi5ISAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIN+Wjn4+4Fcvl2Jl
KpggT+wCRxpSvtqqpVrQrKN1/A22AAAAkOHDLnYZvYS6H9Q3S3Nk4ri3R2jAZlQlBbUos5
FkHpYgNw65KCWCTXtP7ye2czMC3zjn2r98pJLobsLYQgRiHIv/CUdAdsqbvMPECB+wl/UQ
e+JpiSq66Z6GIt0801skPh20jxOO3F52SoX1IeO5D5PXfZrfSZlw6S8c7bwyp2FHxDewRx
7/wNsnDM0T7nLv/Q==
-----END OPENSSH PRIVATE KEY-----";
const RSA_KEY: &'static str = "-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
NhAAAAAwEAAQAAAQEAuSvQ9m76zhRB4m0BUKPf17lwccj7KQ1Qtse63AOqP/VYItqEH8un
rxPogXNBgrcCEm/ccLZZsyE3qgp3DRQkkqvJhZ6O8VBPsXxjZesRCqoFNCczy+Mf0R/Qmv
Rnpu5+4DDLz0p7vrsRZW9ji/c98KzxeUonWgkplQaCBYLN875WdeUYMGtb1MLfNCEj177j
gZl3CzttLRK3su6dckowXcXYv1gPTPZAwJb49J43o1QhV7+1zdwXvuFM6zuYHdu9ZHSKir
6k1dXOET3/U+LWG5ofAo8oxUWv/7vs6h7MeajwkUeIBOWYtD+wGYRvVpxvj7nyOoWtg+jm
0X6ndnsD+QAAA8irV+ZAq1fmQAAAAAdzc2gtcnNhAAABAQC5K9D2bvrOFEHibQFQo9/XuX
BxyPspDVC2x7rcA6o/9Vgi2oQfy6evE+iBc0GCtwISb9xwtlmzITeqCncNFCSSq8mFno7x
UE+xfGNl6xEKqgU0JzPL4x/RH9Ca9Gem7n7gMMvPSnu+uxFlb2OL9z3wrPF5SidaCSmVBo
IFgs3zvlZ15Rgwa1vUwt80ISPXvuOBmXcLO20tErey7p1ySjBdxdi/WA9M9kDAlvj0njej
VCFXv7XN3Be+4UzrO5gd271kdIqKvqTV1c4RPf9T4tYbmh8CjyjFRa//u+zqHsx5qPCRR4
gE5Zi0P7AZhG9WnG+PufI6ha2D6ObRfqd2ewP5AAAAAwEAAQAAAQAdELqhI/RsSpO45eFR
9hcZtnrm8WQzImrr9dfn1w9vMKSf++rHTuFIQvi48Q10ZiOGH1bbvlPAIVOqdjAPtnyzJR
HhzmyjhjasJlk30zj+kod0kz63HzSMT9EfsYNfmYoCyMYFCKz52EU3xc87Vhi74XmZz0D0
CgIj6TyZftmzC4YJCiwwU8K+29nxBhcbFRxpgwAksFL6PCSQsPl4y7yvXGcX+7lpZD8547
v58q3jIkH1g2tBOusIuaiphDDStVJhVdKA55Z0Kju2kvCqsRIlf1efrq43blRgJFFFCxNZ
8Cpolt4lOHhg+o3ucjILlCOgjDV8dB21YLxmgN5q+xFNAAAAgQC1P+eLUkHDFXnleCEVrW
xL/DFxEyneLQz3IawGdw7cyAb7vxsYrGUvbVUFkxeiv397pDHLZ5U+t5cOYDBZ7G43Mt2g
YfWBuRNvYhHA9Sdf38m5qPA6XCvm51f+FxInwd/kwRKH01RHJuRGsl/4Apu4DqVob8y00V
WTYyV6JBNDkQAAAIEA322lj7ZJXfK/oLhMM/RS+DvaMea1g/q43mdRJFQQso4XRCL6IIVn
oZXFeOxrMIRByVZBw+FSeB6OayWcZMySpJQBo70GdJOc3pJb3js0T+P2XA9+/jwXS58K9a
+IkgLkv9XkfxNGNKyPEEzXC8QQzvjs1LbmO59VLko8ypwHq/cAAACBANQqaULI0qdwa0vm
d3Ae1+k3YLZ0kapSQGVIMT2lkrhKV35tj7HIFpUPa4vitHzcUwtjYhqFezVF+JyPbJ/Fsp
XmEc0g1fFnQp5/SkUwoN2zm8Up52GBelkq2Jk57mOMzWO0QzzNuNV/feJk02b2aE8rrAqP
QR+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 22
let 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 recognized
let 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/FG8YLVoXhsUVZcWaY7iZekMxQ2TAfSVh0LTnRuzsumeLhb
0fh4scIt4C4MLwpGe/u3vj290C28jLkOtysqnIpB4iBUrFNRmEz2YuvjOzkFE8Ju
0l1VrTZ9APhpLZvzT2N7YmTXcLz1yWopCe4KqTHczEP4lfkothxEoACXMaxezt5o
wIYfagDaaH6jXJgJk1SQ5VYrROVpDjjX8/Zg01H1faFQUikYx0M8EwL1fY5B80Hd
6DYSok8kUZGfkZT8HQ54DBgocjSs449CVqkVoQC1aDB+LZpMWovY15q7hFgfQmYD
qulbZRWDxxogS6ui/zUR2IpX7wpQMKKkBS1qdQIDAQABAoIBAQCodpcCKfS2gSzP
uapowY1KvP/FkskkEU18EDiaWWyzi1AzVn5LRo+udT6wEacUAoebLU5K2BaMF+aW
Lr1CKnDWaeA/JIDoMDJk+TaU0i5pyppc5LwXTXvOEpzi6rCzL/O++88nR4AbQ7sm
Uom6KdksotwtGvttJe0ktaUi058qaoFZbels5Fwk5bM5GHDdV6De8uQjSfYV813P
tM/6A5rRVBjC5uY0ocBHxPXkqAdHfJuVk0uApjLrbm6k0M2dg1X5oyhDOf7ZIzAg
QGPgvtsVZkQlyrD1OoCMPwzgULPXTe8SktaP9EGvKdMf5kQOqUstqfyx+E4OZa0A
T82weLjBAoGBAOUChhaLQShL3Vsml/Nuhhw5LsxU7Li34QWM6P5AH0HMtsSncH8X
ULYcUKGbCmmMkVb7GtsrHa4ozy0fjq0Iq9cgufolytlvC0t1vKRsOY6poC2MQgaZ
bqRa05IKwhZdHTr9SUwB/ngtVNWRzzbFKLkn2W5oCpQGStAKqz3LbKstAoGBANsJ
EyrXPbWbG+QWzerCIi6shQl+vzOd3cxqWyWJVaZglCXtlyySV2eKWRW7TcVvaXQr
Nzm/99GNnux3pUCY6szy+9eevjFLLHbd+knzCZWKTZiWZWr503h/ztfFwrMzhoAh
z4nukD/OETugPvtG01c2sxZb/F8LH9KORznhlSlpAoGBAJnqg1J9j3JU4tZTbwcG
fo5ThHeCkINp2owPc70GPbvMqf4sBzjz46QyDaM//9SGzFwocplhNhaKiQvrzMnR
LSVucnCEm/xdXLr/y6S6tEiFCwnx3aJv1uQRw2bBYkcDmBTAjVXPdUcyOHU+BYXr
Jv6ioMlKlel8/SUsNoFWypeVAoGAXhr3Bjf1xlm+0O9PRyZjQ0RR4DN5eHbB/XpQ
cL8hclsaK3V5tuek79JL1f9kOYhVeVi74G7uzTSYbCY3dJp+ftGCjDAirNEMaIGU
cEMgAgSqs/0h06VESwg2WRQZQ57GkbR1E2DQzuj9FG4TwSe700OoC9o3gqon4PHJ
/j9CM8kCgYEAtPJf3xaeqtbiVVzpPAGcuPyajTzU0QHPrXEl8zr/+iSK4Thc1K+c
b9sblB+ssEUQD5IQkhTWcsXdslINQeL77WhIMZ2vBAH8Hcin4jgcLmwUZfpfnnFs
QaChXiDsryJZwsRnruvMRX9nedtqHrgnIsJLTXjppIhGhq5Kg4RQfOU=
-----END RSA PRIVATE KEY-----
";
decode_secret_key(key, None).unwrap();
}
pub const PKCS8_RSA: &'static str = "-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAwBGetHjW+3bDQpVktdemnk7JXgu1NBWUM+ysifYLDBvJ9ttX
GNZSyQKA4v/dNr0FhAJ8I9BuOTjYCy1YfKylhl5D/DiSSXFPsQzERMmGgAlYvU2U
+FTxpBC11EZg69CPVMKKevfoUD+PZA5zB7Hc1dXFfwqFc5249SdbAwD39VTbrOUI
WECvWZs6/ucQxHHXP2O9qxWqhzb/ddOnqsDHUNoeceiNiCf2anNymovrIMjAqq1R
t2UP3f06/Zt7Jx5AxKqS4seFkaDlMAK8JkEDuMDOdKI36raHkKanfx8CnGMSNjFQ
QtvnpD8VSGkDTJN3Qs14vj2wvS477BQXkBKN1QIDAQABAoIBABb6xLMw9f+2ENyJ
hTggagXsxTjkS7TElCu2OFp1PpMfTAWl7oDBO7xi+UqvdCcVbHCD35hlWpqsC2Ui
8sBP46n040ts9UumK/Ox5FWaiuYMuDpF6vnfJ94KRcb0+KmeFVf9wpW9zWS0hhJh
jC+yfwpyfiOZ/ad8imGCaOguGHyYiiwbRf381T/1FlaOGSae88h+O8SKTG1Oahq4
0HZ/KBQf9pij0mfVQhYBzsNu2JsHNx9+DwJkrXT7K9SHBpiBAKisTTCnQmS89GtE
6J2+bq96WgugiM7X6OPnmBmE/q1TgV18OhT+rlvvNi5/n8Z1ag5Xlg1Rtq/bxByP
CeIVHsECgYEA9dX+LQdv/Mg/VGIos2LbpJUhJDj0XWnTRq9Kk2tVzr+9aL5VikEb
09UPIEa2ToL6LjlkDOnyqIMd/WY1W0+9Zf1ttg43S/6Rvv1W8YQde0Nc7QTcuZ1K
9jSSP9hzsa3KZtx0fCtvVHm+ac9fP6u80tqumbiD2F0cnCZcSxOb4+UCgYEAyAKJ
70nNKegH4rTCStAqR7WGAsdPE3hBsC814jguplCpb4TwID+U78Xxu0DQF8WtVJ10
SJuR0R2q4L9uYWpo0MxdawSK5s9Am27MtJL0mkFQX0QiM7hSZ3oqimsdUdXwxCGg
oktxCUUHDIPJNVd4Xjg0JTh4UZT6WK9hl1zLQzECgYEAiZRCFGc2KCzVLF9m0cXA
kGIZUxFAyMqBv+w3+zq1oegyk1z5uE7pyOpS9cg9HME2TAo4UPXYpLAEZ5z8vWZp
45sp/BoGnlQQsudK8gzzBtnTNp5i/MnnetQ/CNYVIVnWjSxRUHBqdMdRZhv0/Uga
e5KA5myZ9MtfSJA7VJTbyHUCgYBCcS13M1IXaMAt3JRqm+pftfqVs7YeJqXTrGs/
AiDlGQigRk4quFR2rpAV/3rhWsawxDmb4So4iJ16Wb2GWP4G1sz1vyWRdSnmOJGC
LwtYrvfPHegqvEGLpHa7UsgDpol77hvZriwXwzmLO8A8mxkeW5dfAfpeR5o+mcxW
pvnTEQKBgQCKx6Ln0ku6jDyuDzA9xV2/PET5D75X61R2yhdxi8zurY/5Qon3OWzk
jn/nHT3AZghGngOnzyv9wPMKt9BTHyTB6DlB6bRVLDkmNqZh5Wi8U1/IjyNYI0t2
xV/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,ENCRYPTED
DEK-Info: AES-128-CBC,80E4FCAD049EE007CCE1C65D52CDB87A
ZKBKtex8+DA/d08TTPp4vY8RV+r+1nUC1La+r0dSiXsfunRNDPcYhHbyA/Fdr9kQ
+d1/E3cEb0k2nq7xYyMzy8hpNp/uHu7UfllGdaBusiPjHR+feg6AQfbM0FWpdGzo
9l/Vho5Ocw8abQq1Q9aPW5QQXBURC7HtCQXbpuYjUAQBeea1LzPCw6UIF80GUUkY
1AycXxVfx1AeURAKTZR4hsxC5pqI4yhAvVNXxP+tTTa9NE8lOP0yqVNurfIqyAnp
5ELMwNdHXZyUcT+EH5PsC69ocQgEZqLs0chvke62woMOjeSpsW5cIjGohW9lOD1f
nJkECVZ50kE0SDvcL4Y338tHwMt7wdwdj1dkAWSUjAJT4ShjqV/TzaLAiNAyRxLl
cm3mAccaFIIBZG/bPLGI0B5+mf9VExXGJrbGlvURhtE3nwmjLg1vT8lVfqbyL3a+
0tFvmDYn71L97t/3hcD2tVnKLv9g8+/OCsUAk3+/0eS7D6GpmlOMRHdLLUHc4SOm
bIDT/dE6MjsCSm7n/JkTb8P+Ta1Hp94dUnX4pfjzZ+O8V1H8wv7QW5KsuJhJ8cn4
eS3BEgNH1I4FCCjLsZdWve9ehV3/19WXh+BF4WXFq9b3plmfJgTiZslvjy4dgThm
OhEK44+fN1UhzguofxTR4Maz7lcehQxGAxp14hf1EnaAEt3LVjEPEShgK5dx1Ftu
LWFz9nR4vZcMsaiszElrevqMhPQHXY7cnWqBenkMfkdcQDoZjKvV86K98kBIDMu+
kf855vqRF8b2n/6HPdm3eqFh/F410nSB0bBSglUfyOZH1nS+cs79RQZEF9fNUmpH
EPQtQ/PALohicj9Vh7rRaMKpsORdC8/Ahh20s01xL6siZ334ka3BLYT94UG796/C
4K1S2kPdUP8POJ2HhaK2l6qaG8tcEX7HbwwZeKiEHVNvWuIGQO9TiDONLycp9x4y
kNM3sv2pI7vEhs7d2NapWgNha1RcTSv0CQ6Th/qhGo73LBpVmKwombVImHAyMGAE
aVF32OycVd9c9tDgW5KdhWedbeaxD6qkSs0no71083kYIS7c6iC1R3ZeufEkMhmx
dwrciWTJ+ZAk6rS975onKz6mo/4PytcCY7Df/6xUxHF3iJCnuK8hNpLdJcdOiqEK
zj/d5YGyw3J2r+NrlV1gs3FyvR3eMCWWH2gpIQISBpnEANY40PxA/ogH+nCUvI/O
n8m437ZeLTg6lnPqsE4nlk2hUEwRdy/SVaQURbn7YlcYIt0e81r5sBXb4MXkLrf0
XRWmpSggdcaaMuXi7nVSdkgCMjGP7epS7HsfP46OrTtJLHn5LxvdOEaW53nPOVQg
/PlVfDbwWl8adE3i3PDQOw9jhYXnYS3sv4R8M8y2GYEXbINrTJyUGrlNggKFS6oh
Hjgt0gsM2N/D8vBrQwnRtyymRnFd4dXFEYKAyt+vk0sa36eLfl0z6bWzIchkJbdu
raMODVc+NiJE0Qe6bwAi4HSpJ0qw2lKwVHYB8cdnNVv13acApod326/9itdbb3lt
KJaj7gc0n6gmKY6r0/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,ENCRYPTED
DEK-Info: AES-128-CBC,EA77308AAF46981303D8C44D548D097E
QR18hXmAgGehm1QMMYGF34PAtBpTj+8/ZPFx2zZxir7pzDpfYoNAIf/fzLsW1ruG
0xo/ZK/T3/TpMgjmLsCR6q+KU4jmCcCqWQIGWYJt9ljFI5y/CXr5uqP3DKcqtdxQ
fbBAfXJ8ITF+Tj0Cljm2S1KYHor+mkil5Lf/ZNiHxcLfoI3xRnpd+2cemN9Ly9eY
HNTbeWbLosfjwdfPJNWFNV5flm/j49klx/UhXhr5HNFNgp/MlTrvkH4rBt4wYPpE
cZBykt4Fo1KGl95pT22inGxQEXVHF1Cfzrf5doYWxjiRTmfhpPSz/Tt0ev3+jIb8
Htx6N8tNBoVxwCiQb7jj3XNim2OGohIp5vgW9sh6RDfIvr1jphVOgCTFKSo37xk0
156EoCVo3VcLf+p0/QitbUHR+RGW/PvUJV/wFR5ShYqjI+N2iPhkD24kftJ/MjPt
AAwCm/GYoYjGDhIzQMB+FETZKU5kz23MQtZFbYjzkcI/RE87c4fkToekNCdQrsoZ
wG0Ne2CxrwwEnipHCqT4qY+lZB9EbqQgbWOXJgxA7lfznBFjdSX7uDc/mnIt9Y6B
MZRXH3PTfotHlHMe+Ypt5lfPBi/nruOl5wLo3L4kY5pUyqR0cXKNycIJZb/pJAnE
ryIb59pZP7njvoHzRqnC9dycnTFW3geK5LU+4+JMUS32F636aorunRCl6IBmVQHL
uZ+ue714fn/Sn6H4dw6IH1HMDG1hr8ozP4sNUCiAQ05LsjDMGTdrUsr2iBBpkQhu
VhUDZy9g/5XF1EgiMbZahmqi5WaJ5K75ToINHb7RjOE7MEiuZ+RPpmYLE0HXyn9X
HTx0ZGr022dDI6nkvUm6OvEwLUUmmGKRHKe0y1EdICGNV+HWqnlhGDbLWeMyUcIY
M6Zh9Dw3WXD3kROf5MrJ6n9MDIXx9jy7nmBh7m6zKjBVIw94TE0dsRcWb0O1IoqS
zLQ6ihno+KsQHDyMVLEUz1TuE52rIpBmqexDm3PdDfCgsNdBKP6QSTcoqcfHKeex
K93FWgSlvFFQQAkJumJJ+B7ZWnK+2pdjdtWwTpflAKNqc8t//WmjWZzCtbhTHCXV
1dnMk7azWltBAuXnjW+OqmuAzyh3ayKgqfW66mzSuyQNa1KqFhqpJxOG7IHvxVfQ
kYeSpqODnL87Zd/dU8s0lOxz3/ymtjPMHlOZ/nHNqW90IIeUwWJKJ46Kv6zXqM1t
MeD1lvysBbU9rmcUdop0D3MOgGpKkinR5gy4pUsARBiz4WhIm8muZFIObWes/GDS
zmmkQRO1IcfXKAHbq/OdwbLBm4vM9nk8vPfszoEQCnfOSd7aWrLRjDR+q2RnzNzh
K+fodaJ864JFIfB/A+aVviVWvBSt0eEbEawhTmNPerMrAQ8tRRhmNxqlDP4gOczi
iKUmK5recsXk5us5Ik7peIR/f9GAghpoJkF0HrHio47SfABuK30pzcj62uNWGljS
3d9UQLCepT6RiPFhks/lgimbtSoiJHql1H9Q/3q4MuO2PuG7FXzlTnui3zGw/Vvy
br8gXU8KyiY9sZVbmplRPF+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-----
MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITo1O0b8YrS0CAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBtLH4T1KOfo1GGr7salhR8BIIE
0KN9ednYwcTGSX3hg7fROhTw7JAJ1D4IdT1fsoGeNu2BFuIgF3cthGHe6S5zceI2
MpkfwvHbsOlDFWMUIAb/VY8/iYxhNmd5J6NStMYRC9NC0fVzOmrJqE1wITqxtORx
IkzqkgFUbaaiFFQPepsh5CvQfAgGEWV329SsTOKIgyTj97RxfZIKA+TR5J5g2dJY
j346SvHhSxJ4Jc0asccgMb0HGh9UUDzDSql0OIdbnZW5KzYJPOx+aDqnpbz7UzY/
P8N0w/pEiGmkdkNyvGsdttcjFpOWlLnLDhtLx8dDwi/sbEYHtpMzsYC9jPn3hnds
TcotqjoSZ31O6rJD4z18FOQb4iZs3MohwEdDd9XKblTfYKM62aQJWH6cVQcg+1C7
jX9l2wmyK26Tkkl5Qg/qSfzrCveke5muZgZkFwL0GCcgPJ8RixSB4GOdSMa/hAMU
kvFAtoV2GluIgmSe1pG5cNMhurxM1dPPf4WnD+9hkFFSsMkTAuxDZIdDk3FA8zof
Yhv0ZTfvT6V+vgH3Hv7Tqcxomy5Qr3tj5vvAqqDU6k7fC4FvkxDh2mG5ovWvc4Nb
Xv8sed0LGpYitIOMldu6650LoZAqJVv5N4cAA2Edqldf7S2Iz1QnA/usXkQd4tLa
Z80+sDNv9eCVkfaJ6kOVLk/ghLdXWJYRLenfQZtVUXrPkaPpNXgD0dlaTN8KuvML
Uw/UGa+4ybnPsdVflI0YkJKbxouhp4iB4S5ACAwqHVmsH5GRnujf10qLoS7RjDAl
o/wSHxdT9BECp7TT8ID65u2mlJvH13iJbktPczGXt07nBiBse6OxsClfBtHkRLzE
QF6UMEXsJnIIMRfrZQnduC8FUOkfPOSXc8r9SeZ3GhfbV/DmWZvFPCpjzKYPsM5+
N8Bw/iZ7NIH4xzNOgwdp5BzjH9hRtCt4sUKVVlWfEDtTnkHNOusQGKu7HkBF87YZ
RN/Nd3gvHob668JOcGchcOzcsqsgzhGMD8+G9T9oZkFCYtwUXQU2XjMN0R4VtQgZ
rAxWyQau9xXMGyDC67gQ5xSn+oqMK0HmoW8jh2LG/cUowHFAkUxdzGadnjGhMOI2
zwNJPIjF93eDF/+zW5E1l0iGdiYyHkJbWSvcCuvTwma9FIDB45vOh5mSR+YjjSM5
nq3THSWNi7Cxqz12Q1+i9pz92T2myYKBBtu1WDh+2KOn5DUkfEadY5SsIu/Rb7ub
5FBihk2RN3y/iZk+36I69HgGg1OElYjps3D+A9AjVby10zxxLAz8U28YqJZm4wA/
T0HLxBiVw+rsHmLP79KvsT2+b4Diqih+VTXouPWC/W+lELYKSlqnJCat77IxgM9e
YIhzD47OgWl33GJ/R10+RDoDvY4koYE+V5NLglEhbwjloo9Ryv5ywBJNS7mfXMsK
/uf+l2AscZTZ1mhtL38efTQCIRjyFHc3V31DI0UdETADi+/Omz+bXu0D5VvX+7c6
b1iVZKpJw8KUjzeUV8yOZhvGu3LrQbhkTPVYL555iP1KN0Eya88ra+FUKMwLgjYr
JkUx4iad4dTsGPodwEP/Y9oX/Qk3ZQr+REZ8lg6IBoKKqqrQeBJ9gkm1jfKE6Xkc
Cog3JMeTrb3LiPHgN6gU2P30MRp6L1j1J/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,
/// SHA1
SHA1,
}
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.2
let 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.2
let 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 parameters
let 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())
}
})?;
// Ciphertext
let 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 algorithm
let 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 OID
writer.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 OID
writer.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-CBC
pbkdf2_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 parameters
writer.next().write_sequence(|writer| {
writer
.next()
.write_oid(&ObjectIdentifier::from_slice(PBES2));
asn1_write_pbes2(writer.next(), rounds as u64, &salt, &iv)
});
// Ciphertext
writer.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 algorithm
writer.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 keys
for _ in 0..nkeys {
position.read_string()?;
}
// Read all secret keys
let 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-1
dmp1.checked_rem(&d, &p1, &mut ctx)?;
let mut dmq1 = openssl::bn::BigNum::new()?; // d mod q-1
dmq1.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 AES128
Aes128Cbc([u8; 16]),
/// Key for AES256
Aes256Cbc([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>
where
S: 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 length
self.buf.clear();
self.buf.resize(4);
self.s.read_exact(&mut self.buf).await?;
// Reading the rest of the buffer
let len = BigEndian::read_u32(&self.buf) as usize;
self.buf.clear();
self.buf.resize(len);
self.s.read_exact(&mut self.buf).await?;
// respond
writebuf.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 identities
if 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 request
let 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 identity
if let Ok(true) = self.add_key(r, false, writebuf).await {
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(18) if !is_locked => {
// remove identity
if let Ok(true) = self.remove_identity(r) {
writebuf.push(msg::SUCCESS)
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(19) if !is_locked => {
// remove all identities
if let Ok(mut keys) = self.keys.0.write() {
keys.clear();
writebuf.push(msg::SUCCESS)
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(22) if !is_locked => {
// lock
if let Ok(()) = self.lock(r) {
writebuf.push(msg::SUCCESS)
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(23) if is_locked => {
// unlock
if let Ok(true) = self.unlock(r) {
writebuf.push(msg::SUCCESS)
} else {
writebuf.push(msg::FAILURE)
}
}
Ok(25) if !is_locked => {
// add identity constrained
if let Ok(true) = self.add_key(r, true, writebuf).await {
} else {
writebuf.push(msg::FAILURE)
}
}
_ => {
// Message not understood
writebuf.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 constraints
Extensions { 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.1
impl<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 message
self.stream.write_all(&self.buf).await?;
self.stream.flush().await?;
// Reading the length
self.buf.clear();
self.buf.resize(4);
self.stream.read_exact(&mut self.buf).await?;
// Reading the rest of the buffer
let 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.4
pub seqn: Wrapping<u32>,
}
impl SSHBuffer {
pub fn new() -> Self {
SSHBuffer {
buffer: CryptoVec::new(),
len: 0,
bytes: 0,
seqn: Wrapping(0),
}
}
pub fn send_ssh_id(&mut self, id: &[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.2
if data.len() as u32 <= channel.sender_window_size {
channel.sender_window_size -= data.len() as u32;
}
if channel.sender_window_size < target / 2 {
debug!(
"sender_window_size {:?}, target {:?}",
channel.sender_window_size, target
);
push_packet!(self.write, {
self.write.push(msg::CHANNEL_WINDOW_ADJUST);
self.write.push_u32_be(channel.recipient_channel);
self.write.push_u32_be(target - channel.sender_window_size);
});
channel.sender_window_size = target;
return true;
}
}
false
}
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 channel
pub 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 to
type 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>
where
R: 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");
// Shutdown
stream.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 response
exchange
.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 ephemeral
buffer.extend_ssh_string(&kexdhdone.exchange.server_ephemeral);
// Hash signature
debug!("signing with key {:?}", kexdhdone.key);
debug!("hash: {:?}", hash);
debug!("key: {:?}", config.keys[kexdhdone.key]);
config.keys[kexdhdone.key].add_signature(&mut buffer, &hash)?;
cipher.write(&buffer, write_buffer);
cipher.write(&[msg::NEWKEYS], write_buffer);
Ok(hash)
});
Ok(Kex::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 request
if 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-5
let 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 service
Err(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 empty
write.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.1
let 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_TYPE
enc.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; // unimplemented
use 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-server
r.read_string()?; // languages server-to-client
let 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); // cookie
buf.extend_list(prefs.kex.iter()); // kex algo
buf.extend_list(prefs.key.iter());
buf.extend_list(prefs.cipher.iter()); // cipher client to server
buf.extend_list(prefs.cipher.iter()); // cipher server to client
buf.extend_list(prefs.mac.iter()); // mac client to server
buf.extend_list(prefs.mac.iter()); // mac server to client
buf.extend_list(prefs.compression.iter()); // compress client to server
buf.extend_list(prefs.compression.iter()); // compress server to client
buf.write_empty_list(); // languages client to server
buf.write_empty_list(); // languagesserver to client
buf.push(0); // doesn't follow
buf.extend(&[0, 0, 0, 0]); // reserved
Ok(())
}
// Copyright 2016 Pierre-Étienne Meunier
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// https://tools.ietf.org/html/rfc4253#section-12
pub const DISCONNECT: u8 = 1;
#[allow(dead_code)]
pub const IGNORE: u8 = 2;
#[allow(dead_code)]
pub const UNIMPLEMENTED: u8 = 3;
#[allow(dead_code)]
pub const DEBUG: u8 = 4;
pub const SERVICE_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.1
pub const KEX_ECDH_INIT: u8 = 30;
pub const KEX_ECDH_REPLY: u8 = 31;
// https://tools.ietf.org/html/rfc4250#section-4.1.2
pub const USERAUTH_REQUEST: u8 = 50;
pub const USERAUTH_FAILURE: u8 = 51;
pub const USERAUTH_SUCCESS: u8 = 52;
pub const USERAUTH_BANNER: u8 = 53;
pub const USERAUTH_PK_OK: u8 = 60;
// https://tools.ietf.org/html/rfc4256#section-5
pub const USERAUTH_INFO_REQUEST: u8 = 60;
pub const USERAUTH_INFO_RESPONSE: u8 = 61;
// https://tools.ietf.org/html/rfc4254#section-9
pub const GLOBAL_REQUEST: u8 = 80;
pub const REQUEST_SUCCESS: u8 = 81;
pub const REQUEST_FAILURE: u8 = 82;
pub const CHANNEL_OPEN: u8 = 90;
pub const CHANNEL_OPEN_CONFIRMATION: u8 = 91;
pub const CHANNEL_OPEN_FAILURE: u8 = 92;
pub const CHANNEL_WINDOW_ADJUST: u8 = 93;
pub const CHANNEL_DATA: u8 = 94;
pub const CHANNEL_EXTENDED_DATA: u8 = 95;
pub const CHANNEL_EOF: u8 = 96;
pub const CHANNEL_CLOSE: u8 = 97;
pub const CHANNEL_REQUEST: u8 = 98;
pub const CHANNEL_SUCCESS: u8 = 99;
pub const CHANNEL_FAILURE: u8 = 100;
// 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-9
Limits {
rekey_write_limit: 1 << 30, // 1 Gb
rekey_read_limit: 1 << 30, // 1 Gb
rekey_time_limit: std::time::Duration::from_secs(3600),
}
}
}
pub use auth::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.html
impl 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.2
BUFFER.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 reply
enc.write.push_u32_be(col_width);
enc.write.push_u32_be(row_height);
enc.write.push_u32_be(pix_width);
enc.write.push_u32_be(pix_height);
});
}
}
}
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>
where
H: Handler + Send + 'static,
R: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
// Writing SSH id.
let mut write_buffer = SSHBuffer::new();
write_buffer.send_ssh_id(config.as_ref().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?;
// Shutdown
buffer.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 response
exchange
.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_REPLY
session.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 request
if 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_REPLY
enc.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 probe
debug!("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 probe
self.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.4
buffer.seqn += Wrapping(1);
buffer.len = 0;
// Remove the padding
buffer.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.1
assert!(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.4
buffer.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=HEAD
use 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> Signer
for 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=";
};
in
with 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"] }
# Thrussh
A 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).
## Contributing
We 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 Reporting
Please 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 License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. 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 by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright 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 Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable 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, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this 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 licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks 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 grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the 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 this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification 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 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.
[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",
]