Skip to content

Commit

Permalink
Revert "Use ssh-key instead of local key handling (Eugeny#368)"
Browse files Browse the repository at this point in the history
This reverts commit ce6cd79.
  • Loading branch information
gleason-m committed Feb 6, 2025
1 parent ef5aa9f commit 86da6e1
Show file tree
Hide file tree
Showing 42 changed files with 2,532 additions and 891 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@
target
Cargo.lock
.cargo-ok
ca-test*
20 changes: 3 additions & 17 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
[workspace]
members = [
"russh-keys",
"russh",
"russh-config",
"cryptovec",
"pageant",
"russh-util",
]
members = ["russh-keys", "russh", "russh-config", "cryptovec", "pageant", "russh-util"]
resolver = "2"

[patch.crates-io]
Expand All @@ -23,19 +16,12 @@ digest = "0.10"
futures = "0.3"
hmac = "0.12"
log = "0.4"
openssl = { version = "0.10" }
rand = "0.8"
sha1 = { version = "0.10", features = ["oid"] }
sha2 = { version = "0.10", features = ["oid"] }
signature = "2.2"
ssh-encoding = "0.2"
ssh-key = { version = "0.6", features = [
"ed25519",
"rsa",
"p256",
"p384",
"p521",
"encryption",
] }
ssh-key = { version = "0.6", features = ["ed25519", "rsa", "encryption"] }
thiserror = "1.0"
tokio = { version = "1.17.0" }
tokio-stream = { version = "0.1", features = ["net", "sync"] }
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,12 @@ This is a fork of [Thrussh](https://nest.pijul.com/pijul/thrussh) by Pierre-Éti
* `publickey`
* `keyboard-interactive`
* `none`
* OpenSSH certificates
* OpenSSH certificates (client only ✨)
* Dependency updates
* OpenSSH keepalive request handling ✨
* OpenSSH agent forwarding channels ✨
* OpenSSH `server-sig-algs` extension ✨
* `openssl` dependency is optional ✨

## Safety

Expand Down
6 changes: 5 additions & 1 deletion russh-keys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ inout = { version = "0.1", features = ["std"] }
log = { workspace = true }
md5 = "0.7"
num-integer = "0.1"
openssl = { workspace = true, optional = true }
p256 = "0.13"
p384 = "0.13"
p521 = "0.13"
Expand All @@ -47,7 +48,6 @@ sec1 = { version = "0.7", features = ["pkcs8"] }
serde = { version = "1.0", features = ["derive"] }
sha1 = { workspace = true }
sha2 = { workspace = true }
signature = { workspace = true }
spki = "0.7"
ssh-encoding = { workspace = true }
ssh-key = { workspace = true }
Expand All @@ -62,6 +62,7 @@ tokio = { workspace = true, features = [
] }

[features]
vendored-openssl = ["openssl", "openssl/vendored"]
legacy-ed25519-pkcs8-parser = ["yasna"]

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
Expand All @@ -82,3 +83,6 @@ pageant = { version = "0.0.1-beta.3", path = "../pageant" }
env_logger = "0.11"
tempdir = "0.3"
tokio = { workspace = true, features = ["test-util", "macros", "process"] }

[package.metadata.docs.rs]
features = ["openssl"]
204 changes: 146 additions & 58 deletions russh-keys/src/agent/client.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use core::str;
use std::convert::TryFrom;

use byteorder::{BigEndian, ByteOrder};
use log::debug;
use russh_cryptovec::CryptoVec;
use ssh_key::{Algorithm, HashAlg, PrivateKey, PublicKey, Signature};
use tokio;
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};

use super::{msg, Constraint};
use crate::encoding::{Encoding, Reader};
use crate::helpers::EncodedExt;
use crate::{key, Error};
use crate::key::{PublicKey, SignatureHash};
use crate::{key, protocol, Error, PublicKeyBase64};

