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:
Alexis Sellier 2023-04-26 10:57:26 +02:00
parent ccbfe5b6fa
commit df25e9a2a5
No known key found for this signature in database
10 changed files with 75 additions and 47 deletions

View File

@ -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!(

View File

@ -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,

View File

@ -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;
} }

View File

@ -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());

View File

@ -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))
} }
} }

View File

@ -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;

View File

@ -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> {

View File

@ -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,
} }
} }

View File

@ -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)
} }
} }

View File

@ -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())
} }