use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ManifestSignature {
pub algorithm: String,
pub manifest_digest: String,
pub signature: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bundle: Option<String>,
}
const DEFAULT_KEY: &[u8] = b"processfork-default-self-sign-key.v1";
pub fn sign_manifest(manifest_bytes: &[u8], key: Option<&str>) -> ManifestSignature {
let manifest_digest = sha256_hex(manifest_bytes);
let key_bytes = key.map_or(DEFAULT_KEY, str::as_bytes);
let signature = hmac_sha256_hex(key_bytes, manifest_bytes);
ManifestSignature {
algorithm: "hmac-sha256".into(),
manifest_digest,
signature,
bundle: None,
}
}
pub fn verify_manifest(
manifest_bytes: &[u8],
sig: &ManifestSignature,
key: Option<&str>,
) -> Result<(), String> {
if sig.algorithm != "hmac-sha256" {
return Err(format!(
"unsupported signature algorithm {:?} (v1 supports only hmac-sha256; \
cosign verifier lands in v1.1)",
sig.algorithm
));
}
let observed = sha256_hex(manifest_bytes);
if observed != sig.manifest_digest {
return Err(format!(
"manifest digest mismatch: bytes hash to {observed}, sig records {}",
sig.manifest_digest
));
}
let key_bytes = key.map_or(DEFAULT_KEY, str::as_bytes);
let expected = hmac_sha256_hex(key_bytes, manifest_bytes);
if expected != sig.signature {
return Err("HMAC tag does not validate".into());
}
Ok(())
}
fn sha256_hex(b: &[u8]) -> String {
let mut h = Sha256::new();
h.update(b);
hex::encode(h.finalize())
}
fn hmac_sha256_hex(key: &[u8], msg: &[u8]) -> String {
use ring::hmac;
let k = hmac::Key::new(hmac::HMAC_SHA256, key);
hex::encode(hmac::sign(&k, msg).as_ref())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sign_then_verify_round_trip() {
let bytes = b"{\"hello\":\"world\"}";
let sig = sign_manifest(bytes, None);
verify_manifest(bytes, &sig, None).unwrap();
}
#[test]
fn verify_fails_on_tampered_manifest() {
let sig = sign_manifest(b"original", None);
assert!(verify_manifest(b"tampered", &sig, None).is_err());
}
#[test]
fn verify_fails_on_wrong_key() {
let sig = sign_manifest(b"x", Some("key-a"));
assert!(verify_manifest(b"x", &sig, Some("key-b")).is_err());
}
#[test]
fn verify_rejects_unknown_algorithm() {
let mut sig = sign_manifest(b"x", None);
sig.algorithm = "future-cosine".into();
assert!(verify_manifest(b"x", &sig, None).is_err());
}
}