Add `Signer::try_sign` method and cleanup usage

To support ssh-agent, add a signing method that can fail.
Also cleanup usage so that we always accept signer references.

Signed-off-by: Alexis Sellier <alexis@radicle.xyz>
This commit is contained in:
Alexis Sellier 2022-11-03 10:12:22 +01:00
parent 5494490c40
commit a79ef67b2a
No known key found for this signature in database
13 changed files with 54 additions and 59 deletions

View File

@ -1,4 +1,3 @@
use std::sync::Arc;
use std::{fmt, ops::Deref, str::FromStr}; use std::{fmt, ops::Deref, str::FromStr};
use ed25519_compact as ed25519; use ed25519_compact as ed25519;
@ -20,37 +19,30 @@ pub struct Verified;
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct Unverified; pub struct Unverified;
/// Error returned if signing fails, eg. due to an HSM or KMS.
#[derive(Debug, Error)]
#[error(transparent)]
pub struct SignerError {
#[from]
source: Box<dyn std::error::Error + Send + Sync>,
}
impl SignerError {
pub fn new(source: impl std::error::Error + Send + Sync + 'static) -> Self {
Self {
source: Box::new(source),
}
}
}
pub trait Signer: Send + Sync { pub trait Signer: Send + Sync {
/// Return this signer's public/verification key. /// Return this signer's public/verification key.
fn public_key(&self) -> &PublicKey; fn public_key(&self) -> &PublicKey;
/// Sign a message and return the signature. /// Sign a message and return the signature.
fn sign(&self, msg: &[u8]) -> Signature; fn sign(&self, msg: &[u8]) -> Signature;
} /// Sign a message and return the signature, or fail if the signer was unable
/// to produce a signature.
impl<T> Signer for Arc<T> fn try_sign(&self, msg: &[u8]) -> Result<Signature, SignerError>;
where
T: Signer + ?Sized,
{
fn sign(&self, msg: &[u8]) -> Signature {
self.deref().sign(msg)
}
fn public_key(&self) -> &PublicKey {
self.deref().public_key()
}
}
impl<T> Signer for &T
where
T: Signer + ?Sized,
{
fn sign(&self, msg: &[u8]) -> Signature {
self.deref().sign(msg)
}
fn public_key(&self) -> &PublicKey {
self.deref().public_key()
}
} }
/// Cryptographic signature. /// Cryptographic signature.

View File

@ -1,4 +1,4 @@
use crate::{KeyPair, PublicKey, SecretKey, Seed, Signature, Signer}; use crate::{KeyPair, PublicKey, SecretKey, Seed, Signature, Signer, SignerError};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct MockSigner { pub struct MockSigner {
@ -62,4 +62,8 @@ impl Signer for MockSigner {
fn sign(&self, msg: &[u8]) -> Signature { fn sign(&self, msg: &[u8]) -> Signature {
self.sk.sign(msg, None).into() self.sk.sign(msg, None).into()
} }
fn try_sign(&self, msg: &[u8]) -> Result<Signature, SignerError> {
Ok(self.sign(msg))
}
} }

View File

@ -247,7 +247,7 @@ pub enum AnnouncementMessage {
impl AnnouncementMessage { impl AnnouncementMessage {
/// Sign this announcement message. /// Sign this announcement message.
pub fn signed<S: crypto::Signer>(self, signer: S) -> Announcement { pub fn signed<G: crypto::Signer>(self, signer: &G) -> Announcement {
let msg = wire::serialize(&self); let msg = wire::serialize(&self);
let signature = signer.sign(&msg); let signature = signer.sign(&msg);
@ -407,11 +407,11 @@ impl Message {
.into() .into()
} }
pub fn node<S: crypto::Signer>(message: NodeAnnouncement, signer: S) -> Self { pub fn node<G: crypto::Signer>(message: NodeAnnouncement, signer: &G) -> Self {
AnnouncementMessage::from(message).signed(signer).into() AnnouncementMessage::from(message).signed(signer).into()
} }
pub fn inventory<S: crypto::Signer>(message: InventoryAnnouncement, signer: S) -> Self { pub fn inventory<G: crypto::Signer>(message: InventoryAnnouncement, signer: &G) -> Self {
AnnouncementMessage::from(message).signed(signer).into() AnnouncementMessage::from(message).signed(signer).into()
} }

View File

@ -29,7 +29,7 @@ pub fn messages(count: usize, now: LocalTime, delta: LocalDuration) -> Vec<Messa
inventory: arbitrary::gen(3), inventory: arbitrary::gen(3),
timestamp: time.as_secs(), timestamp: time.as_secs(),
}, },
signer, &signer,
)) ))
} }
msgs msgs

View File

