radicle: Fix unverified `Remote::new` constructor
We were allowing remotes to be constructed via a function that didn't check its inputs. This is now fixed by moving the public key into the `SignedRefs` struct.
This commit is contained in:
parent
ccbfe5b6fa
commit
df25e9a2a5
|
|
@ -868,9 +868,9 @@ where
|
||||||
}
|
}
|
||||||
// Process a peer inventory update announcement by (maybe) fetching.
|
// Process a peer inventory update announcement by (maybe) fetching.
|
||||||
AnnouncementMessage::Refs(message) => {
|
AnnouncementMessage::Refs(message) => {
|
||||||
for (remote_id, theirs) in message.refs.iter() {
|
for theirs in message.refs.iter() {
|
||||||
if theirs.verify(remote_id).is_err() {
|
if theirs.verify(&theirs.id).is_err() {
|
||||||
warn!(target: "service", "Peer {relayer} relayed refs announcement with invalid signature for {remote_id}");
|
warn!(target: "service", "Peer {relayer} relayed refs announcement with invalid signature for {}", theirs.id);
|
||||||
return Err(session::Error::Misbehavior);
|
return Err(session::Error::Misbehavior);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1033,10 +1033,7 @@ where
|
||||||
trusted.remove(&self.node_id());
|
trusted.remove(&self.node_id());
|
||||||
|
|
||||||
// Check if there is at least one trusted ref.
|
// Check if there is at least one trusted ref.
|
||||||
Ok(message
|
Ok(message.refs.iter().any(|refs| trusted.contains(&refs.id)))
|
||||||
.refs
|
|
||||||
.iter()
|
|
||||||
.any(|(pub_key, _refs)| trusted.contains(pub_key)))
|
|
||||||
}
|
}
|
||||||
Err(NamespacesError::NoTrusted { rid }) => {
|
Err(NamespacesError::NoTrusted { rid }) => {
|
||||||
debug!(target: "service", "No trusted nodes to fetch {}", &rid);
|
debug!(target: "service", "No trusted nodes to fetch {}", &rid);
|
||||||
|
|
@ -1212,7 +1209,7 @@ where
|
||||||
|
|
||||||
for remote_id in remotes.into_iter() {
|
for remote_id in remotes.into_iter() {
|
||||||
if refs
|
if refs
|
||||||
.push((remote_id, repo.remote(&remote_id)?.refs.unverified()))
|
.push(repo.remote(&remote_id)?.refs.unverified())
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
warn!(
|
warn!(
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ pub struct RefsAnnouncement {
|
||||||
/// Repository identifier.
|
/// Repository identifier.
|
||||||
pub rid: Id,
|
pub rid: Id,
|
||||||
/// Updated refs.
|
/// Updated refs.
|
||||||
pub refs: BoundedVec<(NodeId, SignedRefs<Unverified>), REF_REMOTE_LIMIT>,
|
pub refs: BoundedVec<SignedRefs<Unverified>, REF_REMOTE_LIMIT>,
|
||||||
/// Time of announcement.
|
/// Time of announcement.
|
||||||
pub timestamp: Timestamp,
|
pub timestamp: Timestamp,
|
||||||
}
|
}
|
||||||
|
|
@ -163,8 +163,8 @@ impl RefsAnnouncement {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (remote_id, theirs) in self.refs.iter() {
|
for theirs in self.refs.iter() {
|
||||||
if let Ok(ours) = repo.remote(remote_id) {
|
if let Ok(ours) = repo.remote(&theirs.id) {
|
||||||
if *ours.refs != theirs.refs {
|
if *ours.refs != theirs.refs {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +188,7 @@ impl RefsAnnouncement {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((_, refs)) = self.refs.iter().find(|(nid, _)| nid == remote) {
|
if let Some(refs) = self.refs.iter().find(|refs| &refs.id == remote) {
|
||||||
let local_refs = repo.remote(remote)?.refs.unverified();
|
let local_refs = repo.remote(remote)?.refs.unverified();
|
||||||
return Ok(&local_refs == refs);
|
return Ok(&local_refs == refs);
|
||||||
}
|
}
|
||||||
|
|
@ -534,8 +534,7 @@ mod tests {
|
||||||
assert_eq!(refs.capacity(), REF_REMOTE_LIMIT);
|
assert_eq!(refs.capacity(), REF_REMOTE_LIMIT);
|
||||||
|
|
||||||
for _ in 0..refs.capacity() {
|
for _ in 0..refs.capacity() {
|
||||||
refs.push((*signer.public_key(), signed_refs.clone()))
|
refs.push(signed_refs.clone()).unwrap();
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg: Message = AnnouncementMessage::from(RefsAnnouncement {
|
let msg: Message = AnnouncementMessage::from(RefsAnnouncement {
|
||||||
|
|
@ -588,9 +587,7 @@ mod tests {
|
||||||
let signer = MockSigner::new(&mut fastrand::Rng::new());
|
let signer = MockSigner::new(&mut fastrand::Rng::new());
|
||||||
let timestamp = 0;
|
let timestamp = 0;
|
||||||
let signed_refs = refs.signed(&signer).unwrap();
|
let signed_refs = refs.signed(&signer).unwrap();
|
||||||
let refs = BoundedVec::collect_from(
|
let refs = BoundedVec::collect_from(&mut [signed_refs.unverified()].into_iter());
|
||||||
&mut [(*signer.public_key(), signed_refs.unverified())].into_iter(),
|
|
||||||
);
|
|
||||||
let message = AnnouncementMessage::Refs(RefsAnnouncement {
|
let message = AnnouncementMessage::Refs(RefsAnnouncement {
|
||||||
rid,
|
rid,
|
||||||
refs,
|
refs,
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,7 @@ where
|
||||||
if let Ok(false) = repo.is_empty() {
|
if let Ok(false) = repo.is_empty() {
|
||||||
if let Ok(remotes) = repo.remotes() {
|
if let Ok(remotes) = repo.remotes() {
|
||||||
for (remote_id, remote) in remotes.into_iter() {
|
for (remote_id, remote) in remotes.into_iter() {
|
||||||
if let Err(e) = refs.push((remote_id, remote.refs.unverified())) {
|
if let Err(e) = refs.push(remote.refs.unverified()) {
|
||||||
debug!(target: "test", "Failed to push {remote_id} to refs: {e}");
|
debug!(target: "test", "Failed to push {remote_id} to refs: {e}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1150,7 +1150,7 @@ fn test_refs_synced_event() {
|
||||||
.unverified();
|
.unverified();
|
||||||
let ann = AnnouncementMessage::from(RefsAnnouncement {
|
let ann = AnnouncementMessage::from(RefsAnnouncement {
|
||||||
rid: acme,
|
rid: acme,
|
||||||
refs: vec![(alice.id, refs)].try_into().unwrap(),
|
refs: vec![refs].try_into().unwrap(),
|
||||||
timestamp: bob.timestamp(),
|
timestamp: bob.timestamp(),
|
||||||
});
|
});
|
||||||
let msg = ann.signed(bob.signer());
|
let msg = ann.signed(bob.signer());
|
||||||
|
|
|
||||||
|
|
@ -429,6 +429,7 @@ impl<V> Encode for SignedRefs<V> {
|
||||||
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
fn encode<W: io::Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
|
||||||
let mut n = 0;
|
let mut n = 0;
|
||||||
|
|
||||||
|
n += self.id.encode(writer)?;
|
||||||
n += self.refs.encode(writer)?;
|
n += self.refs.encode(writer)?;
|
||||||
n += self.signature.encode(writer)?;
|
n += self.signature.encode(writer)?;
|
||||||
|
|
||||||
|
|
@ -438,10 +439,11 @@ impl<V> Encode for SignedRefs<V> {
|
||||||
|
|
||||||
impl Decode for SignedRefs<Unverified> {
|
impl Decode for SignedRefs<Unverified> {
|
||||||
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
fn decode<R: io::Read + ?Sized>(reader: &mut R) -> Result<Self, Error> {
|
||||||
|
let id = NodeId::decode(reader)?;
|
||||||
let refs = Refs::decode(reader)?;
|
let refs = Refs::decode(reader)?;
|
||||||
let signature = Signature::decode(reader)?;
|
let signature = Signature::decode(reader)?;
|
||||||
|
|
||||||
Ok(Self::new(refs, signature))
|
Ok(Self::new(refs, id, signature))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -205,8 +205,6 @@ impl<V> From<Remotes<V>> for HashMap<RemoteId, Refs> {
|
||||||
/// A project remote.
|
/// A project remote.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
pub struct Remote<V = Verified> {
|
pub struct Remote<V = Verified> {
|
||||||
/// ID of remote.
|
|
||||||
pub id: PublicKey,
|
|
||||||
/// Git references published under this remote, and their hashes.
|
/// Git references published under this remote, and their hashes.
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub refs: SignedRefs<V>,
|
pub refs: SignedRefs<V>,
|
||||||
|
|
@ -214,13 +212,10 @@ pub struct Remote<V = Verified> {
|
||||||
pub delegate: bool,
|
pub delegate: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<V> Remote<V> {
|
impl Remote<Unverified> {
|
||||||
// TODO(finto): This function seems out of place in the API for a couple of reasons:
|
/// Create a new unverified remotes object.
|
||||||
// * The SignedRefs aren't guaranteed to be by the `id`
|
pub fn new(refs: impl Into<SignedRefs<Unverified>>) -> Self {
|
||||||
// * I could write `Remote::<Verified>::new(id, refs) and because of the above, it's a LIE
|
|
||||||
pub fn new(id: PublicKey, refs: impl Into<SignedRefs<V>>) -> Self {
|
|
||||||
Self {
|
Self {
|
||||||
id,
|
|
||||||
refs: refs.into(),
|
refs: refs.into(),
|
||||||
delegate: false,
|
delegate: false,
|
||||||
}
|
}
|
||||||
|
|
@ -229,10 +224,9 @@ impl<V> Remote<V> {
|
||||||
|
|
||||||
impl Remote<Unverified> {
|
impl Remote<Unverified> {
|
||||||
pub fn verified(self) -> Result<Remote<Verified>, crypto::Error> {
|
pub fn verified(self) -> Result<Remote<Verified>, crypto::Error> {
|
||||||
let refs = self.refs.verified(&self.id)?;
|
let refs = self.refs.verified()?;
|
||||||
|
|
||||||
Ok(Remote {
|
Ok(Remote {
|
||||||
id: self.id,
|
|
||||||
refs,
|
refs,
|
||||||
delegate: self.delegate,
|
delegate: self.delegate,
|
||||||
})
|
})
|
||||||
|
|
@ -240,15 +234,30 @@ impl Remote<Unverified> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Remote<Verified> {
|
impl Remote<Verified> {
|
||||||
|
/// Create a new unverified remotes object.
|
||||||
|
pub fn new(refs: impl Into<SignedRefs<Verified>>) -> Self {
|
||||||
|
Self {
|
||||||
|
refs: refs.into(),
|
||||||
|
delegate: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn unverified(self) -> Remote<Unverified> {
|
pub fn unverified(self) -> Remote<Unverified> {
|
||||||
Remote {
|
Remote {
|
||||||
id: self.id,
|
|
||||||
refs: self.refs.unverified(),
|
refs: self.refs.unverified(),
|
||||||
delegate: self.delegate,
|
delegate: self.delegate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<V> Deref for Remote<V> {
|
||||||
|
type Target = SignedRefs<V>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.refs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Read-only operations on a storage instance.
|
/// Read-only operations on a storage instance.
|
||||||
pub trait ReadStorage {
|
pub trait ReadStorage {
|
||||||
type Repository: ReadRepository;
|
type Repository: ReadRepository;
|
||||||
|
|
|
||||||
|
|
@ -436,8 +436,8 @@ impl ReadRepository for Repository {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remote(&self, remote: &RemoteId) -> Result<Remote<Verified>, refs::Error> {
|
fn remote(&self, remote: &RemoteId) -> Result<Remote<Verified>, refs::Error> {
|
||||||
let refs = SignedRefs::load(remote, self)?;
|
let refs = SignedRefs::load(*remote, self)?;
|
||||||
Ok(Remote::new(*remote, refs))
|
Ok(Remote::<Verified>::new(refs))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn references_of(&self, remote: &RemoteId) -> Result<Refs, Error> {
|
fn references_of(&self, remote: &RemoteId) -> Result<Refs, Error> {
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ impl Refs {
|
||||||
/// Verify the given signature on these refs, and return [`SignedRefs`] on success.
|
/// Verify the given signature on these refs, and return [`SignedRefs`] on success.
|
||||||
pub fn verified(
|
pub fn verified(
|
||||||
self,
|
self,
|
||||||
signer: &PublicKey,
|
signer: PublicKey,
|
||||||
signature: Signature,
|
signature: Signature,
|
||||||
) -> Result<SignedRefs<Verified>, Error> {
|
) -> Result<SignedRefs<Verified>, Error> {
|
||||||
let refs = self;
|
let refs = self;
|
||||||
|
|
@ -80,6 +80,7 @@ impl Refs {
|
||||||
Ok(()) => Ok(SignedRefs {
|
Ok(()) => Ok(SignedRefs {
|
||||||
refs,
|
refs,
|
||||||
signature,
|
signature,
|
||||||
|
id: signer,
|
||||||
_verified: PhantomData,
|
_verified: PhantomData,
|
||||||
}),
|
}),
|
||||||
Err(e) => Err(e.into()),
|
Err(e) => Err(e.into()),
|
||||||
|
|
@ -98,6 +99,7 @@ impl Refs {
|
||||||
Ok(SignedRefs {
|
Ok(SignedRefs {
|
||||||
refs,
|
refs,
|
||||||
signature,
|
signature,
|
||||||
|
id: *signer.public_key(),
|
||||||
_verified: PhantomData,
|
_verified: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -200,27 +202,34 @@ impl DerefMut for Refs {
|
||||||
/// [`Unverified`].
|
/// [`Unverified`].
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
pub struct SignedRefs<V> {
|
pub struct SignedRefs<V> {
|
||||||
|
/// The signed refs.
|
||||||
pub refs: Refs,
|
pub refs: Refs,
|
||||||
|
/// The signature of the signer over the refs.
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
pub signature: Signature,
|
pub signature: Signature,
|
||||||
|
/// This is the remote under which these refs exist, and the public key of the signer.
|
||||||
|
pub id: PublicKey,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
_verified: PhantomData<V>,
|
_verified: PhantomData<V>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignedRefs<Unverified> {
|
impl SignedRefs<Unverified> {
|
||||||
pub fn new(refs: Refs, signature: Signature) -> Self {
|
pub fn new(refs: Refs, id: PublicKey, signature: Signature) -> Self {
|
||||||
Self {
|
Self {
|
||||||
refs,
|
refs,
|
||||||
signature,
|
signature,
|
||||||
|
id,
|
||||||
_verified: PhantomData,
|
_verified: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verified(self, signer: &PublicKey) -> Result<SignedRefs<Verified>, crypto::Error> {
|
pub fn verified(self) -> Result<SignedRefs<Verified>, crypto::Error> {
|
||||||
match self.verify(signer) {
|
match self.verify(&self.id) {
|
||||||
Ok(()) => Ok(SignedRefs {
|
Ok(()) => Ok(SignedRefs {
|
||||||
refs: self.refs,
|
refs: self.refs,
|
||||||
signature: self.signature,
|
signature: self.signature,
|
||||||
|
id: self.id,
|
||||||
_verified: PhantomData,
|
_verified: PhantomData,
|
||||||
}),
|
}),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
|
|
@ -238,16 +247,16 @@ impl SignedRefs<Unverified> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SignedRefs<Verified> {
|
impl SignedRefs<Verified> {
|
||||||
pub fn load<S>(remote: &RemoteId, repo: &S) -> Result<Self, Error>
|
pub fn load<S>(remote: RemoteId, repo: &S) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
S: ReadRepository,
|
S: ReadRepository,
|
||||||
{
|
{
|
||||||
let oid = repo.reference_oid(remote, &SIGREFS_BRANCH)?;
|
let oid = repo.reference_oid(&remote, &SIGREFS_BRANCH)?;
|
||||||
|
|
||||||
SignedRefs::load_at(oid, remote, repo)
|
SignedRefs::load_at(oid, remote, repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_at<S>(oid: Oid, remote: &RemoteId, repo: &S) -> Result<Self, Error>
|
pub fn load_at<S>(oid: Oid, remote: RemoteId, repo: &S) -> Result<Self, Error>
|
||||||
where
|
where
|
||||||
S: storage::ReadRepository,
|
S: storage::ReadRepository,
|
||||||
{
|
{
|
||||||
|
|
@ -262,6 +271,7 @@ impl SignedRefs<Verified> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
refs,
|
refs,
|
||||||
signature,
|
signature,
|
||||||
|
id: remote,
|
||||||
_verified: PhantomData,
|
_verified: PhantomData,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -335,6 +345,7 @@ impl SignedRefs<Verified> {
|
||||||
SignedRefs {
|
SignedRefs {
|
||||||
refs: self.refs,
|
refs: self.refs,
|
||||||
signature: self.signature,
|
signature: self.signature,
|
||||||
|
id: self.id,
|
||||||
_verified: PhantomData,
|
_verified: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use std::ops::RangeBounds;
|
||||||
use std::{iter, net};
|
use std::{iter, net};
|
||||||
|
|
||||||
use crypto::test::signer::MockSigner;
|
use crypto::test::signer::MockSigner;
|
||||||
use crypto::{PublicKey, Signer, Unverified, Verified};
|
use crypto::{PublicKey, Unverified, Verified};
|
||||||
use nonempty::NonEmpty;
|
use nonempty::NonEmpty;
|
||||||
use qcheck::Arbitrary;
|
use qcheck::Arbitrary;
|
||||||
|
|
||||||
|
|
@ -137,9 +137,10 @@ impl Arbitrary for SignedRefs<Unverified> {
|
||||||
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
fn arbitrary(g: &mut qcheck::Gen) -> Self {
|
||||||
let bytes: [u8; 64] = Arbitrary::arbitrary(g);
|
let bytes: [u8; 64] = Arbitrary::arbitrary(g);
|
||||||
let signature = crypto::Signature::from(bytes);
|
let signature = crypto::Signature::from(bytes);
|
||||||
|
let id = PublicKey::arbitrary(g);
|
||||||
let refs = Refs::arbitrary(g);
|
let refs = Refs::arbitrary(g);
|
||||||
|
|
||||||
Self::new(refs, signature)
|
Self::new(refs, id, signature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -195,7 +196,7 @@ impl Arbitrary for storage::Remote<crypto::Verified> {
|
||||||
let signer = MockSigner::arbitrary(g);
|
let signer = MockSigner::arbitrary(g);
|
||||||
let signed = refs.signed(&signer).unwrap();
|
let signed = refs.signed(&signer).unwrap();
|
||||||
|
|
||||||
storage::Remote::new(*signer.public_key(), signed)
|
storage::Remote::<crypto::Verified>::new(signed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -151,10 +151,13 @@ impl ReadRepository for MockRepository {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remote(&self, remote: &RemoteId) -> Result<Remote<Verified>, refs::Error> {
|
fn remote(&self, id: &RemoteId) -> Result<Remote<Verified>, refs::Error> {
|
||||||
self.remotes
|
self.remotes
|
||||||
.get(remote)
|
.get(id)
|
||||||
.map(|refs| Remote::new(*remote, refs.clone()))
|
.map(|refs| Remote {
|
||||||
|
refs: refs.clone(),
|
||||||
|
delegate: false,
|
||||||
|
})
|
||||||
.ok_or(refs::Error::InvalidRef)
|
.ok_or(refs::Error::InvalidRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -162,7 +165,15 @@ impl ReadRepository for MockRepository {
|
||||||
Ok(self
|
Ok(self
|
||||||
.remotes
|
.remotes
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, refs)| (*id, Remote::new(*id, refs.clone())))
|
.map(|(id, refs)| {
|
||||||
|
(
|
||||||
|
*id,
|
||||||
|
Remote {
|
||||||
|
refs: refs.clone(),
|
||||||
|
delegate: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue