csaf_walker/verification/
mod.rs

1//! Verification
2//!
3//! Checks to ensure conformity with the specification.
4
5use crate::{
6    discover::{AsDiscovered, DiscoveredAdvisory},
7    retrieve::{AsRetrieved, RetrievalContext, RetrievedAdvisory, RetrievedVisitor},
8    source::Source,
9    validation::{ValidatedAdvisory, ValidatedVisitor, ValidationContext, ValidationError},
10    verification::check::{Check, CheckError},
11};
12use csaf::Csaf;
13use serde::de::Error as _;
14use std::{
15    collections::{HashMap, HashSet},
16    fmt::{Debug, Display},
17    future::Future,
18    hash::Hash,
19    marker::PhantomData,
20    ops::{Deref, DerefMut},
21};
22use url::Url;
23use walker_common::{retrieve::RetrievalError, utils::url::Urlify};
24
25pub mod check;
26
27#[derive(Debug)]
28pub struct VerifiedAdvisory<A, I>
29where
30    A: AsRetrieved,
31    I: Clone + PartialEq + Eq + Hash,
32{
33    pub advisory: A,
34    pub csaf: Csaf,
35    pub failures: HashMap<I, Vec<CheckError>>,
36    pub successes: HashSet<I>,
37}
38
39impl<A, I> Deref for VerifiedAdvisory<A, I>
40where
41    A: AsRetrieved,
42    I: Clone + PartialEq + Eq + Hash,
43{
44    type Target = A;
45
46    fn deref(&self) -> &Self::Target {
47        &self.advisory
48    }
49}
50
51impl<A, I> DerefMut for VerifiedAdvisory<A, I>
52where
53    A: AsRetrieved,
54    I: Clone + PartialEq + Eq + Hash,
55{
56    fn deref_mut(&mut self) -> &mut Self::Target {
57        &mut self.advisory
58    }
59}
60
61#[derive(Debug, thiserror::Error)]
62pub enum VerificationError<UE, A>
63where
64    A: Debug,
65    UE: Display + Debug,
66{
67    #[error(transparent)]
68    Upstream(UE),
69    #[error("document parsing error: {error}")]
70    Parsing {
71        advisory: A,
72        error: serde_json::Error,
73    },
74    #[error("check runtime error: {error}")]
75    Check { advisory: A, error: anyhow::Error },
76}
77
78impl<A, UE> AsDiscovered for VerificationError<UE, A>
79where
80    A: AsDiscovered + Debug,
81    UE: AsDiscovered + Display + Debug,
82{
83    fn as_discovered(&self) -> &DiscoveredAdvisory {
84        match self {
85            Self::Upstream(err) => err.as_discovered(),
86            Self::Parsing { advisory, .. } => advisory.as_discovered(),
87            Self::Check { advisory, .. } => advisory.as_discovered(),
88        }
89    }
90}
91
92impl<UE, A> Urlify for VerificationError<UE, A>
93where
94    A: AsRetrieved + Debug,
95    UE: Urlify + Display + Debug,
96{
97    fn url(&self) -> &Url {
98        match self {
99            Self::Upstream(err) => err.url(),
100            Self::Parsing { advisory, .. } => advisory.as_retrieved().url(),
101            Self::Check { advisory, .. } => advisory.as_retrieved().url(),
102        }
103    }
104}
105
106pub struct VerificationContext {}
107
108/// A visitor accepting a verified advisory
109pub trait VerifiedVisitor<A, E, I>
110where
111    A: AsRetrieved,
112    E: Display + Debug,
113    I: Clone + PartialEq + Eq + Hash,
114{
115    type Error: Display + Debug;
116    type Context;
117
118    fn visit_context(
119        &self,
120        context: &VerificationContext,
121    ) -> impl Future<Output = Result<Self::Context, Self::Error>>;
122
123    fn visit_advisory(
124        &self,
125        context: &Self::Context,
126        result: Result<VerifiedAdvisory<A, I>, VerificationError<E, A>>,
127    ) -> impl Future<Output = Result<(), Self::Error>>;
128}
129
130#[derive(Debug, thiserror::Error)]
131pub enum Error<VE>
132where
133    VE: Display + Debug,
134{
135    #[error(transparent)]
136    Visitor(VE),
137}
138
139/// A visitor implementing the verification of a CSAF document
140pub struct VerifyingVisitor<A, E, V, I>
141where
142    A: AsRetrieved,
143    V: VerifiedVisitor<A, E, I>,
144    E: Display + Debug,
145    I: Clone + PartialEq + Eq + Hash,
146{
147    visitor: V,
148    checks: Vec<(I, Box<dyn Check>)>,
149    _marker: PhantomData<(A, E)>,
150}
151
152impl<A, E, V, I> VerifyingVisitor<A, E, V, I>
153where
154    A: AsRetrieved,
155    V: VerifiedVisitor<A, E, I>,
156    E: Display + Debug,
157    I: Clone + PartialEq + Eq + Hash,
158{
159    pub fn new(visitor: V) -> Self {
160        Self {
161            visitor,
162            checks: vec![],
163            _marker: Default::default(),
164        }
165    }
166
167    pub fn with_checks(visitor: V, checks: Vec<(I, Box<dyn Check>)>) -> Self {
168        Self {
169            visitor,
170            checks,
171            _marker: Default::default(),
172        }
173    }
174
175    pub fn add<F: Check + 'static>(mut self, index: I, check: F) -> Self {
176        self.checks.push((index, Box::new(check)));
177        self
178    }
179
180    async fn verify(&self, advisory: A) -> Result<VerifiedAdvisory<A, I>, VerificationError<E, A>> {
181        let data = advisory.as_retrieved().data.clone();
182
183        let csaf = match tokio::task::spawn_blocking(move || serde_json::from_slice::<Csaf>(&data))
184            .await
185        {
186            Ok(Ok(csaf)) => csaf,
187            Ok(Err(error)) => return Err(VerificationError::Parsing { error, advisory }),
188            Err(_) => {
189                return Err(VerificationError::Parsing {
190                    error: serde_json::error::Error::custom("failed to wait for deserialization"),
191                    advisory,
192                });
193            }
194        };
195
196        let mut failures = HashMap::new();
197        let mut successes = HashSet::new();
198
199        for (index, check) in &self.checks {
200            let result = match check.as_ref().check(&csaf).await {
201                Ok(result) => result,
202                Err(error) => return Err(VerificationError::Check { error, advisory }),
203            };
204            if !result.is_empty() {
205                failures.insert(index.clone(), result);
206            } else {
207                successes.insert(index.clone());
208            }
209        }
210
211        Ok(VerifiedAdvisory {
212            advisory,
213            csaf,
214            failures,
215            successes,
216        })
217    }
218}
219
220impl<V, I, S> RetrievedVisitor<S>
221    for VerifyingVisitor<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>, V, I>
222where
223    V: VerifiedVisitor<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>, I>,
224    I: Clone + PartialEq + Eq + Hash,
225    S: Source,
226{
227    type Error = Error<V::Error>;
228    type Context = V::Context;
229
230    async fn visit_context(
231        &self,
232        _context: &RetrievalContext<'_>,
233    ) -> Result<Self::Context, Self::Error> {
234        self.visitor
235            .visit_context(&VerificationContext {})
236            .await
237            .map_err(Error::Visitor)
238    }
239
240    async fn visit_advisory(
241        &self,
242        context: &Self::Context,
243        result: Result<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>>,
244    ) -> Result<(), Self::Error> {
245        let result = match result {
246            Ok(doc) => self.verify(doc).await,
247            Err(err) => Err(VerificationError::Upstream(err)),
248        };
249
250        self.visitor
251            .visit_advisory(context, result)
252            .await
253            .map_err(Error::Visitor)?;
254
255        Ok(())
256    }
257}
258
259impl<V, I, S> ValidatedVisitor<S> for VerifyingVisitor<ValidatedAdvisory, ValidationError<S>, V, I>
260where
261    V: VerifiedVisitor<ValidatedAdvisory, ValidationError<S>, I>,
262    I: Clone + PartialEq + Eq + Hash,
263    S: Source,
264{
265    type Error = Error<V::Error>;
266    type Context = V::Context;
267
268    async fn visit_context(
269        &self,
270        _context: &ValidationContext<'_>,
271    ) -> Result<Self::Context, Self::Error> {
272        self.visitor
273            .visit_context(&VerificationContext {})
274            .await
275            .map_err(Error::Visitor)
276    }
277
278    async fn visit_advisory(
279        &self,
280        context: &Self::Context,
281        result: Result<ValidatedAdvisory, ValidationError<S>>,
282    ) -> Result<(), Self::Error> {
283        let result = match result {
284            Ok(doc) => self.verify(doc).await,
285            Err(err) => Err(VerificationError::Upstream(err)),
286        };
287
288        self.visitor
289            .visit_advisory(context, result)
290            .await
291            .map_err(Error::Visitor)?;
292
293        Ok(())
294    }
295}
296
297impl<F, E, Fut, A, I, UE> VerifiedVisitor<A, UE, I> for F
298where
299    UE: Debug + Display + 'static,
300    F: Fn(Result<VerifiedAdvisory<A, I>, VerificationError<UE, A>>) -> Fut,
301    Fut: Future<Output = Result<(), E>>,
302    E: Display + Debug + 'static,
303    A: AsRetrieved + 'static,
304    I: Clone + PartialEq + Eq + Hash + 'static,
305{
306    type Error = E;
307    type Context = ();
308
309    async fn visit_context(
310        &self,
311        _context: &VerificationContext,
312    ) -> Result<Self::Context, Self::Error> {
313        Ok(())
314    }
315
316    async fn visit_advisory(
317        &self,
318        _ctx: &Self::Context,
319        outcome: Result<VerifiedAdvisory<A, I>, VerificationError<UE, A>>,
320    ) -> Result<(), Self::Error> {
321        self(outcome).await
322    }
323}