@ -202,7 +202,7 @@ fn test_inventory_sync() {
Storage::open(tmp.path().join("alice")).unwrap(), Storage::open(tmp.path().join("alice")).unwrap(),
); );
let bob_signer = MockSigner::default(); let bob_signer = MockSigner::default();
let bob_storage = fixtures::storage(tmp.path().join("bob"), bob_signer).unwrap(); let bob_storage = fixtures::storage(tmp.path().join("bob"), &bob_signer).unwrap();
let bob = Peer::new("bob", [8, 8, 8, 8], bob_storage); let bob = Peer::new("bob", [8, 8, 8, 8], bob_storage);
let now = LocalTime::now().as_secs(); let now = LocalTime::now().as_secs();
let projs = bob.storage().inventory().unwrap(); let projs = bob.storage().inventory().unwrap();

View File

@ -137,7 +137,7 @@ impl Doc<Verified> {
false false
} }
pub fn sign<G: crypto::Signer>(&self, signer: G) -> Result<(git::Oid, Signature), DocError> { pub fn sign<G: crypto::Signer>(&self, signer: &G) -> Result<(git::Oid, Signature), DocError> {
let (oid, bytes) = self.encode()?; let (oid, bytes) = self.encode()?;
let sig = signer.sign(&bytes); let sig = signer.sign(&bytes);

View File

@ -15,7 +15,7 @@ use std::{env, io};
use thiserror::Error; use thiserror::Error;
use crate::crypto::{KeyPair, PublicKey, SecretKey, Signature, Signer}; use crate::crypto::{KeyPair, PublicKey, SecretKey, Signature, Signer, SignerError};
use crate::keystore::UnsafeKeystore; use crate::keystore::UnsafeKeystore;
use crate::node; use crate::node;
use crate::storage::git::transport; use crate::storage::git::transport;
@ -45,6 +45,10 @@ impl Signer for UnsafeSigner {
fn sign(&self, msg: &[u8]) -> Signature { fn sign(&self, msg: &[u8]) -> Signature {
Signature(self.secret.sign(msg, None)) Signature(self.secret.sign(msg, None))
} }
fn try_sign(&self, msg: &[u8]) -> Result<Signature, SignerError> {
Ok(self.sign(msg))
}
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -47,7 +47,7 @@ pub fn init<G: Signer>(
name: &str, name: &str,
description: &str, description: &str,
default_branch: BranchName, default_branch: BranchName,
signer: G, signer: &G,
storage: &Storage, storage: &Storage,
) -> Result<(Id, SignedRefs<Verified>), InitError> { ) -> Result<(Id, SignedRefs<Verified>), InitError> {
// TODO: Better error when project id already exists in storage, but remote doesn't. // TODO: Better error when project id already exists in storage, but remote doesn't.
@ -114,7 +114,7 @@ pub enum ForkError {
pub fn fork_remote<G: Signer, S: storage::WriteStorage>( pub fn fork_remote<G: Signer, S: storage::WriteStorage>(
proj: Id, proj: Id,
remote: &RemoteId, remote: &RemoteId,
signer: G, signer: &G,
storage: S, storage: S,
) -> Result<(), ForkError> { ) -> Result<(), ForkError> {
// TODO: Copy tags over? // TODO: Copy tags over?
@ -150,7 +150,7 @@ pub fn fork_remote<G: Signer, S: storage::WriteStorage>(
&format!("creating identity branch for {me}"), &format!("creating identity branch for {me}"),
)?; )?;
repository.sign_refs(&signer)?; repository.sign_refs(signer)?;
Ok(()) Ok(())
} }
@ -179,7 +179,7 @@ pub fn fork<G: Signer, S: storage::WriteStorage>(
false, false,
&format!("creating identity branch for {me}"), &format!("creating identity branch for {me}"),
)?; )?;
repository.sign_refs(&signer)?; repository.sign_refs(signer)?;
Ok(()) Ok(())
} }

View File

@ -304,7 +304,7 @@ pub trait WriteRepository: ReadRepository {
namespaces: impl Into<Namespaces>, namespaces: impl Into<Namespaces>,
) -> Result<Vec<RefUpdate>, FetchError>; ) -> Result<Vec<RefUpdate>, FetchError>;
fn set_head(&self) -> Result<Oid, ProjectError>; fn set_head(&self) -> Result<Oid, ProjectError>;
fn sign_refs<G: Signer>(&self, signer: G) -> Result<SignedRefs<Verified>, Error>; fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, Error>;
fn raw(&self) -> &git2::Repository; fn raw(&self) -> &git2::Repository;
} }

View File

