use crate::artifact::{
artifact_envelope_hash_from_identity, artifact_envelope_identity,
verify_canonical_artifact_envelope, ArtifactVerificationReport, AttestationRef,
CanonicalArtifactEnvelope, SignatureEnvelope, SignatureRef,
};
use crate::evidence::{content_hash, sort_findings, sorted_findings};
use serde::{Deserialize, Serialize};
pub const BACKUP_MANIFEST_BODY_SCHEMA_VERSION: u32 = 1;
pub const RESTORE_PROOF_REPORT_SCHEMA_VERSION: u32 = 1;
pub type SegmentBytesDigest = [u8; 32];
pub type RestoreProofHash = SegmentBytesDigest;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct BackupSegmentRef {
pub segment_id: u64,
pub bytes_digest: SegmentBytesDigest,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BackupManifestBody {
pub schema_version: u32,
pub backup_id: SegmentBytesDigest,
pub layout_revision: u32,
pub tooling_revision: u32,
pub segments: Vec<BackupSegmentRef>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BackupManifestEnvelope {
pub body: BackupManifestBody,
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>,
}
impl BackupManifestEnvelope {
#[must_use]
fn to_canonical_envelope(&self) -> CanonicalArtifactEnvelope<BackupManifestBody> {
CanonicalArtifactEnvelope {
body: self.body.clone(),
envelope_schema_version: self.envelope_schema_version,
generated_at_wall_ms: self.generated_at_wall_ms,
diagnostic_note: self.diagnostic_note.clone(),
signatures: self.signatures.clone(),
attestations: self.attestations.clone(),
}
}
pub fn body_hash(&self) -> Result<SegmentBytesDigest, rmp_serde::encode::Error> {
backup_manifest_envelope_body_hash(self)
}
pub fn envelope_hash(&self) -> Result<SegmentBytesDigest, rmp_serde::encode::Error> {
backup_manifest_envelope_hash(self)
}
}
impl From<CanonicalArtifactEnvelope<BackupManifestBody>> for BackupManifestEnvelope {
fn from(envelope: CanonicalArtifactEnvelope<BackupManifestBody>) -> Self {
Self {
body: envelope.body,
envelope_schema_version: envelope.envelope_schema_version,
generated_at_wall_ms: envelope.generated_at_wall_ms,
diagnostic_note: envelope.diagnostic_note,
signatures: envelope.signatures,
attestations: envelope.attestations,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum BackupEnvelopeFinding {
UnsupportedManifestBodySchemaVersion {
observed: u32,
expected: u32,
},
DuplicateSegmentRef {
segment_id: u64,
bytes_digest: SegmentBytesDigest,
},
InconsistentSegmentId {
segment_id: u64,
first_digest: SegmentBytesDigest,
second_digest: SegmentBytesDigest,
},
ManifestBodyHashMismatch {
claimed: SegmentBytesDigest,
computed: SegmentBytesDigest,
},
MissingExpectedSegment {
segment_id: u64,
},
UnexpectedObservedSegment {
segment_id: u64,
},
SegmentBytesDigestMismatch {
segment_id: u64,
expected: SegmentBytesDigest,
observed: SegmentBytesDigest,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct BackupManifestVerification {
pub envelope_plane: ArtifactVerificationReport,
pub findings: Vec<BackupEnvelopeFinding>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RestoreProofReportBody {
pub schema_version: u32,
pub manifest_body_hash: SegmentBytesDigest,
pub observed_segments_sorted: Vec<BackupSegmentRef>,
pub findings: Vec<BackupEnvelopeFinding>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct RestoreProofEvidenceReport {
pub body: RestoreProofReportBody,
pub body_hash: RestoreProofHash,
}
impl RestoreProofEvidenceReport {
pub fn from_body(body: RestoreProofReportBody) -> Result<Self, rmp_serde::encode::Error> {
let body_hash = restore_proof_report_body_hash(&body)?;
Ok(Self { body, body_hash })
}
}
pub fn sort_backup_segment_refs(segments: &mut [BackupSegmentRef]) {
segments.sort();
}
#[must_use]
pub fn normalize_backup_manifest_body(body: &BackupManifestBody) -> BackupManifestBody {
let mut segments = body.segments.clone();
segments.sort();
BackupManifestBody {
segments,
..body.clone()
}
}
#[must_use]
pub fn normalize_backup_manifest_envelope(
envelope: &BackupManifestEnvelope,
) -> BackupManifestEnvelope {
BackupManifestEnvelope {
body: normalize_backup_manifest_body(&envelope.body),
envelope_schema_version: envelope.envelope_schema_version,
generated_at_wall_ms: envelope.generated_at_wall_ms,
diagnostic_note: envelope.diagnostic_note.clone(),
signatures: envelope.signatures.clone(),
attestations: envelope.attestations.clone(),
}
}
pub fn backup_manifest_body_bytes(
body: &BackupManifestBody,
) -> Result<Vec<u8>, rmp_serde::encode::Error> {
let normalized = normalize_backup_manifest_body(body);
crate::encoding::to_bytes(&normalized)
}
pub fn backup_manifest_body_hash(
body: &BackupManifestBody,
) -> Result<SegmentBytesDigest, rmp_serde::encode::Error> {
let bytes = backup_manifest_body_bytes(body)?;
Ok(content_hash(&bytes))
}
pub fn backup_manifest_envelope_body_hash(
envelope: &BackupManifestEnvelope,
) -> Result<SegmentBytesDigest, rmp_serde::encode::Error> {
backup_manifest_body_hash(&envelope.body)
}
pub fn backup_manifest_envelope_hash(
envelope: &BackupManifestEnvelope,
) -> Result<SegmentBytesDigest, rmp_serde::encode::Error> {
let normalized = normalize_backup_manifest_envelope(envelope);
let body_hash = backup_manifest_body_hash(&normalized.body)?;
let artifact_envelope = normalized.to_canonical_envelope();
let identity = artifact_envelope_identity(&artifact_envelope, body_hash);
artifact_envelope_hash_from_identity(&identity)
}
fn collect_segment_digest_index(
sorted_segments: &[BackupSegmentRef],
findings: &mut Vec<BackupEnvelopeFinding>,
) -> std::collections::BTreeMap<u64, SegmentBytesDigest> {
let mut map = std::collections::BTreeMap::new();
for seg in sorted_segments {
if let Some(prev) = map.get(&seg.segment_id).copied() {
if prev == seg.bytes_digest {
findings.push(BackupEnvelopeFinding::DuplicateSegmentRef {
segment_id: seg.segment_id,
bytes_digest: seg.bytes_digest,
});
} else {
findings.push(BackupEnvelopeFinding::InconsistentSegmentId {
segment_id: seg.segment_id,
first_digest: prev,
second_digest: seg.bytes_digest,
});
}
continue;
}
map.insert(seg.segment_id, seg.bytes_digest);
}
map
}
#[must_use]
pub fn audit_backup_manifest_segments(body: &BackupManifestBody) -> Vec<BackupEnvelopeFinding> {
let normalized = normalize_backup_manifest_body(body);
let mut findings = Vec::new();
if body.schema_version != BACKUP_MANIFEST_BODY_SCHEMA_VERSION {
findings.push(
BackupEnvelopeFinding::UnsupportedManifestBodySchemaVersion {
observed: body.schema_version,
expected: BACKUP_MANIFEST_BODY_SCHEMA_VERSION,
},
);
}
let _map = collect_segment_digest_index(&normalized.segments, &mut findings);
sort_findings(&mut findings);
findings
}
pub fn verify_backup_manifest_envelope<F>(
envelope: &BackupManifestEnvelope,
claimed_manifest_hash: SegmentBytesDigest,
verify_signature: F,
) -> Result<BackupManifestVerification, rmp_serde::encode::Error>
where
F: FnMut(&SignatureRef, &[u8]) -> Result<(), String>,
{
let envelope_norm = normalize_backup_manifest_envelope(envelope);
let artifact_envelope = envelope_norm.to_canonical_envelope();
let envelope_plane = verify_canonical_artifact_envelope(&artifact_envelope, verify_signature)?;
let mut findings = audit_backup_manifest_segments(&envelope.body);
let computed = backup_manifest_body_hash(&envelope.body)?;
if computed != claimed_manifest_hash {
findings.push(BackupEnvelopeFinding::ManifestBodyHashMismatch {
claimed: claimed_manifest_hash,
computed,
});
}
sort_findings(&mut findings);
Ok(BackupManifestVerification {
envelope_plane,
findings,
})
}
pub fn verify_backup_manifest_signatures_only<F>(
envelope: &BackupManifestEnvelope,
verify_signature: F,
) -> Result<ArtifactVerificationReport, rmp_serde::encode::Error>
where
F: FnMut(&SignatureRef, &[u8]) -> Result<(), String>,
{
let envelope_norm = normalize_backup_manifest_envelope(envelope);
let artifact_envelope = envelope_norm.to_canonical_envelope();
verify_canonical_artifact_envelope(&artifact_envelope, verify_signature)
}
pub fn restore_proof_report_body(
expected_manifest: &BackupManifestBody,
observed_segments: &[BackupSegmentRef],
) -> Result<RestoreProofReportBody, rmp_serde::encode::Error> {
let manifest_body_hash = backup_manifest_body_hash(expected_manifest)?;
let mut observed_segments_sorted: Vec<BackupSegmentRef> = observed_segments.to_vec();
observed_segments_sorted.sort();
let mut findings = Vec::new();
if expected_manifest.schema_version != BACKUP_MANIFEST_BODY_SCHEMA_VERSION {
findings.push(
BackupEnvelopeFinding::UnsupportedManifestBodySchemaVersion {
observed: expected_manifest.schema_version,
expected: BACKUP_MANIFEST_BODY_SCHEMA_VERSION,
},
);
}
let normalized = normalize_backup_manifest_body(expected_manifest);
let expected_map = collect_segment_digest_index(&normalized.segments, &mut findings);
let observed_map = collect_segment_digest_index(&observed_segments_sorted, &mut findings);
for (&id, &exp_digest) in &expected_map {
match observed_map.get(&id) {
None => findings.push(BackupEnvelopeFinding::MissingExpectedSegment { segment_id: id }),
Some(&obs_digest) if obs_digest != exp_digest => {
findings.push(BackupEnvelopeFinding::SegmentBytesDigestMismatch {
segment_id: id,
expected: exp_digest,
observed: obs_digest,
});
}
Some(_) => {}
}
}
for id in observed_map.keys() {
if !expected_map.contains_key(id) {
findings.push(BackupEnvelopeFinding::UnexpectedObservedSegment { segment_id: *id });
}
}
sort_findings(&mut findings);
Ok(RestoreProofReportBody {
schema_version: RESTORE_PROOF_REPORT_SCHEMA_VERSION,
manifest_body_hash,
observed_segments_sorted,
findings,
})
}
pub fn restore_proof_report_body_hash(
report: &RestoreProofReportBody,
) -> Result<SegmentBytesDigest, rmp_serde::encode::Error> {
let findings = sorted_findings(&report.findings);
let mut observed_segments_sorted = report.observed_segments_sorted.clone();
observed_segments_sorted.sort();
let normalized = RestoreProofReportBody {
findings,
observed_segments_sorted,
..report.clone()
};
let bytes = crate::encoding::to_bytes(&normalized)?;
Ok(content_hash(&bytes))
}
pub fn restore_proof_evidence_report(
expected_manifest: &BackupManifestBody,
observed_segments: &[BackupSegmentRef],
) -> Result<RestoreProofEvidenceReport, rmp_serde::encode::Error> {
let body = restore_proof_report_body(expected_manifest, observed_segments)?;
RestoreProofEvidenceReport::from_body(body)
}