Skip to content
Clone
a06673b
185 lines 6.1 KB Rust
//! signature usecase: signature verification.
use gix::hash::ObjectId;
use crate::GitRepository;
use crate::signature::impls::SignatureOps;
use crate::signature::types::{SignatureInfo, VerifyResult};
fn extract_signature(
extra_headers: &[(gix::bstr::BString, gix::bstr::BString)],
) -> Option<&gix::bstr::BString> {
for (k, v) in extra_headers {
if k.eq_ignore_ascii_case(b"gpgsig") {
return Some(v);
}
}
None
}
/// Verify a PGP signature over the given signed data using the system `gpg`.
///
/// Returns `Ok(true)` if the signature is valid, `Ok(false)` if invalid/unsigned.
/// Returns `Err` if gpg is unavailable (fail-closed so callers can decide).
fn verify_pgp(signature: &[u8], signed_data: &[u8]) -> Result<bool, crate::Error> {
use std::io::Write;
use std::process::{Command, Stdio};
let mut child = Command::new("gpg")
.arg("--batch")
.arg("--verify")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(|e| crate::Error::Other(format!("gpg unavailable: {e}")))?;
let mut stdin = child
.stdin
.take()
.ok_or_else(|| crate::Error::Other("gpg stdin unavailable".to_string()))?;
// Write signature then signed data to gpg's stdin.
stdin
.write_all(signature)
.map_err(|e| crate::Error::Io(e.to_string()))?;
stdin
.write_all(b"\n")
.map_err(|e| crate::Error::Io(e.to_string()))?;
stdin
.write_all(signed_data)
.map_err(|e| crate::Error::Io(e.to_string()))?;
drop(stdin);
let output = child
.wait_with_output()
.map_err(|e| crate::Error::Io(e.to_string()))?;
Ok(output.status.success())
}
impl SignatureOps for GitRepository {
fn signature_verify_commit(&self, id: &ObjectId) -> Result<VerifyResult, crate::Error> {
let repo = self.repo()?;
// Get the raw commit bytes so we can extract the PGP signature and
// the exact signed data.
let raw = repo
.find_object(*id)
.map_err(|e| crate::Error::Find(e.to_string()))?
.data
.to_vec();
let (sig, signed_data) =
match gix::diff::object::CommitRefIter::signature(&raw, repo.object_hash()) {
Ok(Some((sig, signed_data))) => (sig, signed_data),
Ok(None) | Err(_) => {
return Ok(VerifyResult {
signed: false,
valid: false,
key_id: None,
signer: None,
});
}
};
let valid = verify_pgp(sig.as_ref(), &signed_data.to_bstring())?;
let commit = repo
.find_commit(*id)
.map_err(|e| crate::Error::Find(e.to_string()))?;
let decoded = commit
.decode()
.map_err(|e| crate::Error::Find(e.to_string()))?
.into_owned()
.map_err(|e| crate::Error::Find(e.to_string()))?;
let info = extract_signature(&decoded.extra_headers).map(|s| SignatureInfo {
signature: s.to_string(),
payload: decoded.message.to_string(),
key_id: None,
signer: None,
});
Ok(VerifyResult {
signed: info.is_some(),
valid,
key_id: None,
signer: None,
})
}
fn signature_verify_tag(&self, id: &ObjectId) -> Result<VerifyResult, crate::Error> {
let repo = self.repo()?;
let tag = repo
.find_tag(*id)
.map_err(|e| crate::Error::Find(e.to_string()))?;
let tag_ref = tag
.decode()
.map_err(|e| crate::Error::Find(e.to_string()))?;
let Some(sig) = tag_ref.pgp_signature else {
return Ok(VerifyResult {
signed: false,
valid: false,
key_id: None,
signer: None,
});
};
// Reconstruct the signed tag payload (gix does not expose it directly,
// so verify the signature over the raw tag object bytes).
let mut buf = Vec::new();
use gix::objs::WriteTo;
tag_ref
.write_to(&mut buf)
.map_err(|e| crate::Error::Odb(e.to_string()))?;
let valid = verify_pgp(sig.as_ref(), &buf)?;
Ok(VerifyResult {
signed: true,
valid,
key_id: None,
signer: None,
})
}
fn signature_commit_signature(
&self,
id: &ObjectId,
) -> Result<Option<SignatureInfo>, crate::Error> {
if let Some(hit) = self.cache_get_oid::<Option<SignatureInfo>>("sig_commit_meta", id) {
return Ok(hit);
}
let repo = self.repo()?;
let commit = repo
.find_commit(*id)
.map_err(|e| crate::Error::Find(e.to_string()))?;
let decoded = commit
.decode()
.map_err(|e| crate::Error::Find(e.to_string()))?
.into_owned()
.map_err(|e| crate::Error::Find(e.to_string()))?;
let v = extract_signature(&decoded.extra_headers).map(|s| SignatureInfo {
signature: s.to_string(),
payload: decoded.message.to_string(),
key_id: None,
signer: None,
});
self.cache_put_oid("sig_commit_meta", id, &v);
Ok(v)
}
fn signature_tag_signature(
&self,
id: &ObjectId,
) -> Result<Option<SignatureInfo>, crate::Error> {
if let Some(hit) = self.cache_get_oid::<Option<SignatureInfo>>("sig_tag_meta", id) {
return Ok(hit);
}
let repo = self.repo()?;
let tag = repo
.find_tag(*id)
.map_err(|e| crate::Error::Find(e.to_string()))?;
let tag_ref = tag
.decode()
.map_err(|e| crate::Error::Find(e.to_string()))?;
let v = tag_ref.pgp_signature.map(|s| SignatureInfo {
signature: s.to_string(),
payload: tag_ref.message.to_string(),
key_id: None,
signer: None,
});
self.cache_put_oid("sig_tag_meta", id, &v);
Ok(v)
}
}