csaf_walker/
retrieve.rs

1//! Retrieval
2
3use crate::{
4    discover::{AsDiscovered, DiscoveredAdvisory, DiscoveredContext, DiscoveredVisitor},
5    source::Source,
6};
7use bytes::Bytes;
8use sha2::{Sha256, Sha512};
9use std::{
10    fmt::Debug,
11    future::Future,
12    ops::{Deref, DerefMut},
13};
14use url::Url;
15use walker_common::retrieve::RetrievedDocument;
16use walker_common::{
17    retrieve::{RetrievalError, RetrievalMetadata, RetrievedDigest},
18    utils::{openpgp::PublicKey, url::Urlify},
19    validate::source::{KeySource, KeySourceError},
20};
21
22/// A retrieved (but unverified) advisory
23#[derive(Clone, Debug)]
24pub struct RetrievedAdvisory {
25    /// The discovered advisory
26    pub discovered: DiscoveredAdvisory,
27
28    /// The advisory data
29    pub data: Bytes,
30    /// Signature data
31    pub signature: Option<String>,
32
33    /// SHA-256 digest
34    pub sha256: Option<RetrievedDigest<Sha256>>,
35    /// SHA-512 digest
36    pub sha512: Option<RetrievedDigest<Sha512>>,
37
38    /// Metadata from the retrieval process
39    pub metadata: RetrievalMetadata,
40}
41
42impl Urlify for RetrievedAdvisory {
43    fn url(&self) -> &Url {
44        &self.url
45    }
46
47    fn relative_base_and_url(&self) -> Option<(&Url, String)> {
48        self.discovered.relative_base_and_url()
49    }
50}
51
52/// Get a document as [`RetrievedAdvisory`]
53pub trait AsRetrieved: Debug {
54    fn as_retrieved(&self) -> &RetrievedAdvisory;
55}
56
57impl AsDiscovered for RetrievedAdvisory {
58    fn as_discovered(&self) -> &DiscoveredAdvisory {
59        &self.discovered
60    }
61}
62
63impl AsRetrieved for RetrievedAdvisory {
64    fn as_retrieved(&self) -> &RetrievedAdvisory {
65        self
66    }
67}
68
69impl Deref for RetrievedAdvisory {
70    type Target = DiscoveredAdvisory;
71
72    fn deref(&self) -> &Self::Target {
73        &self.discovered
74    }
75}
76
77impl DerefMut for RetrievedAdvisory {
78    fn deref_mut(&mut self) -> &mut Self::Target {
79        &mut self.discovered
80    }
81}
82
83impl RetrievedDocument for RetrievedAdvisory {
84    type Discovered = DiscoveredAdvisory;
85}
86
87pub struct RetrievalContext<'c> {
88    pub discovered: &'c DiscoveredContext<'c>,
89    pub keys: &'c Vec<PublicKey>,
90}
91
92impl<'c> Deref for RetrievalContext<'c> {
93    type Target = DiscoveredContext<'c>;
94
95    fn deref(&self) -> &Self::Target {
96        self.discovered
97    }
98}
99
100pub trait RetrievedVisitor<S: Source> {
101    type Error: std::fmt::Display + Debug;
102    type Context;
103
104    fn visit_context(
105        &self,
106        context: &RetrievalContext,
107    ) -> impl Future<Output = Result<Self::Context, Self::Error>>;
108
109    fn visit_advisory(
110        &self,
111        context: &Self::Context,
112        result: Result<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>>,
113    ) -> impl Future<Output = Result<(), Self::Error>>;
114}
115
116impl<F, E, Fut, S> RetrievedVisitor<S> for F
117where
118    F: Fn(Result<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>>) -> Fut,
119    Fut: Future<Output = Result<(), E>>,
120    E: std::fmt::Display + Debug,
121    S: Source,
122{
123    type Error = E;
124    type Context = ();
125
126    async fn visit_context(
127        &self,
128        _context: &RetrievalContext<'_>,
129    ) -> Result<Self::Context, Self::Error> {
130        Ok(())
131    }
132
133    async fn visit_advisory(
134        &self,
135        _ctx: &Self::Context,
136        outcome: Result<RetrievedAdvisory, RetrievalError<DiscoveredAdvisory, S>>,
137    ) -> Result<(), Self::Error> {
138        self(outcome).await
139    }
140}
141
142pub struct RetrievingVisitor<V: RetrievedVisitor<S>, S: Source + KeySource> {
143    visitor: V,
144    source: S,
145}
146
147impl<V, S> RetrievingVisitor<V, S>
148where
149    V: RetrievedVisitor<S>,
150    S: Source + KeySource,
151{
152    pub fn new(source: S, visitor: V) -> Self {
153        Self { visitor, source }
154    }
155}
156
157#[derive(Debug, thiserror::Error)]
158pub enum Error<VE, SE, KSE>
159where
160    VE: std::fmt::Display + Debug,
161    SE: std::fmt::Display + Debug,
162    KSE: std::fmt::Display + Debug,
163{
164    #[error("Source error: {0}")]
165    Source(SE),
166    #[error("Key source error: {0}")]
167    KeySource(KeySourceError<KSE>),
168    #[error(transparent)]
169    Visitor(VE),
170}
171
172impl<V, S> DiscoveredVisitor for RetrievingVisitor<V, S>
173where
174    V: RetrievedVisitor<S>,
175    S: Source + KeySource,
176{
177    type Error =
178        Error<V::Error, <S as walker_common::source::Source>::Error, <S as KeySource>::Error>;
179    type Context = V::Context;
180
181    async fn visit_context(
182        &self,
183        context: &DiscoveredContext<'_>,
184    ) -> Result<Self::Context, Self::Error> {
185        let mut keys = Vec::with_capacity(context.metadata.public_openpgp_keys.len());
186
187        for key in &context.metadata.public_openpgp_keys {
188            keys.push(
189                self.source
190                    .load_public_key(key.into())
191                    .await
192                    .map_err(Error::KeySource)?,
193            );
194        }
195
196        log::info!(
197            "Loaded {} public key{}",
198            keys.len(),
199            (keys.len() != 1).then_some("s").unwrap_or_default()
200        );
201        if log::log_enabled!(log::Level::Debug) {
202            for key in keys.iter().flat_map(|k| &k.certs) {
203                log::debug!("   {}", key.key_handle());
204                for id in key.userids() {
205                    log::debug!("     {}", String::from_utf8_lossy(id.value()));
206                }
207            }
208        }
209
210        self.visitor
211            .visit_context(&RetrievalContext {
212                discovered: context,
213                keys: &keys,
214            })
215            .await
216            .map_err(Error::Visitor)
217    }
218
219    async fn visit_advisory(
220        &self,
221        context: &Self::Context,
222        discovered: DiscoveredAdvisory,
223    ) -> Result<(), Self::Error> {
224        let advisory = self
225            .source
226            .load_advisory(discovered.clone())
227            .await
228            .map_err(|err| RetrievalError::Source { err, discovered });
229
230        self.visitor
231            .visit_advisory(context, advisory)
232            .await
233            .map_err(Error::Visitor)?;
234
235        Ok(())
236    }
237}