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 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.
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)]
|
||||||
|
|
|
||||||
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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!()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue