use crate::evidence::{content_hash, sort_findings, sorted_findings};
use serde::{Deserialize, Serialize};
pub type ArtifactHash = [u8; 32];
pub const ARTIFACT_ENVELOPE_FRAMING_VERSION: u32 = 1;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SignatureRef {
pub algorithm_id: u32,
pub key_id: ArtifactHash,
pub signature_bytes: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct SignatureEnvelope {
pub signature: SignatureRef,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct AttestationRef {
pub kind_id: u32,
pub bytes: Vec<u8>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CanonicalArtifactEnvelope<T> {
pub body: T,
pub envelope_schema_version: u32,
pub generated_at_wall_ms: Option<u64>,
pub diagnostic_note: Option<String>,
pub signatures: Vec<SignatureEnvelope>,
pub attestations: Vec<AttestationRef>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ArtifactEnvelopeIdentity {
pub framing_schema_version: u32,
pub body_hash: ArtifactHash,
pub envelope_schema_version: u32,
pub generated_at_wall_ms: Option<u64>,
pub diagnostic_note: Option<String>,
pub signatures_sorted: Vec<SignatureEnvelope>,
pub attestations_sorted: Vec<AttestationRef>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ArtifactVerificationReport {
pub body_hash: ArtifactHash,
pub envelope_hash: ArtifactHash,
pub findings: Vec<ArtifactEnvelopeFinding>,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum ArtifactEnvelopeFinding {
InvalidSignature {
key_id: ArtifactHash,
reason: String,
},
}
pub fn artifact_body_bytes<T: Serialize>(body: &T) -> Result<Vec<u8>, rmp_serde::encode::Error> {
crate::encoding::to_bytes(body)
}
pub fn artifact_body_hash_from_body<T: Serialize>(
body: &T,
) -> Result<ArtifactHash, rmp_serde::encode::Error> {
let bytes = artifact_body_bytes(body)?;
Ok(content_hash(&bytes))
}
pub fn artifact_envelope_identity<T: Serialize>(
envelope: &CanonicalArtifactEnvelope<T>,
body_hash: ArtifactHash,
) -> ArtifactEnvelopeIdentity {
let mut signatures_sorted = envelope.signatures.clone();
signatures_sorted.sort();
let mut attestations_sorted = envelope.attestations.clone();
attestations_sorted.sort();
ArtifactEnvelopeIdentity {
framing_schema_version: ARTIFACT_ENVELOPE_FRAMING_VERSION,
body_hash,
envelope_schema_version: envelope.envelope_schema_version,
generated_at_wall_ms: envelope.generated_at_wall_ms,
diagnostic_note: envelope.diagnostic_note.clone(),
signatures_sorted,
attestations_sorted,
}
}
pub fn artifact_envelope_hash_from_identity(
identity: &ArtifactEnvelopeIdentity,
) -> Result<ArtifactHash, rmp_serde::encode::Error> {
let bytes = crate::encoding::to_bytes(identity)?;
Ok(content_hash(&bytes))
}
pub fn artifact_envelope_hash_for<T: Serialize>(
envelope: &CanonicalArtifactEnvelope<T>,
) -> Result<ArtifactHash, rmp_serde::encode::Error> {
let bh = artifact_body_hash_from_body(&envelope.body)?;
let id = artifact_envelope_identity(envelope, bh);
artifact_envelope_hash_from_identity(&id)
}
pub fn verify_canonical_artifact_envelope<T: Serialize, F>(
envelope: &CanonicalArtifactEnvelope<T>,
mut verify_signature: F,
) -> Result<ArtifactVerificationReport, rmp_serde::encode::Error>
where
F: FnMut(&SignatureRef, &[u8]) -> Result<(), String>,
{
let body_raw = artifact_body_bytes(&envelope.body)?;
let body_digest = content_hash(&body_raw);
let env_id = artifact_envelope_identity(envelope, body_digest);
let env_digest = artifact_envelope_hash_from_identity(&env_id)?;
let mut findings = Vec::new();
for sig in &env_id.signatures_sorted {
if let Err(reason) = verify_signature(&sig.signature, &body_raw) {
findings.push(ArtifactEnvelopeFinding::InvalidSignature {
key_id: sig.signature.key_id,
reason,
});
}
}
sort_findings(&mut findings);
Ok(ArtifactVerificationReport {
body_hash: body_digest,
envelope_hash: env_digest,
findings,
})
}
pub fn artifact_verification_report_body_hash(
report: &ArtifactVerificationReport,
) -> Result<ArtifactHash, rmp_serde::encode::Error> {
let findings = sorted_findings(&report.findings);
let normalized = ArtifactVerificationReport {
findings,
..report.clone()
};
let bytes = crate::encoding::to_bytes(&normalized)?;
Ok(content_hash(&bytes))
}
impl<T: Serialize> CanonicalArtifactEnvelope<T> {
pub fn body_hash(&self) -> Result<ArtifactHash, rmp_serde::encode::Error> {
artifact_body_hash_from_body(&self.body)
}
pub fn envelope_hash(&self) -> Result<ArtifactHash, rmp_serde::encode::Error> {
artifact_envelope_hash_for(self)
}
}