1use crate::{
4 discover::DiscoveredSbom,
5 retrieve::{RetrievalContext, RetrievedSbom, RetrievedVisitor},
6 source::Source,
7};
8use std::{
9 fmt::{Debug, Display},
10 future::Future,
11 marker::PhantomData,
12 ops::{Deref, DerefMut},
13};
14use url::Url;
15use walker_common::{
16 retrieve::RetrievalError,
17 utils::{openpgp::PublicKey, url::Urlify},
18 validate::{ValidationError, ValidationOptions, digest::validate_digest, openpgp},
19};
20
21#[derive(Clone, Debug)]
22pub struct ValidatedSbom {
23 pub retrieved: RetrievedSbom,
25}
26
27impl Urlify for ValidatedSbom {
28 fn url(&self) -> &Url {
29 &self.url
30 }
31
32 fn relative_base_and_url(&self) -> Option<(&Url, String)> {
33 self.retrieved.relative_base_and_url()
34 }
35}
36
37impl Deref for ValidatedSbom {
38 type Target = RetrievedSbom;
39
40 fn deref(&self) -> &Self::Target {
41 &self.retrieved
42 }
43}
44
45impl DerefMut for ValidatedSbom {
46 fn deref_mut(&mut self) -> &mut Self::Target {
47 &mut self.retrieved
48 }
49}
50
51pub struct ValidationContext<'c> {
52 pub retrieval: &'c RetrievalContext<'c>,
53}
54
55impl<'c> Deref for ValidationContext<'c> {
56 type Target = RetrievalContext<'c>;
57
58 fn deref(&self) -> &Self::Target {
59 self.retrieval
60 }
61}
62
63pub trait ValidatedVisitor<S: Source> {
64 type Error: Display + Debug;
65 type Context;
66
67 fn visit_context(
68 &self,
69 context: &ValidationContext,
70 ) -> impl Future<Output = Result<Self::Context, Self::Error>>;
71
72 fn visit_sbom(
73 &self,
74 context: &Self::Context,
75 result: Result<ValidatedSbom, ValidationError<S>>,
76 ) -> impl Future<Output = Result<(), Self::Error>>;
77}
78
79impl<F, E, Fut, S> ValidatedVisitor<S> for F
80where
81 F: Fn(Result<ValidatedSbom, ValidationError<S>>) -> Fut,
82 Fut: Future<Output = Result<(), E>>,
83 E: Display + Debug,
84 S: Source,
85{
86 type Error = E;
87 type Context = ();
88
89 async fn visit_context(
90 &self,
91 _context: &ValidationContext<'_>,
92 ) -> Result<Self::Context, Self::Error> {
93 Ok(())
94 }
95
96 async fn visit_sbom(
97 &self,
98 _context: &Self::Context,
99 result: Result<ValidatedSbom, ValidationError<S>>,
100 ) -> Result<(), Self::Error> {
101 self(result).await
102 }
103}
104
105pub struct ValidationVisitor<V, S>
106where
107 V: ValidatedVisitor<S>,
108 S: Source,
109{
110 visitor: V,
111 options: ValidationOptions,
112 _marker: PhantomData<S>,
113}
114
115enum ValidationProcessError<S: Source> {
116 Proceed(ValidationError<S>),
118 #[allow(unused)]
120 Abort(anyhow::Error),
121}
122
123#[derive(Debug, thiserror::Error)]
124pub enum Error<VE>
125where
126 VE: Display + Debug,
127{
128 #[error("{0}")]
129 Visitor(VE),
130 #[error("Severe validation error: {0}")]
131 Validation(anyhow::Error),
132}
133
134impl<V, S> ValidationVisitor<V, S>
135where
136 V: ValidatedVisitor<S>,
137 S: Source<Retrieved = RetrievedSbom>,
138{
139 pub fn new(visitor: V) -> Self {
140 Self {
141 visitor,
142 options: Default::default(),
143 _marker: Default::default(),
144 }
145 }
146
147 pub fn with_options(mut self, options: impl Into<ValidationOptions>) -> Self {
148 self.options = options.into();
149 self
150 }
151
152 async fn validate(
156 &self,
157 context: &InnerValidationContext<V::Context>,
158 retrieved: RetrievedSbom,
159 ) -> Result<ValidatedSbom, ValidationProcessError<S>> {
160 if let Err((expected, actual)) = validate_digest(&retrieved.sha256) {
161 return Err(ValidationProcessError::Proceed(
162 ValidationError::DigestMismatch {
163 expected,
164 actual,
165 retrieved,
166 },
167 ));
168 }
169 if let Err((expected, actual)) = validate_digest(&retrieved.sha512) {
170 return Err(ValidationProcessError::Proceed(
171 ValidationError::DigestMismatch {
172 expected,
173 actual,
174 retrieved,
175 },
176 ));
177 }
178
179 if let Some(signature) = &retrieved.signature {
180 match openpgp::validate_signature(
181 &self.options,
182 &context.keys,
183 signature,
184 &retrieved.data,
185 ) {
186 Ok(()) => Ok(ValidatedSbom { retrieved }),
187 Err(error) => Err(ValidationProcessError::Proceed(
188 ValidationError::Signature { error, retrieved },
189 )),
190 }
191 } else {
192 Ok(ValidatedSbom { retrieved })
193 }
194 }
195}
196
197pub struct InnerValidationContext<VC> {
198 context: VC,
199 keys: Vec<PublicKey>,
200}
201
202impl<V, S> RetrievedVisitor<S> for ValidationVisitor<V, S>
203where
204 V: ValidatedVisitor<S>,
205 S: Source<Retrieved = RetrievedSbom>,
206{
207 type Error = Error<V::Error>;
208 type Context = InnerValidationContext<V::Context>;
209
210 async fn visit_context(
211 &self,
212 context: &RetrievalContext<'_>,
213 ) -> Result<Self::Context, Self::Error> {
214 let keys = context.keys.clone();
215
216 let context = self
217 .visitor
218 .visit_context(&ValidationContext { retrieval: context })
219 .await
220 .map_err(Error::Visitor)?;
221
222 Ok(Self::Context { context, keys })
223 }
224
225 async fn visit_sbom(
226 &self,
227 context: &Self::Context,
228 outcome: Result<RetrievedSbom, RetrievalError<DiscoveredSbom, S>>,
229 ) -> Result<(), Self::Error> {
230 match outcome {
231 Ok(advisory) => {
232 let result = match self.validate(context, advisory).await {
233 Ok(result) => Ok(result),
234 Err(ValidationProcessError::Proceed(err)) => Err(err),
235 Err(ValidationProcessError::Abort(err)) => return Err(Error::Validation(err)),
236 };
237 self.visitor
238 .visit_sbom(&context.context, result)
239 .await
240 .map_err(Error::Visitor)?
241 }
242 Err(err) => self
243 .visitor
244 .visit_sbom(&context.context, Err(ValidationError::Retrieval(err)))
245 .await
246 .map_err(Error::Visitor)?,
247 }
248
249 Ok(())
250 }
251}