From 7517325791f7d7a105dd5861e45b473f6ac4874e Mon Sep 17 00:00:00 2001 From: xphoniex Date: Mon, 10 Oct 2022 11:31:11 +0000 Subject: [PATCH] Connect to ssh-agent in a sync way Signed-off-by: xphoniex --- Cargo.toml | 14 +++------ deny.toml | 2 +- examples/ssh-agent.rs | 70 +++++++++++++++++++------------------------ src/lib.rs | 2 -- src/sign/ed25519.rs | 47 +++++++++++++---------------- src/sign/ssh.rs | 54 ++++++++++++++------------------- 6 files changed, 78 insertions(+), 111 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e8fb74e..76fc773 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,17 +7,15 @@ edition = "2018" license = "GPL-3.0-or-later" [features] -ssh-agent = ["lnk-thrussh-agent", "lnk-thrussh-encoding"] +ssh-agent = ["agent", "encoding"] [dependencies] -async-trait = "0.1" byteorder = "1.4" -futures = "0.3" generic-array = { version = "0.14", features = ["serde"] } lazy_static = "1" -lnk-cryptovec = "0.6.0" -lnk-thrussh-agent = { version = "0.1.0", optional = true, default-features = false } -lnk-thrussh-encoding = { version = "0.1.0", optional = true } +cryptovec = { version = "0.6.0", git = "https://github.com/radicle-dev/radicle-ssh" } +agent = { version = "0.1.0", git = "https://github.com/radicle-dev/radicle-ssh", optional = true, default-features = false } +encoding = { version = "0.1.0", git = "https://github.com/radicle-dev/radicle-ssh", optional = true } rand = "0.8.4" rpassword = "4.0" secstr = "0.5" @@ -43,11 +41,7 @@ version = "^0.10.0" default-features = false [dev-dependencies] -tokio = { version = ">= 1.8.4", features = ["macros", "rt"] } ed25519-dalek = "=1.0.1" -lnk-thrussh-agent = { version = "0.1.0", features = [ "smol-agent" ], default-features = false } rand = { version = "0.8.4", default-features = false } -smol = { version = "1.2" } sodiumoxide = "0.2" tempfile = "3" - diff --git a/deny.toml b/deny.toml index e4dc7db..e0d618f 100644 --- a/deny.toml +++ b/deny.toml @@ -192,4 +192,4 @@ unknown-git = "deny" # if not specified. If it is specified but empty, no registries are allowed. allow-registry = ["https://github.com/rust-lang/crates.io-index"] # List of URLs for allowed Git repositories -allow-git = [] +allow-git = ["https://github.com/radicle-dev/radicle-ssh"] diff --git a/examples/ssh-agent.rs b/examples/ssh-agent.rs index f1779b4..6252e96 100644 --- a/examples/ssh-agent.rs +++ b/examples/ssh-agent.rs @@ -16,56 +16,46 @@ // along with this program. If not, see . #[cfg(feature = "ssh-agent")] -use smol::{io, net::unix::UnixStream}; +use std::{io, os::unix::net::UnixStream}; #[cfg(feature = "ssh-agent")] fn main() -> io::Result<()> { use radicle_keystore::sign::{ssh, Signer, SshAgent}; use rand::rngs::OsRng; - smol::block_on(async { - let sk = ed25519_zebra::SigningKey::new(OsRng {}); - let pk = ed25519_zebra::VerificationKey::from(&sk); - let public = ssh::ed25519::PublicKey(pk.into()); - let agent = SshAgent::new(public); + let sk = ed25519_zebra::SigningKey::new(OsRng {}); + let pk = ed25519_zebra::VerificationKey::from(&sk); + let public = ssh::ed25519::PublicKey(pk.into()); + let agent = SshAgent::new(public); - // This could be a `rad-ssh-add` executable which reads the local key from - // the filestore (prompting for the password). - ssh::add_key::(&agent, sk, &[]).await.unwrap(); + // This could be a `rad-ssh-add` executable which reads the local key from + // the filestore (prompting for the password). + ssh::add_key::(&agent, sk, &[]).unwrap(); - println!("connecting to ssh-agent"); - let signer = agent - .connect::() - .await - .expect("could not connect to ssh-agent"); - println!("asking agent to sign some data"); - let sig = signer - .sign(b"cooper") - .await - .expect("signing via ssh-agent failed"); - println!("verifying signature"); - pk.verify(&ed25519_zebra::Signature::from(sig.0), b"cooper") - .expect("ssh-agent didn't return a valid signature"); - println!("it worksed"); + println!("connecting to ssh-agent"); + let signer = agent + .connect::() + .expect("could not connect to ssh-agent"); + println!("asking agent to sign some data"); + let sig = signer + .sign(b"cooper") + .expect("signing via ssh-agent failed"); + println!("verifying signature"); + pk.verify(&ed25519_zebra::Signature::from(sig.0), b"cooper") + .expect("ssh-agent didn't return a valid signature"); + println!("it worksed"); - let keys = ssh::list_keys::(&agent) - .await - .expect("could not list keys"); - if keys.contains(&public) { - println!("added key succesfully") - } - ssh::remove_key::(&agent, &public) - .await - .expect("could not remove key from ssh-agent"); - let keys = ssh::list_keys::(&agent) - .await - .expect("could not list keys"); - if !keys.contains(&public) { - println!("removed key successfully") - } + let keys = ssh::list_keys::(&agent).expect("could not list keys"); + if keys.contains(&public) { + println!("added key succesfully") + } + ssh::remove_key::(&agent, &public).expect("could not remove key from ssh-agent"); + let keys = ssh::list_keys::(&agent).expect("could not list keys"); + if !keys.contains(&public) { + println!("removed key successfully") + } - Ok(()) - }) + Ok(()) } #[cfg(not(feature = "ssh-agent"))] diff --git a/src/lib.rs b/src/lib.rs index 2b3dad4..ddf2c04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,8 +33,6 @@ //! encryption entirely to an external system (such as GPG, or a password //! manager). -#[macro_use] -extern crate async_trait; #[macro_use] extern crate lazy_static; diff --git a/src/sign/ed25519.rs b/src/sign/ed25519.rs index 5ddba6c..b072dcc 100644 --- a/src/sign/ed25519.rs +++ b/src/sign/ed25519.rs @@ -105,7 +105,6 @@ impl Debug for Signature { } } -#[async_trait] pub trait Signer { type Error: std::error::Error + Send + Sync + 'static; @@ -114,10 +113,9 @@ pub trait Signer { /// Sign the supplied data with the secret key corresponding to /// [`Signer::public_key`] - async fn sign(&self, data: &[u8]) -> Result; + fn sign(&self, data: &[u8]) -> Result; } -#[async_trait] impl Signer for Arc where S: Signer + Send + Sync, @@ -128,12 +126,11 @@ where self.as_ref().public_key() } - async fn sign(&self, data: &[u8]) -> Result { - self.as_ref().sign(data).await + fn sign(&self, data: &[u8]) -> Result { + self.as_ref().sign(data) } } -#[async_trait] impl Signer for ed25519_zebra::SigningKey { type Error = Infallible; @@ -142,7 +139,7 @@ impl Signer for ed25519_zebra::SigningKey { PublicKey(vk.into()) } - async fn sign(&self, data: &[u8]) -> Result { + fn sign(&self, data: &[u8]) -> Result { let signature = self.sign(data); Ok(Signature(signature.into())) } @@ -152,10 +149,10 @@ impl Signer for ed25519_zebra::SigningKey { pub mod thrussh { use std::convert::{TryFrom as _, TryInto as _}; + use agent; use byteorder::{BigEndian, ByteOrder as _}; - use lnk_cryptovec::CryptoVec; - use lnk_thrussh_agent as agent; - use lnk_thrussh_encoding::{Encoding as _, Position, Reader as _}; + use cryptovec::CryptoVec; + use encoding::{Encoding as _, Position, Reader as _}; use thiserror::Error; use super::*; @@ -165,7 +162,7 @@ pub mod thrussh { #[error("invalid signature was computed")] Invalid, #[error(transparent)] - Encoding(#[from] lnk_thrussh_encoding::Error), + Encoding(#[from] encoding::Error), } impl agent::key::Signature for Signature { @@ -190,7 +187,7 @@ pub mod thrussh { #[error("the public key parsed was not 32 bits in length")] Invalid, #[error(transparent)] - Encoding(#[from] lnk_thrussh_encoding::Error), + Encoding(#[from] encoding::Error), } impl agent::key::Public for PublicKey { @@ -227,7 +224,7 @@ pub mod thrussh { #[derive(Debug, Error)] pub enum SigningKeyError { #[error(transparent)] - Encoding(#[from] lnk_thrussh_encoding::Error), + Encoding(#[from] encoding::Error), #[error(transparent)] Ed25519(#[from] ed25519_zebra::Error), } @@ -299,7 +296,7 @@ mod tests { /// implementations must be byte-for-byte equal. /// /// All combinatorial pairs of `Signer` implementations must pass this. - async fn compat(signer1: S1, signer2: S2) + fn compat(signer1: S1, signer2: S2) where S1: Signer, S2: Signer, @@ -309,12 +306,11 @@ mod tests { { assert_eq!(signer1.public_key(), signer2.public_key()); assert_eq!( - signer1.sign(MESSAGE).await.unwrap(), - signer2.sign(MESSAGE).await.unwrap() + signer1.sign(MESSAGE).unwrap(), + signer2.sign(MESSAGE).unwrap() ); } - #[async_trait] impl Signer for sodiumoxide::crypto::sign::ed25519::SecretKey { type Error = Infallible; @@ -322,14 +318,13 @@ mod tests { PublicKey(self.public_key().0) } - async fn sign(&self, data: &[u8]) -> Result { + fn sign(&self, data: &[u8]) -> Result { Ok(Signature( sodiumoxide::crypto::sign::ed25519::sign_detached(data, self).to_bytes(), )) } } - #[async_trait] impl Signer for ed25519_dalek::Keypair { type Error = Infallible; @@ -337,13 +332,13 @@ mod tests { PublicKey(self.public.to_bytes()) } - async fn sign(&self, data: &[u8]) -> Result { + fn sign(&self, data: &[u8]) -> Result { Ok(Signature(DalekSigner::sign(self, data).to_bytes())) } } - #[tokio::test] - async fn compat_sodium_dalek() { + #[test] + fn compat_sodium_dalek() { sodiumoxide::init().unwrap(); let (_, sodium) = sodium::gen_keypair(); @@ -353,11 +348,11 @@ mod tests { ed25519_dalek::Keypair { secret, public } }; - compat(sodium, dalek).await + compat(sodium, dalek) } - #[tokio::test] - async fn compat_zebra_dalek() { + #[test] + fn compat_zebra_dalek() { use rand::rngs::OsRng; let csprng = OsRng {}; @@ -369,6 +364,6 @@ mod tests { ed25519_dalek::Keypair { secret, public } }; - compat(zebra, dalek).await + compat(zebra, dalek) } } diff --git a/src/sign/ssh.rs b/src/sign/ssh.rs index 4018462..88a13e3 100644 --- a/src/sign/ssh.rs +++ b/src/sign/ssh.rs @@ -16,12 +16,11 @@ // along with this program. If not, see . use std::path::PathBuf; +use std::sync::Mutex; -use futures::lock::Mutex; -use lnk_thrussh_agent::{ - client::{self, AgentClient, ClientStream}, - Constraint, -}; +use agent::client::Error::PoisonError; +use agent::client::{self, AgentClient, ClientStream}; +use agent::Constraint; pub use super::ed25519; @@ -100,16 +99,11 @@ impl SshAgent { /// This is to leave the async runtime agnostic. The different /// implementations for streams can be found at [`ClientStream`]'s /// documentation. - pub async fn connect( - &self, - ) -> Result, error::Connect> + pub fn connect(&self) -> Result, error::Connect> where S: ClientStream + Unpin, { - let client = self - .client::() - .await - .map(|client| Mutex::new(Some(client)))?; + let client = self.client::().map(|client| Mutex::new(Some(client)))?; Ok(Signer { rfc: self.key, @@ -117,13 +111,13 @@ impl SshAgent { }) } - async fn client(&self) -> Result, client::Error> + fn client(&self) -> Result, client::Error> where S: ClientStream + Unpin, { match &self.path { - None => Ok(S::connect_env().await?), - Some(path) => Ok(S::connect_uds(path).await?), + None => Ok(S::connect_env()?), + Some(path) => Ok(S::connect_uds(path)?), } } } @@ -148,7 +142,7 @@ struct Signer { /// The stream parameter `S` needs to be chosen when calling this function. This /// is to leave the async runtime agnostic. The different implementations for /// streams can be found at [`ClientStream`]'s documentation. -pub async fn add_key( +pub fn add_key( agent: &SshAgent, secret: ed25519_zebra::SigningKey, constraints: &[Constraint], @@ -156,35 +150,31 @@ pub async fn add_key( where S: ClientStream + Unpin, { - let mut client = agent.client::().await?; + let mut client = agent.client::()?; let secret = ed25519::SigningKey::from(secret); - client.add_identity(&secret, constraints).await?; + client.add_identity(&secret, constraints)?; Ok(()) } -pub async fn remove_key( - agent: &SshAgent, - key: &ed25519::PublicKey, -) -> Result<(), error::RemoveKey> +pub fn remove_key(agent: &SshAgent, key: &ed25519::PublicKey) -> Result<(), error::RemoveKey> where S: ClientStream + Unpin, { - let mut client = agent.client::().await?; - client.remove_identity(key).await?; + let mut client = agent.client::()?; + client.remove_identity(key)?; Ok(()) } -pub async fn list_keys(agent: &SshAgent) -> Result, error::ListKeys> +pub fn list_keys(agent: &SshAgent) -> Result, error::ListKeys> where S: ClientStream + Unpin, { - let mut client = agent.client::().await?; - let keys = client.request_identities().await?; + let mut client = agent.client::()?; + let keys = client.request_identities()?; Ok(keys) } -#[async_trait] impl ed25519::Signer for Signer where S: ClientStream + Unpin, @@ -195,14 +185,14 @@ where self.rfc } - async fn sign(&self, data: &[u8]) -> Result { - let mut guard = self.client.lock().await; + fn sign(&self, data: &[u8]) -> Result { + let mut guard = self.client.lock().map_err(|_| PoisonError)?; let client = match guard.take() { - None => ClientStream::connect_env().await?, + None => ClientStream::connect_env()?, Some(client) => client, }; - let (client, sig) = client.sign_request_signature(&self.rfc, data).await; + let (client, sig) = client.sign_request_signature(&self.rfc, data); *guard = Some(client); Ok(sig?) }