@ -638,10 +638,10 @@ impl WriteRepository for Repository {
Ok(head) Ok(head)
} }
fn sign_refs<G: Signer>(&self, signer: G) -> Result<SignedRefs<Verified>, Error> { fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, Error> {
let remote = signer.public_key(); let remote = signer.public_key();
let refs = self.references(remote)?; let refs = self.references(remote)?;
let signed = refs.signed(&signer)?; let signed = refs.signed(signer)?;
signed.save(remote, self)?; signed.save(remote, self)?;
@ -751,7 +751,7 @@ mod tests {
let tmp = tempfile::tempdir().unwrap(); let tmp = tempfile::tempdir().unwrap();
let alice_signer = MockSigner::default(); let alice_signer = MockSigner::default();
let alice_pk = *alice_signer.public_key(); let alice_pk = *alice_signer.public_key();
let alice = fixtures::storage(tmp.path().join("alice"), alice_signer).unwrap(); let alice = fixtures::storage(tmp.path().join("alice"), &alice_signer).unwrap();
let bob = Storage::open(tmp.path().join("bob")).unwrap(); let bob = Storage::open(tmp.path().join("bob")).unwrap();
let inventory = alice.inventory().unwrap(); let inventory = alice.inventory().unwrap();
let proj = *inventory.first().unwrap(); let proj = *inventory.first().unwrap();
@ -893,7 +893,7 @@ mod tests {
"radicle", "radicle",
"radicle", "radicle",
git::refname!("master"), git::refname!("master"),
signer, &signer,
&storage, &storage,
) )
.unwrap(); .unwrap();

View File

@ -7,7 +7,7 @@ use std::ops::{Deref, DerefMut};
use std::path::Path; use std::path::Path;
use std::str::FromStr; use std::str::FromStr;
use crypto::{PublicKey, Signature, Signer, Unverified, Verified}; use crypto::{PublicKey, Signature, Signer, SignerError, Unverified, Verified};
use thiserror::Error; use thiserror::Error;
use crate::git; use crate::git;
@ -36,6 +36,8 @@ pub enum Updated {
pub enum Error { pub enum Error {
#[error("invalid signature: {0}")] #[error("invalid signature: {0}")]
InvalidSignature(#[from] crypto::Error), InvalidSignature(#[from] crypto::Error),
#[error("signer error: {0}")]
Signer(#[from] SignerError),
#[error("canonical refs: {0}")] #[error("canonical refs: {0}")]
Canonical(#[from] canonical::Error), Canonical(#[from] canonical::Error),
#[error("invalid reference")] #[error("invalid reference")]
@ -84,13 +86,13 @@ impl Refs {
} }
/// Sign these refs with the given signer and return [`SignedRefs`]. /// Sign these refs with the given signer and return [`SignedRefs`].
pub fn signed<S>(self, signer: S) -> Result<SignedRefs<Verified>, Error> pub fn signed<G>(self, signer: &G) -> Result<SignedRefs<Verified>, Error>
where where
S: Signer, G: Signer,
{ {
let refs = self; let refs = self;
let msg = refs.canonical(); let msg = refs.canonical();
let signature = signer.sign(&msg); let signature = signer.try_sign(&msg)?;
Ok(SignedRefs { Ok(SignedRefs {
refs, refs,

View File

@ -9,7 +9,7 @@ use crate::storage::git::Storage;
use crate::storage::refs::SignedRefs; use crate::storage::refs::SignedRefs;
/// Create a new storage with a project. /// Create a new storage with a project.
pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: G) -> Result<Storage, rad::InitError> { pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: &G) -> Result<Storage, rad::InitError> {
let path = path.as_ref(); let path = path.as_ref();
let storage = Storage::open(path.join("storage"))?; let storage = Storage::open(path.join("storage"))?;
@ -22,14 +22,7 @@ pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: G) -> Result<Storage,
("rx", "A pixel editor"), ("rx", "A pixel editor"),
] { ] {
let (repo, _) = repository(path.join("workdir").join(name)); let (repo, _) = repository(path.join("workdir").join(name));
rad::init( rad::init(&repo, name, desc, git::refname!("master"), signer, &storage)?;
&repo,
name,
desc,
git::refname!("master"),
&signer,
&storage,
)?;
} }
Ok(storage) Ok(storage)
@ -39,7 +32,7 @@ pub fn storage<P: AsRef<Path>, G: Signer>(path: P, signer: G) -> Result<Storage,
pub fn project<P: AsRef<Path>, G: Signer>( pub fn project<P: AsRef<Path>, G: Signer>(
path: P, path: P,
storage: &Storage, storage: &Storage,
signer: G, signer: &G,
) -> Result<(Id, SignedRefs<Verified>, git2::Repository, git2::Oid), rad::InitError> { ) -> Result<(Id, SignedRefs<Verified>, git2::Repository, git2::Oid), rad::InitError> {
transport::local::register(storage.clone()); transport::local::register(storage.clone());

View File

@ -151,7 +151,7 @@ impl WriteRepository for MockRepository {
fn sign_refs<G: Signer>( fn sign_refs<G: Signer>(
&self, &self,
_signer: G, _signer: &G,
) -> Result<crate::storage::refs::SignedRefs<Verified>, Error> { ) -> Result<crate::storage::refs::SignedRefs<Verified>, Error> {
todo!() todo!()
} }