1use crate::{
4 discover::{AsDiscovered, DiscoveredAdvisory},
5 retrieve::{AsRetrieved, RetrievalContext, RetrievedAdvisory, RetrievedVisitor},
6 source::Source,
7};
8use std::{
9 fmt::{Debug, Display, Formatter},
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::{ValidationOptions, digest::validate_digest, openpgp},
19};
20
21#[derive(Clone, Debug)]
28pub struct ValidatedAdvisory {
29 pub retrieved: RetrievedAdvisory,
31}
32
33impl Urlify for ValidatedAdvisory {
34 fn url(&self) -> &Url {
35 &self.url
36 }
37
38 fn relative_base_and_url(&self) -> Option<(&Url, String)> {
39 self.retrieved.relative_base_and_url()
40 }
41}
42
43impl Deref for ValidatedAdvisory {
44 type Target = RetrievedAdvisory;
45
46 fn deref(&self) -> &Self::Target {
47 &self.retrieved
48 }
49}
50
51impl DerefMut for ValidatedAdvisory {
52 fn deref_mut(&mut self) -> &mut Self::Target {
53 &mut self.retrieved
54 }
55}
56
57impl AsDiscovered for ValidatedAdvisory {
58 fn as_discovered(&self) -> &DiscoveredAdvisory {
59 &self.discovered
60 }
61}
62
63impl AsRetrieved for ValidatedAdvisory {
64 fn as_retrieved(&self) -> &RetrievedAdvisory {
65 &self.retrieved
66 }
67}
68
69#[derive(Debug, thiserror::Error)]
70#[allow(clippy::large_enum_variant)]
71pub enum ValidationError<S: Source> {
72 Retrieval(RetrievalError<DiscoveredAdvisory, S>),
73 DigestMismatch {
74 expected: String,
75 actual: String,
76 retrieved: RetrievedAdvisory,
77 },
78 Signature {
79 error: anyhow::Error,
80 retrieved: RetrievedAdvisory,
81 },
82}
83
84impl<S: Source + Debug> AsDiscovered for ValidationError<S> {
85 fn as_discovered(&self) -> &DiscoveredAdvisory {
86 match self {
87 Self::Retrieval(err) => err.discovered(),
88 Self::DigestMismatch { retrieved, .. } => retrieved.as_discovered(),
89 Self::Signature { retrieved, .. } => retrieved.as_discovered(),
90 }
91 }
92}
93
94impl<S: Source> Urlify for ValidationError<S> {
95 fn url(&self) -> &Url {
96 match self {
97 Self::Retrieval(err) => err.url(),
98 Self::DigestMismatch { retrieved, .. } => &retrieved.url,
99 Self::Signature { retrieved, .. } => &retrieved.url,
100 }
101 }
102}
103
104impl<S: Source> Display for ValidationError<S> {
105 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
106 match self {
107 Self::Retrieval(err) => write!(f, "Retrieval error: {err}"),
108 Self::DigestMismatch {
109 expected,
110 actual,
111 retrieved: _,
112 } => write!(
113 f,
114 "Digest mismatch - expected: {expected}, actual: {actual}",
115 ),
116 Self::Signature {
117 error,
118 retrieved: _,
119 } => {
120 write!(f, "Invalid signature: {error}",)
121 }
122 }
123 }
124}
125
126pub struct ValidationContext<'c> {
127 pub retrieval: &'c RetrievalContext<'c>,
128}
129
130impl<'c> Deref for ValidationContext<'c> {
131 type Target = RetrievalContext<'c>;
132
133 fn deref(&self) -> &Self::Target {
134 self.retrieval
135 }
136}
137
138pub trait ValidatedVisitor<S: Source> {
139 type Error: Display + Debug;
140 type Context;
141
142 fn visit_context(
143 &self,
144 context: &ValidationContext,
145 ) -> impl Future<Output = Result<Self::Context, Self::Error>>;
146
147 fn visit_advisory(
148 &self,
149 context: &Self::Context,
150 result: Result<ValidatedAdvisory, ValidationError<S>>,
151 ) -> impl Future<Output = Result<(), Self::Error>>;
152}
153
154impl<F, E, Fut, S> ValidatedVisitor<S> for F
155where
156 F: Fn(Result<ValidatedAdvisory, ValidationError<S>>) -> Fut,
157 Fut: Future<Output = Result<(), E>>,
158 E: Display + Debug,
159 S: Source,
160{
161 type Error = E;
162 type Context = ();
163
164 async fn visit_context(
165 &self,
166 _context: &ValidationContext<'_>,
167 ) -> Result<Self::Context, Self::Error> {
168 Ok(())
169 }
170
171 async fn visit_advisory(
172 &self,
173 _context: &Self::Context,
174 result: Result<ValidatedAdvisory, ValidationError<S>>,
175 ) -> Result<(), Self::Error> {
176 self(result).await
177 }
178}
179
180pub struct ValidationVisitor<V, S>
181where
182 V: ValidatedVisitor<S>,
183 S: Source,
184{
185 visitor: V,
186 options: ValidationOptions,
187 _marker: PhantomData<S>,
188}
189
190#[allow(clippy::large_enum_variant)]
191enum ValidationProcessError<S: Source> {
192 Proceed(ValidationError<S>),
194 #[allow(unused)]
196 Abort(anyhow::Error),
197}
198
199#[derive(Debug, thiserror::Error)]
200pub enum Error<VE>
201where
202 VE: Display + Debug,
203{
204 #[error("{0}")]
205 Visitor(VE),
206 #[error("Severe validation error: {0}")]
207 Validation(anyhow::Error),
208}
209
210impl<V, S> ValidationVisitor<V, S>
211where
212 V: ValidatedVisitor<S>,
213 S: Source,
214{
215 pub fn new(visitor: V) -> Self {
216 Self {
217 visitor,
218 options: Default::default(),
219 _marker: Default::default(),
220 }
221 }
222
223 pub fn with_options(mut self, options: impl Into<ValidationOptions>) -> Self {
224 self.options = options.into();
225 self
226 }
227
228 async fn validate(
232 &self,
233 context: &InnerValidationContext<V::Context>,
234 retrieved: RetrievedAdvisory,
235 ) -> Result<ValidatedAdvisory, ValidationProcessError<S>> {
236 if let Err((expected, actual)) = validate_digest(&retrieved.sha256) {
237 return Err(ValidationProcessError::Proceed(
238 ValidationError::DigestMismatch {
239 expected,
240 actual,
241 retrieved,
242 },
243 ));
244 }
245 if let Err((expected, actual)) = validate_digest(&retrieved.sha512) {
246 return Err(ValidationProcessError::Proceed(
247 ValidationError::DigestMismatch {
248 expected,
249 actual,
250 retrieved,
251 },
252 ));
253 }
254
255 if let Some(signature) = &retrieved.signature {
256 match openpgp::validate_signature(
257 &self.options,
258 &context.keys,
259 signature,
260 &retrieved.data,
261 ) {
262 Ok(()) => Ok(ValidatedAdvisory { retrieved }),
263 Err(error) => Err(ValidationProcessError::Proceed(
264 ValidationError::Signature { error, retrieved },
265 )),
266 }
267 } else {
268 Ok(ValidatedAdvisory { retrieved })
269 }
270 }
271}
272
273pub struct InnerValidationContext<VC> {
274 context: VC,
275 keys: Vec<PublicKey>,
276}
277
278impl<V, S> RetrievedVisitor<S> for ValidationVisitor<V, S>
279where
280 V: ValidatedVisitor<S>,
281 S: Source,
282{
283 type Error = Error<V::Error>;
284 type Context = InnerValidationContext<V::Context>;
285
286 async fn visit_context(
287 &self,
288 context: &RetrievalContext<'_>,
289 ) -> Result<Self::Context, Self::Error> {
290 let keys = context.keys.clone();
291
292 let context = self
293 .visitor
294 .visit_context(&ValidationContext { retrieval: context })
295 .await
296 .map_err(Error::Visitor)?;
297
298 Ok(Self::Context { context, keys })
299 }
300
301 async fn visit_advisory(
302 &self,
303 context: &Self::Context,
304 outcome: Result<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>>,
305 ) -> Result<(), Self::Error> {
306 match outcome {
307 Ok(advisory) => {
308 let result = match self.validate(context, advisory).await {
309 Ok(result) => Ok(result),
310 Err(ValidationProcessError::Proceed(err)) => Err(err),
311 Err(ValidationProcessError::Abort(err)) => return Err(Error::Validation(err)),
312 };
313 self.visitor
314 .visit_advisory(&context.context, result)
315 .await
316 .map_err(Error::Visitor)?
317 }
318 Err(err) => self
319 .visitor
320 .visit_advisory(&context.context, Err(ValidationError::Retrieval(err)))
321 .await
322 .map_err(Error::Visitor)?,
323 }
324
325 Ok(())
326 }
327}