Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
Verify identity root commit signatures
Alexis Sellier committed 3 years ago
commit edafe055b467bda7dbbb56c082e12c93fe7975f6
parent c183f80a229c25d59656d4c910f1c771ea0fba01
4 files changed +49 -15
modified radicle/src/identity.rs
@@ -35,14 +35,16 @@ pub enum IdentityError {
    MismatchedRoot(Oid),
    #[error("the document root is missing")]
    MissingRoot,
+
    #[error("root commit is missing one or more delegate signatures")]
+
    MissingRootSignatures,
    #[error("commit signature for {0} is invalid: {1}")]
    InvalidSignature(PublicKey, crypto::Error),
    #[error("commit message for {0} is invalid")]
    InvalidCommitMessage(Oid),
    #[error("commit trailers for {0} are invalid: {1}")]
    InvalidCommitTrailers(Oid, trailers::Error),
-
    #[error("quorum not reached: {0} signatures for a threshold of {1}")]
-
    QuorumNotReached(usize, usize),
+
    #[error("threshold not reached: {0} signatures for a threshold of {1}")]
+
    ThresholdNotReached(usize, usize),
    #[error("identity document error: {0}")]
    Doc(#[from] doc::DocError),
}
@@ -106,6 +108,27 @@ impl Identity<Untrusted> {
        let trusted = Doc::from_json(root_blob.content())?;
        let revision = history.len() as u32;

+
        {
+
            let root_commit = repo.commit(root_oid)?;
+
            let root_msg = root_commit
+
                .message_raw()
+
                .ok_or(IdentityError::InvalidCommitMessage(root_oid))?;
+
            let root_sigs = trailers::parse_signatures(root_msg)
+
                .map_err(|e| IdentityError::InvalidCommitTrailers(root_oid, e))?;
+

+
            for (pk, sig) in &root_sigs {
+
                if let Err(err) = pk.verify(root_blob.content(), sig) {
+
                    return Err(IdentityError::InvalidSignature(*pk, err));
+
                }
+
            }
+
            // Every identity founder must have signed the root document.
+
            for founder in &trusted.delegates {
+
                if !root_sigs.iter().any(|(k, _)| k == &**founder) {
+
                    return Err(IdentityError::MissingRootSignatures);
+
                }
+
            }
+
        }
+

        let mut trusted = trusted.verified()?;
        let mut current = root;
        let mut signatures = Vec::new();
@@ -136,7 +159,10 @@ impl Identity<Untrusted> {
                .filter(|(key, _)| trusted.delegates.iter().any(|d| &**d == key))
                .count();
            if quorum < trusted.threshold {
-
                return Err(IdentityError::QuorumNotReached(quorum, trusted.threshold));
+
                return Err(IdentityError::ThresholdNotReached(
+
                    quorum,
+
                    trusted.threshold,
+
                ));
            }

            trusted = untrusted;
modified radicle/src/identity/doc.rs
@@ -167,10 +167,11 @@ impl Doc<Verified> {
    pub fn init(
        doc: &[u8],
        remote: &RemoteId,
+
        signatures: &[(&PublicKey, Signature)],
        repo: &git2::Repository,
    ) -> Result<git::Oid, DocError> {
        let tree = git::write_tree(*PATH, doc, repo)?;
-
        let oid = Doc::commit(remote, &tree, "Initialize Radicle\n", &[], repo)?;
+
        let oid = Doc::commit(remote, &tree, "Initialize Radicle\n", &[], signatures, repo)?;

        Ok(oid)
    }
@@ -182,17 +183,11 @@ impl Doc<Verified> {
        signatures: &[(&PublicKey, Signature)],
        repo: &git2::Repository,
    ) -> Result<git::Oid, DocError> {
-
        let mut msg = format!("{}\n\n", msg.trim());
-
        for (key, sig) in signatures {
-
            writeln!(&mut msg, "{}: {key} {sig}", trailers::SIGNATURE_TRAILER)
-
                .expect("in-memory writes don't fail");
-
        }
-

        let (_, doc) = self.encode()?;
        let tree = git::write_tree(*PATH, doc.as_slice(), repo)?;
        let id_ref = git::refs::storage::id(remote);
        let head = repo.find_reference(&id_ref)?.peel_to_commit()?;
-
        let oid = Doc::commit(remote, &tree, &msg, &[&head], repo)?;
+
        let oid = Doc::commit(remote, &tree, msg, &[&head], signatures, repo)?;

        Ok(oid)
    }
@@ -202,14 +197,21 @@ impl Doc<Verified> {
        tree: &git2::Tree,
        msg: &str,
        parents: &[&git2::Commit],
+
        signatures: &[(&PublicKey, Signature)],
        repo: &git2::Repository,
    ) -> Result<git::Oid, DocError> {
        let sig = repo
            .signature()
            .or_else(|_| git2::Signature::now("radicle", remote.to_string().as_str()))?;

+
        let mut msg = format!("{}\n\n", msg.trim());
+
        for (key, sig) in signatures {
+
            writeln!(&mut msg, "{}: {key} {sig}", trailers::SIGNATURE_TRAILER)
+
                .expect("in-memory writes don't fail");
+
        }
+

        let id_ref = git::refs::storage::id(remote);
-
        let oid = repo.commit(Some(&id_ref), &sig, &sig, msg, tree, parents)?;
+
        let oid = repo.commit(Some(&id_ref), &sig, &sig, &msg, tree, parents)?;

        Ok(oid.into())
    }
modified radicle/src/rad.rs
@@ -65,7 +65,7 @@ pub fn init<G: Signer>(
        default_branch: default_branch.clone(),
    };
    let doc = identity::Doc::initial(proj, delegate).verified()?;
-
    let (project, _) = Repository::init(&doc, pk, storage)?;
+
    let (project, _) = Repository::init(&doc, pk, storage, signer)?;
    let url = git::Url::from(project.id).with_namespace(*pk);

    git::configure_remote(repo, &REMOTE_NAME, &url)?;
modified radicle/src/storage/git.rs
@@ -213,15 +213,21 @@ impl Repository {
    }

    /// Create the repository's identity branch.
-
    pub fn init(
+
    pub fn init<G: Signer>(
        doc: &Doc<Verified>,
        remote: &RemoteId,
        storage: &Storage,
+
        signer: &G,
    ) -> Result<(Self, git::Oid), Error> {
        let (doc_oid, doc) = doc.encode()?;
        let id = Id::from(doc_oid);
        let repo = Self::open(paths::repository(storage, &id), id)?;
-
        let oid = Doc::init(doc.as_slice(), remote, repo.raw())?;
+
        let oid = Doc::init(
+
            doc.as_slice(),
+
            remote,
+
            &[(signer.public_key(), signer.sign(&doc))],
+
            repo.raw(),
+
        )?;

        Ok((repo, oid))
    }