greentic_component_store/
verify.rs1use 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}