crev_data/proof/
mod.rs

1//! Some common stuff for both Review and Trust Proofs
2
3pub use crate::proof::content::{
4    Common, CommonOps, Content, ContentDeserialize, ContentExt, ContentWithDraft, Draft, WithReview,
5};
6use crate::{Error, ParseError, PublicId, Result};
7use chrono::{self, prelude::*};
8pub use package_info::*;
9pub use review::{Code as CodeReview, Package as PackageReview, *};
10pub use revision::*;
11use serde::{Deserialize, Serialize};
12use std::{
13    fmt,
14    io::{self, BufRead},
15};
16pub use trust::*;
17
18pub mod content;
19pub mod package_info;
20pub mod review;
21pub mod revision;
22pub mod trust;
23
24const MAX_PROOF_BODY_LENGTH: usize = 32_000;
25
26pub type Date = chrono::DateTime<FixedOffset>;
27pub type DateUtc = chrono::DateTime<Utc>;
28
29#[derive(Debug, Clone)]
30pub struct Digest(pub [u8; 32]);
31
32impl Digest {
33    #[must_use]
34    pub fn to_base64(&self) -> String {
35        crev_common::base64_encode(&self.0)
36    }
37}
38
39/// Serialized Proof (crate review or trust of someone)
40///
41/// A signed proof containing some signed `Content`
42#[derive(Debug, Clone)]
43pub struct Proof {
44    /// Serialized content
45    body: String,
46
47    /// Signature over the body
48    signature: String,
49
50    /// Common information that should be in any  proof
51    common_content: Common,
52
53    /// Digest (blake2b256)
54    /// TODO: Use Digest newtype here
55    digest: [u8; 32],
56}
57
58impl Proof {
59    /// Assumes the body has been properly signed already
60    pub fn from_parts(body: String, signature: String) -> Result<Self> {
61        let common_content: Common = serde_yaml::from_str(&body).map_err(ParseError::Proof)?;
62        if common_content.kind.is_none() {
63            return Err(Error::KindFieldMissing);
64        }
65        let digest = crev_common::blake2b256sum(body.as_bytes());
66        let signature = signature.trim().to_owned();
67        Ok(Self {
68            body,
69            signature,
70            common_content,
71            digest,
72        })
73    }
74
75    /// For back-compat, ignore it
76    pub fn from_legacy_parts(body: String, signature: String, type_name: String) -> Result<Self> {
77        #[allow(deprecated)]
78        let mut legacy_common_content: content::Common =
79            serde_yaml::from_str(&body).map_err(ParseError::Proof)?;
80        if legacy_common_content.kind.is_some() {
81            return Err(Error::UnexpectedKindValueInALegacyFormat);
82        }
83
84        legacy_common_content.kind = Some(type_name);
85        let digest = crev_common::blake2b256sum(body.as_bytes());
86        let signature = signature.trim().to_owned();
87        Ok(Self {
88            body,
89            signature,
90            common_content: legacy_common_content,
91            digest,
92        })
93    }
94
95    /// YAML in it
96    #[must_use]
97    pub fn body(&self) -> &str {
98        self.body.as_str()
99    }
100
101    /// Signature attached to it
102    #[must_use]
103    pub fn signature(&self) -> &str {
104        self.signature.as_str()
105    }
106
107    /// Hash of the body
108    #[must_use]
109    pub fn digest(&self) -> &[u8; 32] {
110        &self.digest
111    }
112
113    /// Read the YAML with serde, you should already know which type to expect from context
114    pub fn parse_content<T: ContentDeserialize>(&self) -> std::result::Result<T, Error> {
115        T::deserialize_from(self.body.as_bytes())
116    }
117}
118
119impl CommonOps for Proof {
120    fn common(&self) -> &Common {
121        &self.common_content
122    }
123}
124
125const PROOF_START: &str = "----- BEGIN CREV PROOF -----";
126const PROOF_SIGNATURE: &str = "----- SIGN CREV PROOF -----";
127const PROOF_END: &str = "----- END CREV PROOF -----";
128
129const LEGACY_PROOF_START_PREFIX: &str = "-----BEGIN CREV ";
130const LEGACY_PROOF_START_SUFFIX: &str = "-----";
131// There was a bug ... :D ... https://github.com/dpc/crev-proofs/blob/3ea7e440f1ed84f5a333741e71a90e2067fe9cfc/FYlr8YoYGVvDwHQxqEIs89reKKDy-oWisoO0qXXEfHE/trust/2019-10-GkN7aw.proof.crev#L1
132const LEGACY_PROOF_START_SUFFIX_ALT: &str = " -----";
133const LEGACY_PROOF_SIGNATURE_PREFIX: &str = "-----BEGIN CREV ";
134const LEGACY_PROOF_SIGNATURE_SUFFIX: &str = " SIGNATURE-----";
135const LEGACY_PROOF_END_PREFIX: &str = "-----END CREV ";
136const LEGACY_PROOF_END_SUFFIX: &str = "-----";
137
138fn is_start_line(line: &str) -> bool {
139    line.trim() == PROOF_START
140}
141
142fn is_signature_line(line: &str) -> bool {
143    line.trim() == PROOF_SIGNATURE
144}
145
146fn is_end_line(line: &str) -> bool {
147    line.trim() == PROOF_END
148}
149
150fn is_legacy_start_line(line: &str) -> Option<String> {
151    let trimmed = line.trim();
152
153    if trimmed.starts_with(LEGACY_PROOF_START_PREFIX)
154        && trimmed.ends_with(LEGACY_PROOF_START_SUFFIX_ALT)
155    {
156        let type_name = &trimmed[LEGACY_PROOF_START_PREFIX.len()..];
157        let type_name = &type_name[..(type_name.len() - LEGACY_PROOF_START_SUFFIX_ALT.len())];
158
159        Some(type_name.to_lowercase())
160    } else if trimmed.starts_with(LEGACY_PROOF_START_PREFIX)
161        && trimmed.ends_with(LEGACY_PROOF_START_SUFFIX)
162    {
163        let type_name = &trimmed[LEGACY_PROOF_START_PREFIX.len()..];
164        let type_name = &type_name[..(type_name.len() - LEGACY_PROOF_START_SUFFIX.len())];
165
166        Some(type_name.to_lowercase())
167    } else {
168        None
169    }
170}
171
172fn is_legacy_signature_line(line: &str) -> Option<String> {
173    let trimmed = line.trim();
174
175    if trimmed.starts_with(LEGACY_PROOF_SIGNATURE_PREFIX)
176        && trimmed.ends_with(LEGACY_PROOF_SIGNATURE_SUFFIX)
177    {
178        let type_name = &trimmed[LEGACY_PROOF_SIGNATURE_PREFIX.len()..];
179        let type_name = &type_name[..(type_name.len() - LEGACY_PROOF_SIGNATURE_SUFFIX.len())];
180
181        Some(type_name.to_lowercase())
182    } else {
183        None
184    }
185}
186
187fn is_legacy_end_line(line: &str) -> Option<String> {
188    let trimmed = line.trim();
189
190    if trimmed.starts_with(LEGACY_PROOF_END_PREFIX) && trimmed.ends_with(LEGACY_PROOF_END_SUFFIX) {
191        let type_name = &trimmed[LEGACY_PROOF_END_PREFIX.len()..];
192        let type_name = &type_name[..(type_name.len() - LEGACY_PROOF_END_SUFFIX.len())];
193
194        Some(type_name.to_lowercase())
195    } else {
196        None
197    }
198}
199
200impl fmt::Display for Proof {
201    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202        f.write_str(PROOF_START)?;
203        f.write_str("\n")?;
204        f.write_str(&self.body)?;
205        f.write_str(PROOF_SIGNATURE)?;
206        f.write_str("\n")?;
207        f.write_str(&self.signature)?;
208        f.write_str("\n")?;
209        f.write_str(PROOF_END)?;
210        f.write_str("\n")?;
211
212        Ok(())
213    }
214}
215
216impl Proof {
217    /// Read from a file (uses buffering)
218    pub fn parse_from(reader: impl io::Read) -> Result<Vec<Self>> {
219        let reader = std::io::BufReader::new(reader);
220
221        #[derive(PartialEq, Eq, Default)]
222        enum Stage {
223            #[default]
224            None,
225            Body,
226            Signature,
227        }
228
229        #[derive(Default)]
230        struct State {
231            stage: Stage,
232            body: String,
233            signature: String,
234            type_name: Option<String>,
235            proofs: Vec<Proof>,
236        }
237
238        impl State {
239            fn process_line(&mut self, line: &str) -> Result<()> {
240                match self.stage {
241                    Stage::None => {
242                        let line = line.trim();
243                        if line.is_empty() {
244                        } else if let Some(type_name) = is_legacy_start_line(line) {
245                            self.type_name = Some(type_name);
246                            self.stage = Stage::Body;
247                        } else if is_start_line(line) {
248                            assert!(self.type_name.is_none());
249                            self.stage = Stage::Body;
250                        } else {
251                            return Err(Error::ParsingErrorWhenLookingForStartOfCodeReviewProof);
252                        }
253                    }
254                    Stage::Body => {
255                        if self.type_name.is_some() {
256                            if let Some(type_name) = is_legacy_signature_line(line) {
257                                if Some(type_name) != self.type_name {
258                                    return Err(Error::ParsingErrorTypeNameMismatchInTheSignature);
259                                }
260                                self.stage = Stage::Signature;
261                            } else {
262                                self.body += line;
263                                self.body += "\n";
264                            }
265                        } else if is_signature_line(line) {
266                            self.stage = Stage::Signature;
267                        } else {
268                            self.body += line;
269                            self.body += "\n";
270                        }
271                        if self.body.len() > MAX_PROOF_BODY_LENGTH {
272                            return Err(Error::ProofBodyTooLong);
273                        }
274                    }
275                    Stage::Signature => {
276                        if self.type_name.is_some() {
277                            if let Some(type_name) = is_legacy_end_line(line) {
278                                if Some(&type_name) != self.type_name.as_ref() {
279                                    return Err(Error::ParsingErrorTypeNameMismatchInTheFooter);
280                                }
281                                self.stage = Stage::None;
282                                self.type_name = None;
283                                self.proofs.push(Proof::from_legacy_parts(
284                                    std::mem::take(&mut self.body),
285                                    std::mem::take(&mut self.signature),
286                                    type_name,
287                                )?);
288                            } else {
289                                self.signature += line;
290                                self.signature += "\n";
291                            }
292                        } else if is_end_line(line) {
293                            self.stage = Stage::None;
294                            self.proofs.push(Proof::from_parts(
295                                std::mem::take(&mut self.body),
296                                std::mem::take(&mut self.signature),
297                            )?);
298                        } else {
299                            self.signature += line;
300                            self.signature += "\n";
301                        }
302
303                        if self.signature.len() > 2000 {
304                            return Err(Error::SignatureTooLong);
305                        }
306                    }
307                }
308                Ok(())
309            }
310
311            fn finish(self) -> Result<Vec<Proof>> {
312                if self.stage != Stage::None {
313                    return Err(Error::UnexpectedEOFWhileParsing);
314                }
315                Ok(self.proofs)
316            }
317        }
318
319        let mut state: State = Default::default();
320
321        for line in reader.lines() {
322            state.process_line(&line?)?;
323        }
324
325        state.finish()
326    }
327
328    /// Checks the signature
329    pub fn verify(&self) -> Result<()> {
330        let pubkey = &self.from().id;
331        pubkey.verify_signature(self.body.as_bytes(), self.signature())?;
332
333        Ok(())
334    }
335}
336
337fn equals_default_digest_type(s: &str) -> bool {
338    s == default_digest_type()
339}
340
341#[must_use]
342pub fn default_digest_type() -> String {
343    "blake2b".into()
344}
345
346fn equals_default_revision_type(s: &str) -> bool {
347    s == default_revision_type()
348}
349
350#[must_use]
351pub fn default_revision_type() -> String {
352    "git".into()
353}
354
355fn equals_default<T: Default + PartialEq>(t: &T) -> bool {
356    *t == Default::default()
357}
358
359/// A particular ID to override judgment of
360///
361/// Used to correct (well, discard really) specific judgments without
362/// loosing the overall work.
363#[derive(Clone, Debug, Serialize, Deserialize)]
364pub struct OverrideItem {
365    #[serde(flatten)]
366    pub id: PublicId,
367    #[serde(skip_serializing_if = "String::is_empty", default = "Default::default")]
368    pub comment: String,
369}
370
371/// Like [`OverrideItem`] but with a different serialization.
372///
373/// When editing a draft in code editor, we don't want
374/// to skip empty `comments` to show to the user they exist and encourage
375/// to fill them.
376///
377/// Unfortunately we need another type for it (just like [`Draft`] itself).
378#[derive(Clone, Debug, Serialize, Deserialize)]
379pub struct OverrideItemDraft {
380    #[serde(flatten)]
381    pub id: PublicId,
382    #[serde(default = "Default::default")]
383    pub comment: String,
384}
385
386impl From<OverrideItem> for OverrideItemDraft {
387    fn from(item: OverrideItem) -> Self {
388        Self {
389            id: item.id,
390            comment: item.comment,
391        }
392    }
393}
394
395impl From<OverrideItemDraft> for OverrideItem {
396    fn from(item: OverrideItemDraft) -> Self {
397        Self {
398            id: item.id,
399            comment: item.comment,
400        }
401    }
402}