pub trait AgentStream: AsyncRead + AsyncWrite {}

Expand Down Expand Up @@ -143,7 +142,7 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
/// constraints to apply when using the key to sign.
pub async fn add_identity(
&mut self,
key: &PrivateKey,
key: &key::KeyPair,
constraints: &[Constraint],
) -> Result<(), Error> {
// See IETF draft-miller-ssh-agent-13, section 3.2 for format.
Expand All @@ -155,10 +154,30 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
} else {
self.buf.push(msg::ADD_ID_CONSTRAINED)
}

self.buf.extend(key.key_data().encoded()?.as_slice());
self.buf.extend_ssh_string(&[]); // comment field

match *key {
key::KeyPair::Ed25519(ref pair) => {
self.buf.extend_ssh_string(b"ssh-ed25519");
self.buf.extend_ssh_string(pair.verifying_key().as_bytes());
self.buf.push_u32_be(64);
self.buf.extend(pair.to_bytes().as_slice());
self.buf.extend(pair.verifying_key().as_bytes());
self.buf.extend_ssh_string(b"");
}
#[allow(clippy::unwrap_used)] // key is known to be private
key::KeyPair::RSA { ref key, .. } => {
self.buf.extend_ssh_string(b"ssh-rsa");
self.buf
.extend_ssh(&protocol::RsaPrivateKey::try_from(key)?);
}
key::KeyPair::EC { ref key } => {
self.buf.extend_ssh_string(key.algorithm().as_bytes());
self.buf.extend_ssh_string(key.ident().as_bytes());
self.buf
.extend_ssh_string(&key.to_public_key().to_sec1_bytes());
self.buf.extend_ssh_mpint(&key.to_secret_bytes());
self.buf.extend_ssh_string(b""); // comment
}
}
if !constraints.is_empty() {
for cons in constraints {
match *cons {
Expand Down Expand Up @@ -273,54 +292,66 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
for _ in 0..n {
let key_blob = r.read_string()?;
let _comment = r.read_string()?;
keys.push(key::parse_public_key(key_blob)?);
keys.push(key::parse_public_key(
key_blob,
Some(SignatureHash::SHA2_512),
)?);
}
}

Ok(keys)
}

/// Ask the agent to sign the supplied piece of data.
pub async fn sign_request(
&mut self,
public: &PublicKey,
pub fn sign_request(
mut self,
public: &key::PublicKey,
mut data: CryptoVec,
) -> Result<CryptoVec, Error> {
) -> impl futures::Future<Output = (Self, Result<CryptoVec, Error>)> {
debug!("sign_request: {:?}", data);
let hash = self.prepare_sign_request(public, &data)?;
let hash = self.prepare_sign_request(public, &data);

self.read_response().await?;
async move {
if let Err(e) = hash {
return (self, Err(e));
}

if self.buf.first() == Some(&msg::SIGN_RESPONSE) {
self.write_signature(hash, &mut data)?;
Ok(data)
} else if self.buf.first() == Some(&msg::FAILURE) {
Err(Error::AgentFailure)
} else {
debug!("self.buf = {:?}", &self.buf[..]);
Ok(data)
let resp = self.read_response().await;
debug!("resp = {:?}", &self.buf[..]);
if let Err(e) = resp {
return (self, Err(e));
}

#[allow(clippy::indexing_slicing, clippy::unwrap_used)]
// length is checked, hash already checked
if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
let resp = self.write_signature(hash.unwrap(), &mut data);
if let Err(e) = resp {
return (self, Err(e));
}
(self, Ok(data))
} else if self.buf.first() == Some(&msg::FAILURE) {
(self, Err(Error::AgentFailure))
} else {
debug!("self.buf = {:?}", &self.buf[..]);
(self, Ok(data))
}
}
}

fn prepare_sign_request(
&mut self,
public: &ssh_key::PublicKey,
data: &[u8],
) -> Result<u32, Error> {
fn prepare_sign_request(&mut self, public: &key::PublicKey, data: &[u8]) -> Result<u32, Error> {
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.algorithm() {
Algorithm::Rsa {
hash: Some(HashAlg::Sha256),
} => 2,
Algorithm::Rsa {
hash: Some(HashAlg::Sha512),
} => 4,
Algorithm::Rsa { hash: None } => 0,
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);
Expand All @@ -345,7 +376,7 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
/// Ask the agent to sign the supplied piece of data.
pub fn sign_request_base64(
mut self,
public: &ssh_key::PublicKey,
public: &key::PublicKey,
data: &[u8],
) -> impl futures::Future<Output = (Self, Result<String, Error>)> {
debug!("sign_request: {:?}", data);
Expand All @@ -371,32 +402,67 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
}

/// Ask the agent to sign the supplied piece of data, and return a `Signature`.
pub async fn sign_request_signature(
&mut self,
public: &ssh_key::PublicKey,
pub fn sign_request_signature(
mut self,
public: &key::PublicKey,
data: &[u8],
) -> Result<Signature, Error> {
) -> impl futures::Future<Output = (Self, Result<crate::signature::Signature, Error>)> {
debug!("sign_request: {:?}", data);

self.prepare_sign_request(public, data)?;
self.read_response().await?;
let r = self.prepare_sign_request(public, data);

#[allow(clippy::indexing_slicing)] // length is checked
if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
let mut r = self.buf.reader(1);
let mut resp = r.read_string()?.reader(0);
let typ = String::from_utf8(resp.read_string()?.into())?;
let sig = resp.read_string()?;
let algo = Algorithm::new(&typ)?;
let sig = Signature::new(algo, sig.to_vec())?;
Ok(sig)
} else {
Err(Error::AgentProtocolError)
async move {
if let Err(e) = r {
return (self, Err(e));
}

if let Err(e) = self.read_response().await {
return (self, Err(e));
}

#[allow(clippy::indexing_slicing)] // length is checked
if !self.buf.is_empty() && self.buf[0] == msg::SIGN_RESPONSE {
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"ssh-rsa" => Ok(Signature::RSA {
bytes: sig.to_vec(),
hash: SignatureHash::SHA1,
}),
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(),
}),
}
};
let sig = as_sig(&self.buf);
(self, sig)
} else {
(self, Err(Error::AgentProtocolError))
}
}
}

/// Ask the agent to remove a key from its memory.
pub async fn remove_identity(&mut self, public: &ssh_key::PublicKey) -> Result<(), Error> {
pub async fn remove_identity(&mut self, public: &key::PublicKey) -> Result<(), Error> {
self.buf.clear();
self.buf.resize(4);
self.buf.push(msg::REMOVE_IDENTITY);
Expand Down Expand Up @@ -461,7 +527,29 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
}
}

fn key_blob(public: &ssh_key::PublicKey, buf: &mut CryptoVec) -> Result<(), Error> {
buf.extend_ssh_string(public.key_data().encoded()?.as_slice());
fn key_blob(public: &key::PublicKey, buf: &mut CryptoVec) -> Result<(), Error> {
match *public {
PublicKey::RSA { ref key, .. } => {
buf.extend(&[0, 0, 0, 0]);
let len0 = buf.len();
buf.extend_ssh_string(b"ssh-rsa");
buf.extend_ssh(&protocol::RsaPublicKey::from(key));
let len1 = buf.len();
#[allow(clippy::indexing_slicing)] // length is known
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.as_bytes());
let len1 = buf.len();
#[allow(clippy::indexing_slicing)] // length is known
BigEndian::write_u32(&mut buf[5..], (len1 - len0) as u32);
}
PublicKey::EC { .. } => {
buf.extend_ssh_string(&public.public_key_bytes());
}
}
Ok(())
}
Loading

0 comments on commit 86da6e1

Please sign in to comment.