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 = ["[email protected] <[email protected]>"]
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 <[email protected]>"]
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 <[email protected]>"]
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 {