Skip to main content

greentic_component_store/
verify.rs

1use sha2::{Digest as _, Sha256};
2use thiserror::Error;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum DigestAlgorithm {
6    Sha256,
7}
8
9#[derive(Debug, Clone)]
10pub struct DigestPolicy {
11    algorithm: DigestAlgorithm,
12    expected: Option<String>,
13    required: bool,
14}
15
16impl DigestPolicy {
17    pub fn sha256(expected: Option<String>, required: bool) -> Self {
18        Self {
19            algorithm: DigestAlgorithm::Sha256,
20            expected,
21            required,
22        }
23    }
24
25    pub fn expected(&self) -> Option<&str> {
26        self.expected.as_deref()
27    }
28
29    pub fn verify(&self, bytes: &[u8]) -> Result<VerifiedDigest, VerificationError> {
30        let computed = match self.algorithm {
31            DigestAlgorithm::Sha256 => {
32                let digest = Sha256::digest(bytes);
33                VerifiedDigest {
34                    algorithm: DigestAlgorithm::Sha256,
35                    value: hex::encode(digest),
36                }
37            }
38        };
39
40        if let Some(expected) = &self.expected {
41            if !equal_digest(expected, &computed.value) {
42                return Err(VerificationError::DigestMismatch {
43                    expected: expected.clone(),
44                    actual: computed.value,
45                });
46            }
47        } else if self.required {
48            return Err(VerificationError::DigestMissing);
49        }
50
51        Ok(computed)
52    }
53}
54
55#[derive(Debug, Clone)]
56pub enum SignaturePolicy {
57    Disabled,
58    Cosign { required: bool },
59}
60
61impl SignaturePolicy {
62    pub fn cosign_required() -> Self {
63        SignaturePolicy::Cosign { required: true }
64    }
65
66    pub fn cosign_optional() -> Self {
67        SignaturePolicy::Cosign { required: false }
68    }
69
70    pub fn verify(&self, _bytes: &[u8]) -> Result<VerifiedSignature, VerificationError> {
71        match self {
72            SignaturePolicy::Disabled => Ok(VerifiedSignature::Skipped),
73            SignaturePolicy::Cosign { required } => {
74                if *required {
75                    Err(VerificationError::SignatureNotImplemented(
76                        "cosign signature verification required".into(),
77                    ))
78                } else {
79                    Ok(VerifiedSignature::Skipped)
80                }
81            }
82        }
83    }
84}
85
86#[derive(Debug, Clone, Default)]
87pub struct VerificationPolicy {
88    pub digest: Option<DigestPolicy>,
89    pub signature: Option<SignaturePolicy>,
90}
91
92impl VerificationPolicy {
93    pub fn verify(&self, bytes: &[u8]) -> Result<VerificationReport, VerificationError> {
94        let digest = match &self.digest {
95            Some(policy) => Some(policy.verify(bytes)?),
96            None => None,
97        };
98        let signature = match &self.signature {
99            Some(policy) => Some(policy.verify(bytes)?),
100            None => None,
101        };
102        Ok(VerificationReport { digest, signature })
103    }
104}
105
106#[derive(Debug, Clone)]
107pub struct VerificationReport {
108    pub digest: Option<VerifiedDigest>,
109    pub signature: Option<VerifiedSignature>,
110}
111
112#[derive(Debug, Clone)]
113pub struct VerifiedDigest {
114    pub algorithm: DigestAlgorithm,
115    pub value: String,
116}
117
118impl VerifiedDigest {
119    pub fn compute(algorithm: DigestAlgorithm, bytes: &[u8]) -> Self {
120        match algorithm {
121            DigestAlgorithm::Sha256 => {
122                let digest = Sha256::digest(bytes);
123                Self {
124                    algorithm,
125                    value: hex::encode(digest),
126                }
127            }
128        }
129    }
130}
131
132#[derive(Debug, Clone)]
133pub enum VerifiedSignature {
134    Skipped,
135}
136
137#[derive(Debug, Error)]
138pub enum VerificationError {
139    #[error("digest check required but no expected value provided")]
140    DigestMissing,
141    #[error("digest mismatch (expected {expected}, actual {actual})")]
142    DigestMismatch { expected: String, actual: String },
143    #[error("signature verification not implemented: {0}")]
144    SignatureNotImplemented(String),
145}
146
147fn equal_digest(expected: &str, actual: &str) -> bool {
148    expected.eq_ignore_ascii_case(actual)
149}