From ce3dac6324b90077a71c9bf08ca360a0073f0c6d Mon Sep 17 00:00:00 2001 From: Tiago Carvalho Date: Sun, 29 Dec 2024 19:02:23 +0000 Subject: [PATCH] sign commits --- Cargo.lock | 7 +++ Cargo.toml | 1 + src/raw/sign.rs | 45 +++++++++++++---- src/utils.rs | 129 +++++++++++++++++++++++++++++++++++------------- 4 files changed, 139 insertions(+), 43 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab7bd23..ef94c9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,6 +295,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -333,6 +339,7 @@ version = "0.3.0" dependencies = [ "anyhow", "clap", + "either", "git2", "libsignify", "minisign", diff --git a/Cargo.toml b/Cargo.toml index 672ca18..893a6a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] anyhow = "1.0.95" clap = { version = "4.5.23", features = ["derive", "env"] } +either = "1.13.0" git2 = "0.19.0" minisign = "0.7.9" rpassword = "7.3.1" diff --git a/src/raw/sign.rs b/src/raw/sign.rs index 2597f2b..9b5083e 100644 --- a/src/raw/sign.rs +++ b/src/raw/sign.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use anyhow::{Context, Result}; +use either::*; use git2::{ObjectType, Oid, Repository}; use crate::utils; @@ -24,17 +25,22 @@ pub fn sign(repo: &Repository, secret_key: &utils::PrivateKey, rev: &str) -> Res .context("Failed to look-up git object id")?; let object_ptr = object.id(); - let object_mode = match object + let object_mode_or_commit = match object .kind() .context("Failed to determine object kind to sign")? { - ObjectType::Blob => 0o100644, - ObjectType::Tree => 0o040000, - ty @ (ObjectType::Any | ObjectType::Commit | ObjectType::Tag) => { - anyhow::bail!("Unsupported object type {ty}"); + ObjectType::Blob => Left(0o100644), + ObjectType::Tree => Left(0o040000), + ObjectType::Commit => Right(object.as_commit().expect("The object is a commit")), + ty @ (ObjectType::Any | ObjectType::Tag) => { + anyhow::bail!("Unsupported or recursive object type {ty}"); } }; + let commit_author = repo + .signature() + .context("Failed to retrieve commit author")?; + let signature = secret_key.sign(object_ptr.as_bytes())?; let signature_blob = repo .blob(&signature) @@ -58,16 +64,37 @@ pub fn sign(repo: &Repository, secret_key: &utils::PrivateKey, rev: &str) -> Res tree_builder .insert("algorithm", algo_blob, 0o100644) .context("Failed to write algorithm to the tree")?; - tree_builder - .insert("object", object_ptr, object_mode) - .context("Failed to write object to the tree")?; tree_builder .insert("signature", signature_blob, 0o100644) .context("Failed to write signature to the tree")?; + let parents = object_mode_or_commit.either( + |object_mode| { + tree_builder + .insert("object", object_ptr, object_mode) + .context("Failed to write object to the tree")?; + anyhow::Ok(vec![]) + }, + |commit| anyhow::Ok(vec![commit]), + )?; + let tree_oid = tree_builder .write() .context("Failed to write tree to the object store")?; + let tree = repo + .find_tree(tree_oid) + .context("Failed to look-up newly created git tree signature")?; + + let commit_oid = repo + .commit( + None, + &commit_author, + &commit_author, + "git-signify signature", + &tree, + &parents, + ) + .context("Failed to create git signature commit")?; - Ok(tree_oid) + Ok(commit_oid) } diff --git a/src/utils.rs b/src/utils.rs index 909124e..7b2c1b4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -166,46 +166,107 @@ impl<'repo> TreeSignature<'repo> { /// Like [`TreeSignature::load`], but uses a concrete revision pointing /// to the tree signature. pub fn load_oid(repo: &'repo Repository, oid: Oid) -> Result { - let tree = repo - .find_tree(oid) - .context("No tree object found for the given revision")?; - - let (version, algorithm) = tree.get_name("version").map_or( - Ok((TreeSignatureVersion::V0, TreeSignatureAlgo::Signify)), - |version_tree_entry| { - let version_obj = version_tree_entry - .to_object(repo) - .context("The tree signature version could not be retrieved")?; - let version_blob = match version_obj.into_blob() { - Ok(blob) => blob, - Err(_) => { - return Err(anyhow!("The tree signature version object is not a blob")) - } - }; - let version = TreeSignatureVersion::from_blob(version_blob)?; - - let algorithm_obj = tree - .get_name("algorithm") - .context("Failed to look-up tree signature algorithm")? - .to_object(repo) - .context("The tree signature algorithm could not be retrieved")?; - let algorithm_blob = match algorithm_obj.into_blob() { - Ok(blob) => blob, - Err(_) => { - return Err(anyhow!("The tree signature algorithm object is not a blob")) - } - }; - let algorithm = TreeSignatureAlgo::from_blob(algorithm_blob)?; + let object = repo + .find_object(oid, None) + .context("No git object found for the given revision")?; + + match object.kind().context( + "Failed to determine kind of git object, while determining version of the signature", + )? { + ObjectType::Tree => Self::load_oid_v0(repo, object), + ObjectType::Commit => Self::load_oid_v1(repo, object), + _ => anyhow::bail!( + "Invalid object kind provided, while loading tree signature with oid={oid}" + ), + } + } - anyhow::Ok((version, algorithm)) - }, - )?; + /// Load a v0 [`TreeSignature`]. + pub fn load_oid_v0(repo: &'repo Repository, object: Object<'repo>) -> Result { + let tree = object.as_tree().with_context(|| { + format!( + "No tree signature found for object with oid={}", + object.id() + ) + })?; let object_pointer = tree .get_name("object") .context("Failed to look-up signed object in the tree")? .to_object(repo) .context("The signed object could not be retrieved")?; + let signature = { + let signature = tree + .get_name("signature") + .context("Failed to look-up signature in the tree")? + .to_object(repo) + .context("The signature object could not be retrieved")?; + signature + .into_blob() + .map_err(|_| anyhow!("The signature object in oid={} is not a blob", object.id()))? + }; + + Ok(Self { + signature, + object_pointer, + version: TreeSignatureVersion::V0, + algorithm: TreeSignatureAlgo::Signify, + }) + } + + /// Load a v1 [`TreeSignature`]. + pub fn load_oid_v1(repo: &'repo Repository, object: Object<'repo>) -> Result { + let commit = object + .as_commit() + .context("Failed to retrieve v1 git commit with signature")?; + let tree = commit + .tree() + .context("Failed to retrieve v1 git tree with signature")?; + + let version = { + let version_obj = tree + .get_name("version") + .context("Failed to look-up tree signature version")? + .to_object(repo) + .context("The tree signature version could not be retrieved")?; + let version_blob = match version_obj.into_blob() { + Ok(blob) => blob, + Err(_) => return Err(anyhow!("The tree signature version object is not a blob")), + }; + TreeSignatureVersion::from_blob(version_blob)? + }; + let algorithm = { + let algorithm_obj = tree + .get_name("algorithm") + .context("Failed to look-up tree signature algorithm")? + .to_object(repo) + .context("The tree signature algorithm could not be retrieved")?; + let algorithm_blob = match algorithm_obj.into_blob() { + Ok(blob) => blob, + Err(_) => return Err(anyhow!("The tree signature algorithm object is not a blob")), + }; + TreeSignatureAlgo::from_blob(algorithm_blob)? + }; + + let object_pointer = tree.get_name("object").map_or_else( + || { + Ok(commit + .parent(0) + .context( + "No signed `object` in the tree signature nor a parent commit \ + to be signed could be found", + )? + .into_object()) + }, + |entry| { + entry.to_object(repo).with_context(|| { + format!( + "The signed object with oid={} could not be cast to a git object", + entry.id() + ) + }) + }, + )?; let signature = { let signature = tree @@ -215,7 +276,7 @@ impl<'repo> TreeSignature<'repo> { .context("The signature object could not be retrieved")?; signature .into_blob() - .map_err(|_| anyhow!("The signature object in {oid} is not a blob"))? + .map_err(|_| anyhow!("The signature object in oid={} is not a blob", object.id()))? }; Ok(Self { -- 2.51.2