Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to adjust the deny.toml with the new git sources.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

2 changes: 1 addition & 1 deletion deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
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, 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::<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