1pub 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#[derive(Debug, Clone)]
43pub struct Proof {
44 body: String,
46
47 signature: String,
49
50 common_content: Common,
52
53 digest: [u8; 32],
56}
57
58impl Proof {
59 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 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 #[must_use]
97 pub fn body(&self) -> &str {
98 self.body.as_str()
99 }
100
101 #[must_use]
103 pub fn signature(&self) -> &str {
104 self.signature.as_str()
105 }
106
107 #[must_use]
109 pub fn digest(&self) -> &[u8; 32] {
110 &self.digest
111 }
112
113 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 = "-----";
131const 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 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 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#[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#[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}