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:
parent
5494490c40
commit
a79ef67b2a
|
|
@ -1,4 +1,3 @@
|
|||
use std::sync::Arc;
|
||||
use std::{fmt, ops::Deref, str::FromStr};
|
||||
|
||||
use ed25519_compact as ed25519;
|
||||
|
|
@ -20,37 +19,30 @@ pub struct Verified;
|
|||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
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 {
|
||||
/// Return this signer's public/verification key.
|
||||
fn public_key(&self) -> &PublicKey;
|
||||
/// Sign a message and return the signature.
|
||||
fn sign(&self, msg: &[u8]) -> Signature;
|
||||
}
|
||||
|
||||
impl<T> Signer for Arc<T>
|
||||
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()
|
||||
}
|
||||
/// Sign a message and return the signature, or fail if the signer was unable
|
||||
/// to produce a signature.
|
||||
fn try_sign(&self, msg: &[u8]) -> Result<Signature, SignerError>;
|
||||
}
|
||||
|
||||
/// Cryptographic signature.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{KeyPair, PublicKey, SecretKey, Seed, Signature, Signer};
|
||||
use crate::{KeyPair, PublicKey, SecretKey, Seed, Signature, Signer, SignerError};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MockSigner {
|
||||
|
|
@ -62,4 +62,8 @@ impl Signer for MockSigner {
|
|||
fn sign(&self, msg: &[u8]) -> Signature {
|
||||
self.sk.sign(msg, None).into()
|
||||
}
|
||||
|
||||
fn try_sign(&self, msg: &[u8]) -> Result<Signature, SignerError> {
|
||||
Ok(self.sign(msg))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ pub enum AnnouncementMessage {
|
|||
|
||||
impl AnnouncementMessage {
|
||||
/// 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 signature = signer.sign(&msg);
|
||||
|
||||
|
|
@ -407,11 +407,11 @@ impl Message {
|
|||
.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()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ pub fn messages(count: usize, now: LocalTime, delta: LocalDuration) -> Vec<Messa
|
|||
inventory: arbitrary::gen(3),
|
||||
timestamp: time.as_secs(),
|
||||
},
|
||||
signer,
|
||||
&signer,
|
||||
))
|
||||
}
|
||||
msgs
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ fn test_inventory_sync() {
|
|||
Storage::open(tmp.path().join("alice")).unwrap(),
|
||||
);
|
||||
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 now = LocalTime::now().as_secs();
|
||||
let projs = bob.storage().inventory().unwrap();
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ impl Doc<Verified> {
|
|||
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 sig = signer.sign(&bytes);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ use std::{env, io};
|
|||
|
||||
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::node;
|
||||
use crate::storage::git::transport;
|
||||
|
|
@ -45,6 +45,10 @@ impl Signer for UnsafeSigner {
|
|||
fn sign(&self, msg: &[u8]) -> Signature {
|
||||
Signature(self.secret.sign(msg, None))
|
||||
}
|
||||
|
||||
fn try_sign(&self, msg: &[u8]) -> Result<Signature, SignerError> {
|
||||
Ok(self.sign(msg))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ pub fn init<G: Signer>(
|
|||
name: &str,
|
||||
description: &str,
|
||||
default_branch: BranchName,
|
||||
signer: G,
|
||||
signer: &G,
|
||||
storage: &Storage,
|
||||
) -> Result<(Id, SignedRefs<Verified>), InitError> {
|
||||
// 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>(
|
||||
proj: Id,
|
||||
remote: &RemoteId,
|
||||
signer: G,
|
||||
signer: &G,
|
||||
storage: S,
|
||||
) -> Result<(), ForkError> {
|
||||
// TODO: Copy tags over?
|
||||
|
|
@ -150,7 +150,7 @@ pub fn fork_remote<G: Signer, S: storage::WriteStorage>(
|
|||
&format!("creating identity branch for {me}"),
|
||||
)?;
|
||||
|
||||
repository.sign_refs(&signer)?;
|
||||
repository.sign_refs(signer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -179,7 +179,7 @@ pub fn fork<G: Signer, S: storage::WriteStorage>(
|
|||
false,
|
||||
&format!("creating identity branch for {me}"),
|
||||
)?;
|
||||
repository.sign_refs(&signer)?;
|
||||
repository.sign_refs(signer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ pub trait WriteRepository: ReadRepository {
|
|||
namespaces: impl Into<Namespaces>,
|
||||
) -> Result<Vec<RefUpdate>, FetchError>;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -638,10 +638,10 @@ impl WriteRepository for Repository {
|
|||
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 refs = self.references(remote)?;
|
||||
let signed = refs.signed(&signer)?;
|
||||
let signed = refs.signed(signer)?;
|
||||
|
||||
signed.save(remote, self)?;
|
||||
|
||||
|
|
@ -751,7 +751,7 @@ mod tests {
|
|||
let tmp = tempfile::tempdir().unwrap();
|
||||
let alice_signer = MockSigner::default();
|
||||
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 inventory = alice.inventory().unwrap();
|
||||
let proj = *inventory.first().unwrap();
|
||||
|
|
@ -893,7 +893,7 @@ mod tests {
|
|||
"radicle",
|
||||
"radicle",
|
||||
git::refname!("master"),
|
||||
signer,
|
||||
&signer,
|
||||
&storage,
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use std::ops::{Deref, DerefMut};
|
|||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crypto::{PublicKey, Signature, Signer, Unverified, Verified};
|
||||
use crypto::{PublicKey, Signature, Signer, SignerError, Unverified, Verified};
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::git;
|
||||
|
|
@ -36,6 +36,8 @@ pub enum Updated {
|
|||
pub enum Error {
|
||||
#[error("invalid signature: {0}")]
|
||||
InvalidSignature(#[from] crypto::Error),
|
||||
#[error("signer error: {0}")]
|
||||
Signer(#[from] SignerError),
|
||||
#[error("canonical refs: {0}")]
|
||||
Canonical(#[from] canonical::Error),
|
||||
#[error("invalid reference")]
|
||||
|
|
@ -84,13 +86,13 @@ impl Refs {
|
|||
}
|
||||
|
||||
/// 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
|
||||
S: Signer,
|
||||
G: Signer,
|
||||
{
|
||||
let refs = self;
|
||||
let msg = refs.canonical();
|
||||
let signature = signer.sign(&msg);
|
||||
let signature = signer.try_sign(&msg)?;
|
||||
|
||||
Ok(SignedRefs {
|
||||
refs,
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use crate::storage::git::Storage;
|
|||
use crate::storage::refs::SignedRefs;
|
||||
|
||||
/// 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 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"),
|
||||
] {
|
||||
let (repo, _) = repository(path.join("workdir").join(name));
|
||||
rad::init(
|
||||
&repo,
|
||||
name,
|
||||
desc,
|
||||
git::refname!("master"),
|
||||
&signer,
|
||||
&storage,
|
||||
)?;
|
||||
rad::init(&repo, name, desc, git::refname!("master"), signer, &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>(
|
||||
path: P,
|
||||
storage: &Storage,
|
||||
signer: G,
|
||||
signer: &G,
|
||||
) -> Result<(Id, SignedRefs<Verified>, git2::Repository, git2::Oid), rad::InitError> {
|
||||
transport::local::register(storage.clone());
|
||||
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ impl WriteRepository for MockRepository {
|
|||
|
||||
fn sign_refs<G: Signer>(
|
||||
&self,
|
||||
_signer: G,
|
||||
_signer: &G,
|
||||
) -> Result<crate::storage::refs::SignedRefs<Verified>, Error> {
|
||||
todo!()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue