Include new `rad/root` in signed refs
We ensure that a `rad/root` ref is included in the signed refs file under `rad/sigrefs` for all remotes. This prevents a certain kind of "grafting" attack where signed refs can be copied between repositories, by having the peer sign over the identity root together with the data refs. When verifying signed refs, we ensure that the ref is present and points to an identity branch root that matches the repository identity containing the signed refs. Alternatives: lots of alternatives were considered, but this one doesn't introduce any changes to the signing. The `rad/id/root` name was considered but is invalid due to `rad/id`.
This commit is contained in:
parent
24066c2600
commit
989edacd56
|
|
@ -18,8 +18,8 @@ Let's also inspect what remotes are in the repository:
|
|||
|
||||
``` ~alice
|
||||
$ rad inspect --sigrefs
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi f209c9f68aa689af24220a20462e13ee9dfb2a95
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk 161b775a3509c8098de67f57f750972bba015b31
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 99c549702e2bcfe02b0e68d4a2224fb7a1524529
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk e9f48ef90fe8592e1b1c95f96c21a59ca1495300
|
||||
```
|
||||
|
||||
Now let's clean the `heartwood` project:
|
||||
|
|
@ -34,7 +34,7 @@ Inspecting the remotes again, we see that Bob is now gone:
|
|||
|
||||
``` ~alice
|
||||
$ rad inspect --sigrefs
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi f209c9f68aa689af24220a20462e13ee9dfb2a95
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 99c549702e2bcfe02b0e68d4a2224fb7a1524529
|
||||
```
|
||||
|
||||
Note that Bob will be fetched again if we do not untrack his
|
||||
|
|
|
|||
|
|
@ -36,12 +36,14 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
|||
│ └── master
|
||||
└── rad
|
||||
├── id
|
||||
├── root
|
||||
└── sigrefs
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
||||
└── refs
|
||||
├── heads
|
||||
│ └── master
|
||||
└── rad
|
||||
├── root
|
||||
└── sigrefs
|
||||
```
|
||||
|
||||
|
|
@ -75,17 +77,20 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
|||
│ └── master
|
||||
└── rad
|
||||
├── id
|
||||
├── root
|
||||
└── sigrefs
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
||||
└── refs
|
||||
├── heads
|
||||
│ └── master
|
||||
└── rad
|
||||
├── root
|
||||
└── sigrefs
|
||||
z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z
|
||||
└── refs
|
||||
├── heads
|
||||
│ └── master
|
||||
└── rad
|
||||
├── root
|
||||
└── sigrefs
|
||||
```
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
|||
│ └── master
|
||||
└── rad
|
||||
├── id
|
||||
├── root
|
||||
└── sigrefs
|
||||
```
|
||||
|
||||
|
|
@ -39,12 +40,14 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
|||
│ └── master
|
||||
└── rad
|
||||
├── id
|
||||
├── root
|
||||
└── sigrefs
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
||||
└── refs
|
||||
├── heads
|
||||
│ └── master
|
||||
└── rad
|
||||
├── root
|
||||
└── sigrefs
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ $ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add Bob" --des
|
|||
```
|
||||
|
||||
``` ~bob
|
||||
$ rad watch --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --node z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z -r 'refs/rad/sigrefs' -t 95cd447c57de8d232c6154f5dba0451aa593520e -i 500 --timeout 5000
|
||||
$ rad watch --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --node z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z -r 'refs/rad/sigrefs' -t c9a828fc2fb01f893d6e6e9e17b9092dea2b3aba -i 500 --timeout 5000
|
||||
$ rad sync --fetch rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
|
||||
✓ Fetched repository from 1 seed(s)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
|||
│ └── master
|
||||
└── rad
|
||||
├── id
|
||||
├── root
|
||||
└── sigrefs
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
||||
└── refs
|
||||
|
|
@ -36,6 +37,7 @@ z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk
|
|||
│ └── xyz.radicle.issue
|
||||
│ └── f12d512c51d30429f7916db038ae0360e2e938c2
|
||||
└── rad
|
||||
├── root
|
||||
└── sigrefs
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
|||
│ └── b09b2aa0ee055671c811e9ad4ba73eed211ebaa3
|
||||
└── rad
|
||||
├── id
|
||||
├── root
|
||||
└── sigrefs
|
||||
```
|
||||
|
||||
|
|
@ -148,7 +149,7 @@ Similarly, she still does not have Bob's `rad/sigrefs`:
|
|||
|
||||
``` ~alice
|
||||
$ rad inspect --sigrefs
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 7ffed605e4871bb0640ee1538181640e239b182c
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi ae3c6b77dc1ed51c1c1e6a2772339c2779fa9ba8
|
||||
```
|
||||
|
||||
And she can still list the project, without any worries:
|
||||
|
|
@ -195,6 +196,6 @@ $ rad sync -f
|
|||
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
|
||||
✓ Fetched repository from 2 seed(s)
|
||||
$ rad inspect --sigrefs
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 7ffed605e4871bb0640ee1538181640e239b182c
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk cc068d93ee77dc134518d7d0fbe55b39804baf53
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi ae3c6b77dc1ed51c1c1e6a2772339c2779fa9ba8
|
||||
z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk dace6fe948548168a2bb687718949d9b5d9076ee
|
||||
```
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
|||
│ └── master
|
||||
└── rad
|
||||
├── id
|
||||
├── root
|
||||
└── sigrefs
|
||||
```
|
||||
|
||||
|
|
@ -33,7 +34,7 @@ And sigrefs:
|
|||
|
||||
```
|
||||
$ rad inspect --sigrefs
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi f209c9f68aa689af24220a20462e13ee9dfb2a95
|
||||
z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi 99c549702e2bcfe02b0e68d4a2224fb7a1524529
|
||||
```
|
||||
|
||||
Or display the repository identity's payload and delegates:
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
|||
│ └── 696ec5508494692899337afe6713fe1796d0315c
|
||||
└── rad
|
||||
├── id
|
||||
├── root
|
||||
└── sigrefs
|
||||
```
|
||||
|
||||
|
|
@ -115,5 +116,6 @@ z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
|||
│ └── master
|
||||
└── rad
|
||||
├── id
|
||||
├── root
|
||||
└── sigrefs
|
||||
```
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ $ rad sync status --sort-by alias
|
|||
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ● Node Address Status Tip Timestamp │
|
||||
├──────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ● alice (you) alice.radicle.example:8776 unannounced a9ce0d1 [ ... ] │
|
||||
│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 out-of-sync f209c9f [ ... ] │
|
||||
│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 out-of-sync f209c9f [ ... ] │
|
||||
│ ● alice (you) alice.radicle.example:8776 unannounced 056b1db [ ... ] │
|
||||
│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 out-of-sync 99c5497 [ ... ] │
|
||||
│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 out-of-sync 99c5497 [ ... ] │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
|
|
@ -37,9 +37,9 @@ $ rad sync status --sort-by alias
|
|||
╭─────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ● Node Address Status Tip Timestamp │
|
||||
├─────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ● alice (you) alice.radicle.example:8776 a9ce0d1 [ ... ] │
|
||||
│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 synced a9ce0d1 [ ... ] │
|
||||
│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 synced a9ce0d1 [ ... ] │
|
||||
│ ● alice (you) alice.radicle.example:8776 056b1db [ ... ] │
|
||||
│ ● bob z6Mkt67…v4N1tRk bob.radicle.example:8776 synced 056b1db [ ... ] │
|
||||
│ ● eve z6Mkux1…nVhib7Z eve.radicle.example:8776 synced 056b1db [ ... ] │
|
||||
╰─────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ use radicle::node::Database;
|
|||
use radicle::node::UserAgent;
|
||||
use radicle::node::{address, Alias, ConnectOptions};
|
||||
use radicle::rad;
|
||||
use radicle::storage::refs::{RefsAt, SignedRefsAt};
|
||||
use radicle::storage::refs::{RefsAt, SignedRefsAt, IDENTITY_ROOT};
|
||||
use radicle::storage::{ReadRepository, RemoteRepository};
|
||||
use radicle::Storage;
|
||||
|
||||
|
|
@ -353,9 +353,15 @@ where
|
|||
ann.into().signed(self.signer()).into()
|
||||
}
|
||||
|
||||
pub fn signed_refs_at(&self, refs: Refs, at: radicle::git::Oid) -> SignedRefsAt {
|
||||
pub fn signed_refs_at<R: ReadRepository>(
|
||||
&self,
|
||||
mut refs: Refs,
|
||||
at: radicle::git::Oid,
|
||||
repo: &R,
|
||||
) -> SignedRefsAt {
|
||||
refs.insert(IDENTITY_ROOT.to_ref_string(), repo.identity_root().unwrap());
|
||||
SignedRefsAt {
|
||||
sigrefs: refs.signed(self.signer()).unwrap(),
|
||||
sigrefs: refs.signed(self.signer()).unwrap().verified(repo).unwrap(),
|
||||
at,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -877,13 +877,15 @@ fn test_refs_announcement_followed() {
|
|||
let mut bob = Peer::with_storage("bob", [8, 8, 8, 8], storage_bob);
|
||||
|
||||
let node_id = alice.id;
|
||||
alice.storage_mut().repo_mut(&rid).remotes.insert(
|
||||
let repo = alice.storage_mut().repo_mut(&rid);
|
||||
|
||||
repo.remotes.insert(
|
||||
node_id,
|
||||
bob.signed_refs_at(arbitrary::gen::<Refs>(8), arbitrary::oid()),
|
||||
bob.signed_refs_at(arbitrary::gen::<Refs>(8), arbitrary::oid(), repo),
|
||||
);
|
||||
|
||||
// Generate some refs for Bob under their own node_id.
|
||||
let sigrefs = bob.signed_refs_at(arbitrary::gen::<Refs>(8), arbitrary::oid());
|
||||
let sigrefs = bob.signed_refs_at(arbitrary::gen::<Refs>(8), arbitrary::oid(), repo);
|
||||
let node_id = bob.id;
|
||||
bob.init();
|
||||
bob.storage_mut()
|
||||
|
|
@ -1544,9 +1546,10 @@ fn test_queued_fetch_from_ann_same_rid() {
|
|||
|
||||
// Finish the 1st fetch.
|
||||
// Ensure the ref is in the storage and cache.
|
||||
alice.storage_mut().repo_mut(&rid).remotes.insert(
|
||||
let repo = alice.storage_mut().repo_mut(&rid);
|
||||
repo.remotes.insert(
|
||||
carol.id(),
|
||||
carol.signed_refs_at(arbitrary::gen::<Refs>(1), oid),
|
||||
carol.signed_refs_at(arbitrary::gen::<Refs>(1), oid, repo),
|
||||
);
|
||||
alice
|
||||
.database_mut()
|
||||
|
|
|
|||
|
|
@ -210,6 +210,11 @@ fn notify(
|
|||
// be a separate notification on the identity COB itself.
|
||||
continue;
|
||||
}
|
||||
if r == *git::refs::storage::IDENTITY_ROOT {
|
||||
// Don't notify about the peers's identity root pointer. This is only used
|
||||
// for sigref verification.
|
||||
continue;
|
||||
}
|
||||
if let Some(rest) = r.strip_prefix(git::refname!("refs/heads/patches")) {
|
||||
if radicle::cob::ObjectId::from_str(rest.as_str()).is_ok() {
|
||||
// Don't notify about patch branches, since we already get
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ pub enum ApplyError {
|
|||
/// This error indicates that the operations are not being applied
|
||||
/// in causal order, which is a requirement for this CRDT.
|
||||
///
|
||||
/// For example, this can occur if an operation references anothern operation
|
||||
/// For example, this can occur if an operation references another operation
|
||||
/// that hasn't happened yet.
|
||||
#[error("causal dependency {0:?} missing")]
|
||||
Missing(EntryId),
|
||||
|
|
|
|||
|
|
@ -341,7 +341,7 @@ impl<'a, R: WriteRepository> Merged<'a, R> {
|
|||
self,
|
||||
working: &git::raw::Repository,
|
||||
signer: &G,
|
||||
) -> Result<(), storage::Error> {
|
||||
) -> Result<(), storage::RepositoryError> {
|
||||
let nid = signer.public_key();
|
||||
let stored_ref = git::refs::patch(&self.patch).with_namespace(nid.into());
|
||||
let working_ref = git::refs::workdir::patch_upstream(&self.patch);
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ pub enum Error {
|
|||
#[error("object `{1}` of type `{0}` was not found")]
|
||||
NotFound(TypeName, ObjectId),
|
||||
#[error("signed refs: {0}")]
|
||||
SignRefs(#[from] storage::Error),
|
||||
SignRefs(Box<storage::RepositoryError>),
|
||||
#[error("invalid or unknown embed URI: {0}")]
|
||||
EmbedUri(Uri),
|
||||
#[error(transparent)]
|
||||
|
|
@ -181,7 +181,9 @@ where
|
|||
changes,
|
||||
},
|
||||
)?;
|
||||
self.repo.sign_refs(signer).map_err(Error::SignRefs)?;
|
||||
self.repo
|
||||
.sign_refs(signer)
|
||||
.map_err(|e| Error::SignRefs(Box::new(e)))?;
|
||||
|
||||
Ok(updated)
|
||||
}
|
||||
|
|
@ -220,8 +222,14 @@ where
|
|||
contents,
|
||||
},
|
||||
)?;
|
||||
self.repo.sign_refs(signer).map_err(Error::SignRefs)?;
|
||||
|
||||
// Nb. We can't sign our refs before the identity refs exist, which are created after
|
||||
// the identity COB is created. Therefore we manually sign refs when creating identity
|
||||
// COBs.
|
||||
if T::type_name() != &*crate::cob::identity::TYPENAME {
|
||||
self.repo
|
||||
.sign_refs(signer)
|
||||
.map_err(|e| Error::SignRefs(Box::new(e)))?;
|
||||
}
|
||||
Ok((*cob.id(), cob.object))
|
||||
}
|
||||
|
||||
|
|
@ -234,7 +242,9 @@ where
|
|||
{
|
||||
Ok(_) => {
|
||||
cob::remove(self.repo, signer.public_key(), T::type_name(), id)?;
|
||||
self.repo.sign_refs(signer).map_err(Error::SignRefs)?;
|
||||
self.repo
|
||||
.sign_refs(signer)
|
||||
.map_err(|e| Error::SignRefs(Box::new(e)))?;
|
||||
Ok(())
|
||||
}
|
||||
Err(err) if err.code() == git::raw::ErrorCode::NotFound => Ok(()),
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ pub mod refs {
|
|||
|
||||
use super::*;
|
||||
|
||||
/// Where the project's identity document is stored.
|
||||
/// Where the repo's identity document is stored.
|
||||
///
|
||||
/// `refs/rad/id`
|
||||
///
|
||||
|
|
@ -192,6 +192,14 @@ pub mod refs {
|
|||
Qualified::from_components(name::component!("rad"), name::component!("id"), None)
|
||||
});
|
||||
|
||||
/// Where the repo's identity root document is stored.
|
||||
///
|
||||
/// `refs/rad/root`
|
||||
///
|
||||
pub static IDENTITY_ROOT: Lazy<Qualified> = Lazy::new(|| {
|
||||
Qualified::from_components(name::component!("rad"), name::component!("root"), None)
|
||||
});
|
||||
|
||||
/// Where the project's signed references are stored.
|
||||
///
|
||||
/// `refs/rad/sigrefs`
|
||||
|
|
@ -250,6 +258,14 @@ pub mod refs {
|
|||
IDENTITY_BRANCH.with_namespace(remote.into())
|
||||
}
|
||||
|
||||
/// Get the root of the branch where the project's identity document is stored.
|
||||
///
|
||||
/// `refs/namespaces/<remote>/refs/rad/root`
|
||||
///
|
||||
pub fn id_root(remote: &RemoteId) -> Namespaced {
|
||||
IDENTITY_ROOT.with_namespace(remote.into())
|
||||
}
|
||||
|
||||
/// Get the branch where the `remote`'s signed references are
|
||||
/// stored.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#![allow(clippy::let_unit_value)]
|
||||
#![warn(clippy::unwrap_used)]
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
|
@ -54,8 +55,7 @@ pub fn init<G: Signer, S: WriteStorage>(
|
|||
storage: S,
|
||||
) -> Result<(RepoId, identity::Doc<Verified>, SignedRefs<Verified>), InitError> {
|
||||
// TODO: Better error when project id already exists in storage, but remote doesn't.
|
||||
let pk = signer.public_key();
|
||||
let delegate = identity::Did::from(*pk);
|
||||
let delegate: identity::Did = signer.public_key().into();
|
||||
let proj = Project::new(
|
||||
name.to_owned(),
|
||||
description.to_owned(),
|
||||
|
|
@ -70,10 +70,10 @@ pub fn init<G: Signer, S: WriteStorage>(
|
|||
)
|
||||
})?;
|
||||
let doc = identity::Doc::initial(proj, delegate, visibility).verified()?;
|
||||
let (project, _) = Repository::init(&doc, &storage, signer)?;
|
||||
let (project, identity) = Repository::init(&doc, &storage, signer)?;
|
||||
let url = git::Url::from(project.id);
|
||||
|
||||
match init_configure(repo, &project, pk, &default_branch, &url, signer) {
|
||||
match init_configure(repo, &project, &default_branch, &url, identity, signer) {
|
||||
Ok(signed) => Ok((project.id, doc, signed)),
|
||||
Err(err) => {
|
||||
if let Err(e) = project.remove() {
|
||||
|
|
@ -91,15 +91,17 @@ pub fn init<G: Signer, S: WriteStorage>(
|
|||
|
||||
fn init_configure<G>(
|
||||
repo: &git2::Repository,
|
||||
project: &Repository,
|
||||
pk: &crypto::PublicKey,
|
||||
stored: &Repository,
|
||||
default_branch: &BranchName,
|
||||
url: &git::Url,
|
||||
identity: git::Oid,
|
||||
signer: &G,
|
||||
) -> Result<SignedRefs<Verified>, InitError>
|
||||
where
|
||||
G: crypto::Signer,
|
||||
{
|
||||
let pk = signer.public_key();
|
||||
|
||||
git::configure_repository(repo)?;
|
||||
git::configure_remote(repo, &REMOTE_NAME, url, &url.clone().with_namespace(*pk))?;
|
||||
git::push(
|
||||
|
|
@ -110,10 +112,11 @@ where
|
|||
&git::fmt::lit::refs_heads(default_branch).into(),
|
||||
)],
|
||||
)?;
|
||||
stored.set_remote_identity_root_to(pk, identity)?;
|
||||
stored.set_identity_head_to(identity)?;
|
||||
stored.set_head()?;
|
||||
|
||||
let signed = project.sign_refs(signer)?;
|
||||
let _head = project.set_identity_head()?;
|
||||
let _head = project.set_head()?;
|
||||
let signed = stored.sign_refs(signer)?;
|
||||
|
||||
Ok(signed)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -355,8 +355,8 @@ impl Remote<Unverified> {
|
|||
}
|
||||
|
||||
impl Remote<Unverified> {
|
||||
pub fn verified(self) -> Result<Remote<Verified>, crypto::Error> {
|
||||
let refs = self.refs.verified()?;
|
||||
pub fn verified<R: ReadRepository>(self, repo: &R) -> Result<Remote<Verified>, Error> {
|
||||
let refs = self.refs.verified(repo)?;
|
||||
|
||||
Ok(Remote { refs })
|
||||
}
|
||||
|
|
@ -628,11 +628,24 @@ pub trait WriteRepository: ReadRepository + SignRepository {
|
|||
fn set_head(&self) -> Result<SetHead, RepositoryError>;
|
||||
/// Set the repository 'rad/id' to the canonical commit, agreed by quorum.
|
||||
fn set_identity_head(&self) -> Result<Oid, RepositoryError> {
|
||||
let head = self.canonical_identity_head()?;
|
||||
let head = self.canonical_identity_head().unwrap();
|
||||
self.set_identity_head_to(head)?;
|
||||
|
||||
Ok(head)
|
||||
}
|
||||
/// Set the identity root reference to the canonical identity root commit.
|
||||
fn set_remote_identity_root(&self, remote: &RemoteId) -> Result<Oid, RepositoryError> {
|
||||
let root = self.identity_root()?;
|
||||
self.set_remote_identity_root_to(remote, root)?;
|
||||
|
||||
Ok(root)
|
||||
}
|
||||
/// Set the identity root reference to the given commit.
|
||||
fn set_remote_identity_root_to(
|
||||
&self,
|
||||
remote: &RemoteId,
|
||||
root: Oid,
|
||||
) -> Result<(), RepositoryError>;
|
||||
/// Set the repository 'rad/id' to the given commit.
|
||||
fn set_identity_head_to(&self, commit: Oid) -> Result<(), RepositoryError>;
|
||||
/// Set the user info of the Git repository.
|
||||
|
|
@ -644,7 +657,7 @@ pub trait WriteRepository: ReadRepository + SignRepository {
|
|||
/// Allows signing refs.
|
||||
pub trait SignRepository {
|
||||
/// Sign the repository's refs under the `refs/rad/sigrefs` branch.
|
||||
fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, Error>;
|
||||
fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, RepositoryError>;
|
||||
}
|
||||
|
||||
impl<T, S> ReadStorage for T
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@ use crate::node::SyncedAt;
|
|||
use crate::storage::refs;
|
||||
use crate::storage::refs::{Refs, SignedRefs, SignedRefsAt};
|
||||
use crate::storage::{
|
||||
ReadRepository, ReadStorage, Remote, Remotes, RepositoryError, RepositoryInfo, SetHead,
|
||||
SignRepository, WriteRepository, WriteStorage,
|
||||
ReadRepository, ReadStorage, Remote, Remotes, RepositoryInfo, SetHead, SignRepository,
|
||||
WriteRepository, WriteStorage,
|
||||
};
|
||||
use crate::{git, node};
|
||||
|
||||
pub use crate::git::{
|
||||
ext, raw, refname, refspec, Oid, PatternStr, Qualified, RefError, RefString, UserInfo,
|
||||
};
|
||||
pub use crate::storage::Error;
|
||||
pub use crate::storage::{Error, RepositoryError};
|
||||
|
||||
use super::refs::RefsAt;
|
||||
use super::{RemoteId, RemoteRepository, ValidateRepository};
|
||||
|
|
@ -773,9 +773,8 @@ impl ReadRepository for Repository {
|
|||
|
||||
fn identity_root(&self) -> Result<Oid, RepositoryError> {
|
||||
let oid = self.backend.refname_to_id(CANONICAL_IDENTITY.as_str())?;
|
||||
let walk = self.revwalk(oid.into())?.collect::<Vec<_>>();
|
||||
let root = walk
|
||||
.into_iter()
|
||||
let root = self
|
||||
.revwalk(oid.into())?
|
||||
.last()
|
||||
.ok_or(RepositoryError::Doc(DocError::Missing))??;
|
||||
|
||||
|
|
@ -783,14 +782,8 @@ impl ReadRepository for Repository {
|
|||
}
|
||||
|
||||
fn identity_root_of(&self, remote: &RemoteId) -> Result<Oid, RepositoryError> {
|
||||
let oid = self.identity_head_of(remote)?;
|
||||
let walk = self.revwalk(oid)?.collect::<Vec<_>>();
|
||||
let root = walk
|
||||
.into_iter()
|
||||
.last()
|
||||
.ok_or(RepositoryError::Doc(DocError::Missing))??;
|
||||
|
||||
Ok(root.into())
|
||||
self.reference_oid(remote, &git::refs::storage::IDENTITY_ROOT)
|
||||
.map_err(RepositoryError::from)
|
||||
}
|
||||
|
||||
fn canonical_identity_head(&self) -> Result<Oid, RepositoryError> {
|
||||
|
|
@ -857,6 +850,19 @@ impl WriteRepository for Repository {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn set_remote_identity_root_to(
|
||||
&self,
|
||||
remote: &RemoteId,
|
||||
root: Oid,
|
||||
) -> Result<(), RepositoryError> {
|
||||
let refname = git::refs::storage::id_root(remote);
|
||||
|
||||
self.raw()
|
||||
.reference(refname.as_str(), *root, true, "set-id-root (radicle)")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_user(&self, info: &UserInfo) -> Result<(), Error> {
|
||||
let mut config = self.backend.config()?;
|
||||
config.set_str("user.name", &info.name())?;
|
||||
|
|
@ -870,20 +876,24 @@ impl WriteRepository for Repository {
|
|||
}
|
||||
|
||||
impl SignRepository for Repository {
|
||||
fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, Error> {
|
||||
fn sign_refs<G: Signer>(&self, signer: &G) -> Result<SignedRefs<Verified>, RepositoryError> {
|
||||
let remote = signer.public_key();
|
||||
// Ensure the root reference is set, which is checked during sigref verification.
|
||||
if self.identity_root_of(remote).is_err() {
|
||||
self.set_remote_identity_root(remote)?;
|
||||
}
|
||||
let mut refs = self.references_of(remote)?;
|
||||
// Don't sign the `rad/sigrefs` ref itself, and don't sign invalid OIDs.
|
||||
refs.retain(|name, oid| {
|
||||
name.as_refstr() != refs::SIGREFS_BRANCH.as_ref() && !oid.is_zero()
|
||||
});
|
||||
let signed = refs.signed(signer)?;
|
||||
|
||||
let signed = refs.signed(signer)?.verified(self)?;
|
||||
signed.save(self)?;
|
||||
|
||||
Ok(signed)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod trailers {
|
||||
use std::str::FromStr;
|
||||
|
||||
|
|
@ -946,7 +956,6 @@ mod tests {
|
|||
use crate::git;
|
||||
use crate::storage::refs::SIGREFS_BRANCH;
|
||||
use crate::storage::{ReadRepository, ReadStorage};
|
||||
use crate::test::arbitrary;
|
||||
use crate::test::fixtures;
|
||||
|
||||
#[test]
|
||||
|
|
@ -999,7 +1008,13 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
refs,
|
||||
vec![&cob, "refs/heads/master", "refs/rad/id", "refs/rad/sigrefs"]
|
||||
vec![
|
||||
&cob,
|
||||
"refs/heads/master",
|
||||
"refs/rad/id",
|
||||
"refs/rad/root",
|
||||
"refs/rad/sigrefs"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1009,28 +1024,26 @@ mod tests {
|
|||
let mut rng = fastrand::Rng::new();
|
||||
let signer = MockSigner::new(&mut rng);
|
||||
let storage = Storage::open(tmp.path(), fixtures::user()).unwrap();
|
||||
let proj_id = arbitrary::gen::<RepoId>(1);
|
||||
let alice = *signer.public_key();
|
||||
let project = storage.create(proj_id).unwrap();
|
||||
let backend = &project.backend;
|
||||
let (rid, _, working, _) =
|
||||
fixtures::project(tmp.path().join("project"), &storage, &signer).unwrap();
|
||||
let stored = storage.repository(rid).unwrap();
|
||||
let sig = git2::Signature::now(&alice.to_string(), "anonymous@radicle.xyz").unwrap();
|
||||
let head = git::initial_commit(backend, &sig).unwrap();
|
||||
let tree =
|
||||
git::write_tree(Path::new("README"), "Hello World!\n".as_bytes(), backend).unwrap();
|
||||
let head = working.head().unwrap().peel_to_commit().unwrap();
|
||||
|
||||
git::commit(
|
||||
backend,
|
||||
&working,
|
||||
&head,
|
||||
&git::RefString::try_from(format!("refs/remotes/{alice}/heads/master")).unwrap(),
|
||||
"Second commit",
|
||||
&sig,
|
||||
&tree,
|
||||
&head.tree().unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let signed = project.sign_refs(&signer).unwrap();
|
||||
let remote = project.remote(&alice).unwrap();
|
||||
let mut unsigned = project.references_of(&alice).unwrap();
|
||||
let signed = stored.sign_refs(&signer).unwrap();
|
||||
let remote = stored.remote(&alice).unwrap();
|
||||
let mut unsigned = stored.references_of(&alice).unwrap();
|
||||
|
||||
// The signed refs doesn't contain the signature ref itself.
|
||||
let sigref = (*SIGREFS_BRANCH).to_ref_string();
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use crate::{
|
|||
|
||||
use super::{RemoteId, Repository};
|
||||
|
||||
pub use crate::cob::{store, ObjectId};
|
||||
pub use crate::cob::{store, ObjectId, Store};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ObjectsError {
|
||||
|
|
@ -226,7 +226,7 @@ impl<'a, R: storage::ReadRepository> SignRepository for DraftStore<'a, R> {
|
|||
fn sign_refs<G: crypto::Signer>(
|
||||
&self,
|
||||
signer: &G,
|
||||
) -> Result<storage::refs::SignedRefs<Verified>, Error> {
|
||||
) -> Result<storage::refs::SignedRefs<Verified>, RepositoryError> {
|
||||
// Since this is a draft store, we do not actually want to sign the refs.
|
||||
// Instead, we just return the existing signed refs.
|
||||
let remote = self.repo.remote(signer.public_key())?;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use crate::git::ext as git_ext;
|
|||
use crate::git::Oid;
|
||||
use crate::profile::env;
|
||||
use crate::storage;
|
||||
use crate::storage::{ReadRepository, RemoteId, WriteRepository};
|
||||
use crate::storage::{ReadRepository, RemoteId, RepoId, WriteRepository};
|
||||
|
||||
pub use crate::git::refs::storage::*;
|
||||
|
||||
|
|
@ -44,6 +44,12 @@ pub enum Error {
|
|||
Canonical(#[from] canonical::Error),
|
||||
#[error("invalid reference")]
|
||||
InvalidRef,
|
||||
#[error("missing identity root reference '{0}'")]
|
||||
MissingIdentityRoot(git::RefString),
|
||||
#[error("missing identity object '{0}'")]
|
||||
MissingIdentity(Oid),
|
||||
#[error("mismatched identity: local {local}, remote {remote}")]
|
||||
MismatchedIdentity { local: RepoId, remote: RepoId },
|
||||
#[error("invalid reference: {0}")]
|
||||
Ref(#[from] git::RefError),
|
||||
#[error(transparent)]
|
||||
|
|
@ -72,27 +78,17 @@ pub struct Refs(BTreeMap<git::RefString, Oid>);
|
|||
|
||||
impl Refs {
|
||||
/// Verify the given signature on these refs, and return [`SignedRefs`] on success.
|
||||
pub fn verified(
|
||||
pub fn verified<R: ReadRepository>(
|
||||
self,
|
||||
signer: PublicKey,
|
||||
signature: Signature,
|
||||
repo: &R,
|
||||
) -> Result<SignedRefs<Verified>, Error> {
|
||||
let refs = self;
|
||||
let msg = refs.canonical();
|
||||
|
||||
match signer.verify(msg, &signature) {
|
||||
Ok(()) => Ok(SignedRefs {
|
||||
refs,
|
||||
signature,
|
||||
id: signer,
|
||||
_verified: PhantomData,
|
||||
}),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
SignedRefs::new(self, signer, signature).verified(repo)
|
||||
}
|
||||
|
||||
/// Sign these refs with the given signer and return [`SignedRefs`].
|
||||
pub fn signed<G>(self, signer: &G) -> Result<SignedRefs<Verified>, Error>
|
||||
pub fn signed<G>(self, signer: &G) -> Result<SignedRefs<Unverified>, Error>
|
||||
where
|
||||
G: Signer,
|
||||
{
|
||||
|
|
@ -100,12 +96,7 @@ impl Refs {
|
|||
let msg = refs.canonical();
|
||||
let signature = signer.try_sign(&msg)?;
|
||||
|
||||
Ok(SignedRefs {
|
||||
refs,
|
||||
signature,
|
||||
id: *signer.public_key(),
|
||||
_verified: PhantomData,
|
||||
})
|
||||
Ok(SignedRefs::new(refs, *signer.public_key(), signature))
|
||||
}
|
||||
|
||||
/// Get a particular ref.
|
||||
|
|
@ -216,17 +207,17 @@ pub struct SignedRefs<V> {
|
|||
}
|
||||
|
||||
impl SignedRefs<Unverified> {
|
||||
pub fn new(refs: Refs, id: PublicKey, signature: Signature) -> Self {
|
||||
pub fn new(refs: Refs, author: PublicKey, signature: Signature) -> Self {
|
||||
Self {
|
||||
refs,
|
||||
signature,
|
||||
id,
|
||||
id: author,
|
||||
_verified: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verified(self) -> Result<SignedRefs<Verified>, crypto::Error> {
|
||||
match self.verify(&self.id) {
|
||||
pub fn verified<R: ReadRepository>(self, repo: &R) -> Result<SignedRefs<Verified>, Error> {
|
||||
match self.verify(repo) {
|
||||
Ok(()) => Ok(SignedRefs {
|
||||
refs: self.refs,
|
||||
signature: self.signature,
|
||||
|
|
@ -237,13 +228,36 @@ impl SignedRefs<Unverified> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self, signer: &PublicKey) -> Result<(), crypto::Error> {
|
||||
pub fn verify<R: ReadRepository>(&self, repo: &R) -> Result<(), Error> {
|
||||
let canonical = self.refs.canonical();
|
||||
let local = repo.id();
|
||||
|
||||
match signer.verify(canonical, &self.signature) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => Err(e),
|
||||
// Verify signature.
|
||||
if let Err(e) = self.id.verify(canonical, &self.signature) {
|
||||
return Err(e.into());
|
||||
}
|
||||
// If the identity root was signed, verify it points to the right place.
|
||||
if let Some(id_root) = self.refs.get(&IDENTITY_ROOT) {
|
||||
// Get the identity at the given oid.
|
||||
let Ok(doc) = repo.identity_doc_at(id_root) else {
|
||||
return Err(Error::MissingIdentity(id_root));
|
||||
};
|
||||
let remote = RepoId::from(doc.blob);
|
||||
|
||||
// Make sure the signed identity points to the local repo identity.
|
||||
if remote != local {
|
||||
return Err(Error::MismatchedIdentity { local, remote });
|
||||
}
|
||||
} else {
|
||||
// TODO(cloudhead): Make this into a hard error (`Error::MissingIdentityRoot`) for
|
||||
// repos that have migrated to the new identity document schema.
|
||||
log::debug!(
|
||||
target: "storage",
|
||||
"Signed ref verification for {} in {local}: {} is not provided",
|
||||
self.id, *IDENTITY_ROOT
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -264,20 +278,9 @@ impl SignedRefs<Verified> {
|
|||
let refs = repo.blob_at(oid, Path::new(REFS_BLOB_PATH))?;
|
||||
let signature = repo.blob_at(oid, Path::new(SIGNATURE_BLOB_PATH))?;
|
||||
let signature: crypto::Signature = signature.content().try_into()?;
|
||||
let refs = Refs::from_canonical(refs.content())?;
|
||||
|
||||
match remote.verify(refs.content(), &signature) {
|
||||
Ok(()) => {
|
||||
let refs = Refs::from_canonical(refs.content())?;
|
||||
|
||||
Ok(Self {
|
||||
refs,
|
||||
signature,
|
||||
id: remote,
|
||||
_verified: PhantomData,
|
||||
})
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
SignedRefs::new(refs, remote, signature).verified(repo)
|
||||
}
|
||||
|
||||
/// Save the signed refs to disk.
|
||||
|
|
@ -290,7 +293,7 @@ impl SignedRefs<Verified> {
|
|||
// N.b. if the signatures match then there are no updates
|
||||
let parent = match SignedRefsAt::load(*remote, repo)? {
|
||||
Some(SignedRefsAt { sigrefs, at }) if sigrefs.signature == self.signature => {
|
||||
return Ok(Updated::Unchanged { oid: at })
|
||||
return Ok(Updated::Unchanged { oid: at });
|
||||
}
|
||||
Some(SignedRefsAt { at, .. }) => Some(raw.find_commit(*at)?),
|
||||
None => None,
|
||||
|
|
@ -460,13 +463,14 @@ pub mod canonical {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::assert_matches;
|
||||
use crate::{cob::identity::Identity, rad, test::fixtures, Storage};
|
||||
use crypto::test::signer::MockSigner;
|
||||
use qcheck_macros::quickcheck;
|
||||
use storage::{git::transport, RemoteRepository, SignRepository, WriteStorage};
|
||||
|
||||
use super::*;
|
||||
use crate::assert_matches;
|
||||
use crate::{cob::identity::Identity, rad, test::fixtures, Storage};
|
||||
|
||||
#[quickcheck]
|
||||
fn prop_canonical_roundtrip(refs: Refs) {
|
||||
let encoded = refs.canonical();
|
||||
|
|
@ -476,7 +480,6 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
// Test that a user's signed refs are tied to a specific RID, and they can't simply be
|
||||
// used in a different repository.
|
||||
//
|
||||
|
|
@ -634,6 +637,13 @@ mod tests {
|
|||
|
||||
// Due to the verification, we get a validation error when trying to load Bob's remote.
|
||||
// The graft is not allowed.
|
||||
london.remote(bob.public_key()).unwrap_err();
|
||||
assert_matches!(
|
||||
london.remote(bob.public_key()),
|
||||
Err(Error::MismatchedIdentity {
|
||||
local,
|
||||
remote,
|
||||
})
|
||||
if local == london_rid && remote == paris_rid
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,12 +71,13 @@ pub fn vec<T: Eq + Arbitrary>(size: usize) -> Vec<T> {
|
|||
pub fn nonempty_storage(size: usize) -> MockStorage {
|
||||
let mut storage = gen::<MockStorage>(size);
|
||||
for _ in 0..size {
|
||||
let id = gen::<RepoId>(1);
|
||||
let doc = gen::<DocAt>(1);
|
||||
let id = RepoId::from(doc.blob);
|
||||
storage.repos.insert(
|
||||
id,
|
||||
MockRepository {
|
||||
id,
|
||||
doc: gen::<DocAt>(1),
|
||||
doc,
|
||||
remotes: HashMap::new(),
|
||||
},
|
||||
);
|
||||
|
|
@ -90,9 +91,9 @@ pub fn gen<T: Arbitrary>(size: usize) -> T {
|
|||
T::arbitrary(&mut gen)
|
||||
}
|
||||
|
||||
impl Arbitrary for storage::Remotes<crypto::Verified> {
|
||||
impl Arbitrary for storage::Remotes<crypto::Unverified> {
|
||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||
let remotes: RandomMap<storage::RemoteId, storage::Remote<crypto::Verified>> =
|
||||
let remotes: RandomMap<storage::RemoteId, storage::Remote<crypto::Unverified>> =
|
||||
Arbitrary::arbitrary(g);
|
||||
|
||||
storage::Remotes::new(remotes)
|
||||
|
|
@ -181,10 +182,10 @@ impl Arbitrary for SignedRefs<Unverified> {
|
|||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||
let bytes: [u8; 64] = Arbitrary::arbitrary(g);
|
||||
let signature = crypto::Signature::from(bytes);
|
||||
let id = PublicKey::arbitrary(g);
|
||||
let author = PublicKey::arbitrary(g);
|
||||
let refs = Refs::arbitrary(g);
|
||||
|
||||
Self::new(refs, id, signature)
|
||||
Self::new(refs, author, signature)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,13 +244,13 @@ impl Arbitrary for MockRepository {
|
|||
}
|
||||
}
|
||||
|
||||
impl Arbitrary for storage::Remote<crypto::Verified> {
|
||||
impl Arbitrary for storage::Remote<crypto::Unverified> {
|
||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||
let refs = Refs::arbitrary(g);
|
||||
let signer = MockSigner::arbitrary(g);
|
||||
let signed = refs.signed(&signer).unwrap();
|
||||
|
||||
storage::Remote::<crypto::Verified>::new(signed)
|
||||
storage::Remote::<crypto::Unverified>::new(signed)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -300,19 +300,19 @@ impl ReadRepository for MockRepository {
|
|||
}
|
||||
|
||||
fn identity_head_of(&self, _remote: &RemoteId) -> Result<Oid, git::ext::Error> {
|
||||
todo!()
|
||||
Ok(self.doc.commit)
|
||||
}
|
||||
|
||||
fn identity_root(&self) -> Result<Oid, RepositoryError> {
|
||||
todo!()
|
||||
Ok(self.doc.commit)
|
||||
}
|
||||
|
||||
fn identity_root_of(&self, _remote: &RemoteId) -> Result<Oid, RepositoryError> {
|
||||
todo!()
|
||||
Ok(self.doc.commit)
|
||||
}
|
||||
|
||||
fn canonical_identity_head(&self) -> Result<Oid, RepositoryError> {
|
||||
Ok(Oid::from_str("cccccccccccccccccccccccccccccccccccccccc").unwrap())
|
||||
Ok(self.doc.commit)
|
||||
}
|
||||
|
||||
fn merge_base(&self, _left: &Oid, _right: &Oid) -> Result<Oid, git::ext::Error> {
|
||||
|
|
@ -333,6 +333,14 @@ impl WriteRepository for MockRepository {
|
|||
todo!()
|
||||
}
|
||||
|
||||
fn set_remote_identity_root_to(
|
||||
&self,
|
||||
_remote: &RemoteId,
|
||||
_root: Oid,
|
||||
) -> Result<(), RepositoryError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn set_user(&self, _info: &git::UserInfo) -> Result<(), Error> {
|
||||
todo!()
|
||||
}
|
||||
|
|
@ -342,7 +350,7 @@ impl SignRepository for MockRepository {
|
|||
fn sign_refs<G: Signer>(
|
||||
&self,
|
||||
_signer: &G,
|
||||
) -> Result<crate::storage::refs::SignedRefs<Verified>, Error> {
|
||||
) -> Result<crate::storage::refs::SignedRefs<Verified>, RepositoryError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue