Skip to content

Commit

Permalink
Connect to ssh-agent in a sync way
Browse files Browse the repository at this point in the history
Signed-off-by: xphoniex <[email protected]>
  • Loading branch information
xphoniex committed Oct 7, 2022
1 parent 9cbaac5 commit f536867
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 110 deletions.
14 changes: 4 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"

70 changes: 30 additions & 40 deletions examples/ssh-agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,56 +16,46 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

#[cfg(feature = "ssh-agent")]
use smol::{io, net::unix::UnixStream};
use std::{io, net::unix::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::<UnixStream>(&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::<UnixStream>(&agent, sk, &[]).unwrap();

println!("connecting to ssh-agent");
let signer = agent
.connect::<UnixStream>()
.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::<UnixStream>()
.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::<UnixStream>(&agent)
.await
.expect("could not list keys");
if keys.contains(&public) {
println!("added key succesfully")
}
ssh::remove_key::<UnixStream>(&agent, &public)
.await
.expect("could not remove key from ssh-agent");
let keys = ssh::list_keys::<UnixStream>(&agent)
.await
.expect("could not list keys");
if !keys.contains(&public) {
println!("removed key successfully")
}
let keys = ssh::list_keys::<UnixStream>(&agent).expect("could not list keys");
if keys.contains(&public) {
println!("added key succesfully")
}
ssh::remove_key::<UnixStream>(&agent, &public).expect("could not remove key from ssh-agent");
let keys = ssh::list_keys::<UnixStream>(&agent).expect("could not list keys");
if !keys.contains(&public) {
println!("removed key successfully")
}

Ok(())
})
Ok(())
}

#[cfg(not(feature = "ssh-agent"))]
Expand Down
2 changes: 0 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
47 changes: 21 additions & 26 deletions src/sign/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ impl Debug for Signature {
}
}

#[async_trait]
pub trait Signer {
type Error: std::error::Error + Send + Sync + 'static;

Expand All @@ -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<Signature, Self::Error>;
fn sign(&self, data: &[u8]) -> Result<Signature, Self::Error>;
}

#[async_trait]
impl<S> Signer for Arc<S>
where
S: Signer + Send + Sync,
Expand All @@ -128,12 +126,11 @@ where
self.as_ref().public_key()
}

async fn sign(&self, data: &[u8]) -> Result<Signature, Self::Error> {
self.as_ref().sign(data).await
fn sign(&self, data: &[u8]) -> Result<Signature, Self::Error> {
self.as_ref().sign(data)
}
}

#[async_trait]
impl Signer for ed25519_zebra::SigningKey {
type Error = Infallible;

Expand All @@ -142,7 +139,7 @@ impl Signer for ed25519_zebra::SigningKey {
PublicKey(vk.into())
}

async fn sign(&self, data: &[u8]) -> Result<Signature, Self::Error> {
fn sign(&self, data: &[u8]) -> Result<Signature, Self::Error> {
let signature = self.sign(data);
Ok(Signature(signature.into()))
}
Expand All @@ -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::*;
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -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<S1, S2>(signer1: S1, signer2: S2)
fn compat<S1, S2>(signer1: S1, signer2: S2)
where
S1: Signer,
S2: Signer,
Expand All @@ -309,41 +306,39 @@ 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;

fn public_key(&self) -> PublicKey {
PublicKey(self.public_key().0)
}

async fn sign(&self, data: &[u8]) -> Result<Signature, Self::Error> {
fn sign(&self, data: &[u8]) -> Result<Signature, Self::Error> {
Ok(Signature(
sodiumoxide::crypto::sign::ed25519::sign_detached(data, self).to_bytes(),
))
}
}

#[async_trait]
impl Signer for ed25519_dalek::Keypair {
type Error = Infallible;

fn public_key(&self) -> PublicKey {
PublicKey(self.public.to_bytes())
}

async fn sign(&self, data: &[u8]) -> Result<Signature, Self::Error> {
fn sign(&self, data: &[u8]) -> Result<Signature, Self::Error> {
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();
Expand All @@ -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 {};
Expand All @@ -369,6 +364,6 @@ mod tests {
ed25519_dalek::Keypair { secret, public }
};

compat(zebra, dalek).await
compat(zebra, dalek)
}
}
Loading

0 comments on commit f536867

Please sign in to comment.