VK5CSP727IVAYHHI7FZJLES6F4YCR3S5GDSZHVPAR5XSPINGZEVAC
AEIVL6S4SXV6CCOEHFQI7HTEULFIQBEBIBPBLCJ25374JYRXHX2QC
LDEEJH5L2QGDPOAASR6FZ7NYHZF56ZPUYMXVCKXCRGTICVCJU4EAC
NLPZS76WC64DN5RQEOB45KWXUSEP4VJOVEBR6OMCMFX4MYIOMKSAC
LZA3GTZAFVP3RQD4HO7C5F2R5ZOLGBOUPIME4RF47C2GCJG25Q4AC
G3FNNIIUCVMPJUICDYPXVS6BP6225LIQZOV5MRNEGCBC5QY3N6NAC
7FRJYUI62VW257VVFQXND6OKSAILVTHGEJCXFE6CG6FIOIUTDVYAC
2WEO7OZLWJJPUYK4WXLT5FD46G2MAEIHEYMDW5GASCBUNKOPXCVAC
Q323RFJSTFYUJ5FPTKT4NI7DK3KDC3O4YLBDEYWKEYCA7G276SUQC
2NUPA5PYUATBSGP3JRSL44SL6ICHH7IG5H4CBIRYC4WD4DBSFIVQC
ATOFE4ZXBULBMY55LCTFDORZR6AEPXR6RIGDFR4JKPBJ66RCVDFQC
MCS77Y4VJGB6TU2HOLASGSRW4B6MT74XABD4KYALIRS54GGN2DDQC
MFMCIUMJUYCV2GW5P25D5753YBYXWIMLWKKWX4PABEM7ACUIBGWAC
FT67GGO45RNEBZZXT5RRIQOOQKZ5IW2CJXORMCCGITC7VOBGKDJAC
OQZGSEWMQXOSEDB6ACSY7NTNZYIYX4ECZWXJ6JD5I7FC64JHTLIQC
NHOSLQGG4CIWBE7VKL5MB7PSY3RZ5IVDFENMGZG6X755GGZ6B3VQC
KGIUIQYIBBUEGBOQIFYJJNZGGSPV3KERBPYTKKCOBKZZ5CHIJ24AC
HDEDMPBT6TKIKQ67T2UYC7QEKF7PG5I6Y4CMRPBDACFY4S3XEWZQC
#[error("Environment variable `{0}` not found")]
EnvVar(&'static str),
#[error("Unable to connect to ssh-agent. The environment variable `SSH_AUTH_SOCK` \
was set, but it points to a nonexistent file or directory.")]
BadAuthSock,
}
impl agent_key::Signature for Signature {
type Error = Error;
fn read(buf: &CryptoVec) -> Result<Self, Self::Error> {
let mut r = buf.reader(1);
let mut resp = r.read_string()?.reader(0);
let typ = resp.read_string()?;
let sig = resp.read_string()?;
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()),
}
}
}
impl agent_key::Private for KeyPair {
type Error = Error;
fn read(r: &mut Position) -> Result<Option<(Vec<u8>, Self)>, Self::Error> {
let t = r.read_string()?;
match t {
b"ssh-ed25519" => {
let public_ = r.read_string()?;
let concat = r.read_string()?;
let _comment = r.read_string()?;
if &concat[32..64] != public_ {
return Ok(None);
}
let mut public = ed25519::PublicKey::new_zeroed();
let mut secret = ed25519::SecretKey::new_zeroed();
public.key.clone_from_slice(&public_[..32]);
secret.key.clone_from_slice(&concat[..]);
Ok(Some((public_.to_vec(), KeyPair::Ed25519(secret))))
}
#[cfg(feature = "openssl")]
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 mut buf = CryptoVec::new();
buf.extend_ssh_string(b"ssh-rsa");
buf.extend_ssh_mpint(&e);
buf.extend_ssh_mpint(&n);
let blob = buf.to_vec();
Ok(Some((
blob,
KeyPair::RSA {
key,
hash: SignatureHash::SHA2_256,
},
)))
}
_ => Ok(None),
}
}
fn write(&self, buf: &mut CryptoVec) -> Result<(), Self::Error> {
match *self {
KeyPair::Ed25519(ref secret) => {
buf.extend_ssh_string(b"ssh-ed25519");
let public = &secret.key[32..];
buf.extend_ssh_string(public);
buf.push_u32_be(64);
buf.extend(&secret.key);
buf.extend_ssh_string(b"");
}
#[cfg(feature = "openssl")]
KeyPair::RSA { ref key, .. } => {
buf.extend_ssh_string(b"ssh-rsa");
buf.extend_ssh_mpint(&key.n().to_vec());
buf.extend_ssh_mpint(&key.e().to_vec());
buf.extend_ssh_mpint(&key.d().to_vec());
if let Some(iqmp) = key.iqmp() {
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)?;
buf.extend_ssh_mpint(&iqmp.to_vec());
}
buf.extend_ssh_mpint(&key.p().unwrap().to_vec());
buf.extend_ssh_mpint(&key.q().unwrap().to_vec());
buf.extend_ssh_string(b"");
}
}
Ok(())
}
fn write_signature<Bytes: AsRef<[u8]>>(
&self,
buf: &mut CryptoVec,
to_sign: Bytes,
) -> Result<(), Self::Error> {
self.add_signature(buf, to_sign)
}
}
impl agent_key::Public for PublicKey {
type Error = Error;
fn read(r: &mut Position) -> Result<Option<Self>, Self::Error> {
let t = r.read_string()?;
debug!("t = {:?}", std::str::from_utf8(t));
match t {
#[cfg(feature = "openssl")]
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;
Ok(Some(PublicKey::RSA {
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 = ed25519::PublicKey::new_zeroed();
p.key.clone_from_slice(r.read_string()?);
Ok(Some(PublicKey::Ed25519(p)))
}
t => {
info!("Unsupported key type: {:?}", std::str::from_utf8(t));
Ok(None)
}
}
}
fn write_blob(&self, buf: &mut CryptoVec) {
match *self {
#[cfg(feature = "openssl")]
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);
}
}
}
fn hash(&self) -> u32 {
match self {
#[cfg(feature = "openssl")]
PublicKey::RSA { hash, .. } => match hash {
SignatureHash::SHA2_256 => 2,
SignatureHash::SHA2_512 => 4,
SignatureHash::SHA1 => 0,
},
_ => 0,
}
}
[package]
name = "thrussh-encoding"
version = "0.1.0"
edition = "2018"
authors = ["Pierre-Étienne Meunier <pe@pijul.org>"]
description = "Encoding primitives"
keywords = ["ssh"]
license = "Apache-2.0"
[dependencies]
byteorder = "1.4"
cryptovec = "0.6.0"
thiserror = "1.0"
#[derive(Clone)]
struct KeyStore(Arc<RwLock<HashMap<Vec<u8>, (Arc<key::KeyPair>, SystemTime, Vec<Constraint>)>>>);
// NOTE: need to implement this since the derived version will require `Key: Clone` which is unecessary.
impl<Key> Clone for KeyStore<Key> {
fn clone(&self) -> Self {
KeyStore(self.0.clone())
}
}
pub trait Agent: Clone + Send + 'static {
fn confirm(
self,
_pk: Arc<key::KeyPair>,
) -> Box<dyn Future<Output = (Self, bool)> + Unpin + Send> {
pub trait Agent<Key>: Clone + Send + 'static {
fn confirm(self, _pk: Arc<Key>) -> Box<dyn Future<Output = (Self, bool)> + Unpin + Send> {
impl<S: AsyncRead + AsyncWrite + Send + Unpin + 'static, A: Agent + Send + 'static>
Connection<S, A>
impl<K, S, A> Connection<K, S, A>
where
K: Private + Send + Sync + 'static,
K::Error: std::error::Error + Send + Sync + 'static,
S: AsyncRead + AsyncWrite + Send + Unpin + 'static,
A: Agent<K> + Send + 'static,
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))
}
#[cfg(feature = "openssl")]
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 (blob, key) = match K::read(&mut r).map_err(|err| Error::Private(Box::new(err)))? {
Some((blob, key)) => (blob, key),
None => return Ok(false),
#[derive(Debug, Error)]
pub enum Error {
/// Agent protocol error
#[error("Agent protocol error")]
AgentProtocolError,
#[error("Agent failure")]
AgentFailure,
#[error(
"Unable to connect to ssh-agent. The environment variable `SSH_AUTH_SOCK` \
was set, but it points to a nonexistent file or directory."
)]
BadAuthSock,
#[error(transparent)]
Encoding(#[from] thrussh_encoding::Error),
#[error("Environment variable `{0}` not found")]
EnvVar(&'static str),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Private(Box<dyn std::error::Error + Send + Sync + 'static>),
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"");
}
#[cfg(feature = "openssl")]
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"");
}
}
key.write(&mut self.buf)
.map_err(|err| Error::Private(Box::new(err)))?;
let t = r.read_string()?;
debug!("t = {:?}", std::str::from_utf8(t));
match t {
#[cfg(feature = "openssl")]
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 => {
info!("Unsupported key type: {:?}", std::str::from_utf8(t))
}
if let Some(pk) = K::read(&mut r).map_err(|err| Error::Public(Box::new(err)))? {
keys.push(pk);
let hash = match public {
#[cfg(feature = "openssl")]
PublicKey::RSA { hash, .. } => match hash {
SignatureHash::SHA2_256 => 2,
SignatureHash::SHA2_512 => 4,
SignatureHash::SHA1 => 0,
},
_ => 0,
};
let hash = public.hash();
) -> impl futures::Future<Output = (Self, Result<crate::signature::Signature, Error>)> {
) -> impl futures::Future<Output = (Self, Result<Sig, Error>)>
where
K: Public + fmt::Debug,
Sig: Signature,
Sig::Error: std::error::Error + Send + Sync +
'static,
{
let as_sig = |buf: &CryptoVec| -> Result<crate::signature::Signature, Error> {
let mut r = 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()),
}
};
let sig = as_sig(&self.buf);
let sig = Sig::read(&self.buf).map_err(|err| Error::Signature(Box::new(err)));
}
}
fn key_blob(public: &key::PublicKey, buf: &mut CryptoVec) {
match *public {
#[cfg(feature = "openssl")]
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-agent"
version = "0.1.0"
edition = "2018"
authors = ["Pierre-Étienne Meunier <pe@pijul.org>"]
description = "SSH agent client and server"
keywords = ["ssh"]
license = "Apache-2.0"
[features]
default = [ "tokio-agent" ]
tokio-agent = [ "tokio" ]
smol-agent = [ "smol" ]
[dependencies]
async-trait = "0.1"
byteorder = "1.4"
cryptovec = "0.6.0"
data-encoding = "2.3"
futures = "0.3"
log = "0.4"
smol = { version = "1.2", optional = true }
thiserror = "1.0"
thrussh-encoding = "0.1"
tokio = { version = "1.6", features = [ "io-util", "net", "rt-multi-thread", "time" ], optional = true }