1use std::{borrow, fmt, ops};
11use bcder::{decode, encode};
12use bcder::{
13 BitString, Captured, Ia5String, Mode, OctetString, Oid, Tag,
14};
15use bcder::decode::{DecodeError, IntoSource, Source};
16use bcder::encode::{PrimitiveContent, Values};
17use bytes::Bytes;
18use crate::{oid, uri};
19use crate::crypto::{DigestAlgorithm, Signer, SigningError};
20#[cfg(feature = "serde")] use crate::util::base64;
21use super::cert::{Cert, ResourceCert};
22use super::error::{ValidationError, VerificationError};
23use super::sigobj::{SignedObject, SignedObjectBuilder};
24use super::x509::{Serial, Time};
25
26
27#[derive(Clone, Debug)]
35pub struct Manifest {
36 signed: SignedObject,
37 content: ManifestContent,
38}
39
40impl Manifest {
41 #[allow(clippy::redundant_closure)]
43 pub fn decode<S: IntoSource>(
44 source: S,
45 strict: bool
46 ) -> Result<Self, DecodeError<<S::Source as Source>::Error>> {
47 let signed = SignedObject::decode_if_type(
48 source, &oid::CT_RPKI_MANIFEST, strict
49 )?;
50 let content = signed.decode_content(
51 |cons| ManifestContent::take_from(cons)
52 ).map_err(DecodeError::convert)?;
53 Ok(Manifest { signed, content })
54 }
55
56 pub fn validate(
62 self,
63 cert: &ResourceCert,
64 strict: bool,
65 ) -> Result<(ResourceCert, ManifestContent), ValidationError> {
66 self.validate_at(cert, strict, Time::now())
67 }
68
69 pub fn validate_at(
70 self,
71 cert: &ResourceCert,
72 strict: bool,
73 now: Time
74 ) -> Result<(ResourceCert, ManifestContent), ValidationError> {
75 let cert = self.signed.validate_at(cert, strict, now)?;
76 Ok((cert, self.content))
77 }
78
79 pub fn encode_ref(&self) -> impl encode::Values + '_ {
81 self.signed.encode_ref()
82 }
83
84 pub fn to_captured(&self) -> Captured {
86 self.encode_ref().to_captured(Mode::Der)
87 }
88
89 pub fn cert(&self) -> &Cert {
91 self.signed.cert()
92 }
93
94 pub fn content(&self) -> &ManifestContent {
96 &self.content
97 }
98}
99
100
101impl ops::Deref for Manifest {
104 type Target = ManifestContent;
105
106 fn deref(&self) -> &Self::Target {
107 &self.content
108 }
109}
110
111impl AsRef<Manifest> for Manifest {
112 fn as_ref(&self) -> &Self {
113 self
114 }
115}
116
117impl AsRef<ManifestContent> for Manifest {
118 fn as_ref(&self) -> &ManifestContent {
119 &self.content
120 }
121}
122
123impl borrow::Borrow<ManifestContent> for Manifest {
124 fn borrow(&self) -> &ManifestContent {
125 &self.content
126 }
127}
128
129
130#[cfg(feature = "serde")]
133impl serde::Serialize for Manifest {
134 fn serialize<S: serde::Serializer>(
135 &self,
136 serializer: S
137 ) -> Result<S::Ok, S::Error> {
138 let bytes = self.to_captured().into_bytes();
139 let b64 = base64::Serde.encode(&bytes);
140 b64.serialize(serializer)
141 }
142}
143
144#[cfg(feature = "serde")]
145impl<'de> serde::Deserialize<'de> for Manifest {
146 fn deserialize<D: serde::Deserializer<'de>>(
147 deserializer: D
148 ) -> Result<Self, D::Error> {
149 use serde::de;
150
151 let s = String::deserialize(deserializer)?;
152 let decoded = base64::Serde.decode(&s).map_err(de::Error::custom)?;
153 let bytes = Bytes::from(decoded);
154 Manifest::decode(bytes, true).map_err(de::Error::custom)
155 }
156}
157
158
159#[derive(Clone, Debug)]
163pub struct ManifestContent {
164 manifest_number: Serial,
166
167 this_update: Time,
169
170 next_update: Time,
172
173 file_hash_alg: DigestAlgorithm,
175
176 file_list: Captured,
181
182 len: usize,
184}
185
186
187impl ManifestContent {
190 pub fn new<I, FH, F, H>(
191 manifest_number: Serial,
192 this_update: Time,
193 next_update: Time,
194 file_hash_alg: DigestAlgorithm,
195 iter: I,
196 ) -> Self
197 where
198 I: IntoIterator<Item = FH>,
199 FH: AsRef<FileAndHash<F, H>>,
200 F: AsRef<[u8]>,
201 H: AsRef<[u8]>,
202 {
203 let mut len = 0;
204 let mut file_list = Captured::builder(Mode::Der);
205 for item in iter.into_iter() {
206 file_list.extend(item.as_ref().encode_ref());
207 len += 1;
208 }
209 Self {
210 manifest_number,
211 this_update,
212 next_update,
213 file_hash_alg,
214 file_list: file_list.freeze(),
215 len
216 }
217 }
218
219 pub fn into_manifest<S: Signer>(
220 self,
221 mut sigobj: SignedObjectBuilder,
222 signer: &S,
223 issuer_key: &S::KeyId,
224 ) -> Result<Manifest, SigningError<S::Error>> {
225 sigobj.set_v4_resources_inherit();
226 sigobj.set_v6_resources_inherit();
227 sigobj.set_as_resources_inherit();
228 let signed = sigobj.finalize(
229 Oid(oid::CT_RPKI_MANIFEST.0.into()),
230 self.encode_ref().to_captured(Mode::Der).into_bytes(),
231 signer,
232 issuer_key,
233 )?;
234 Ok(Manifest { signed, content: self })
235 }
236}
237
238
239impl ManifestContent {
242 pub fn manifest_number(&self) -> Serial {
244 self.manifest_number
245 }
246
247 pub fn this_update(&self) -> Time {
249 self.this_update
250 }
251
252 pub fn next_update(&self) -> Time {
254 self.next_update
255 }
256
257 pub fn file_hash_alg(&self) -> DigestAlgorithm {
259 self.file_hash_alg
260 }
261
262 pub fn iter(&self) -> FileListIter {
264 FileListIter(self.file_list.clone())
265 }
266
267 pub fn iter_uris<'a>(
272 &'a self,
273 base: &'a uri::Rsync
274 ) -> impl Iterator<Item = (uri::Rsync, ManifestHash)> + 'a {
275 let alg = self.file_hash_alg;
276 self.iter().map(move |item| {
277 let (file, hash) = item.into_pair();
278 (
279 base.join(file.as_ref()).unwrap(),
280 ManifestHash::new(hash, alg)
281 )
282 })
283 }
284
285 pub fn len(&self) -> usize {
287 self.len
288 }
289
290 pub fn is_empty(&self) -> bool {
292 self.file_list.is_empty()
293 }
294
295 pub fn is_stale(&self) -> bool {
299 self.next_update < Time::now()
300 }
301}
302
303impl ManifestContent {
306 pub fn take_from<S: decode::Source>(
308 cons: &mut decode::Constructed<S>
309 ) -> Result<Self, DecodeError<S::Error>> {
310 cons.take_sequence(|cons| {
311 cons.take_opt_constructed_if(Tag::CTX_0, |c| c.skip_u8_if(0))?;
312 let manifest_number = Serial::take_from(cons)?;
313 let this_update = Time::take_from(cons)?;
314 let next_update = Time::take_from(cons)?;
315 let file_hash_alg = DigestAlgorithm::take_oid_from(cons)?;
316 if this_update > next_update {
317 return Err(cons.content_err(
318 "thisUpdate after nextUpdate"
319 ));
320 }
321
322 let mut len = 0;
323 let file_list = cons.take_sequence(|cons| {
324 cons.capture(|cons| {
325 while let Some(()) = FileAndHash::skip_opt_in(cons)? {
326 len += 1;
327 }
328 Ok(())
329 })
330 })?;
331
332 Ok(Self {
333 manifest_number,
334 this_update,
335 next_update,
336 file_hash_alg,
337 file_list,
338 len
339 })
340 })
341 }
342
343
344 pub fn encode_ref(&self) -> impl encode::Values + '_ {
346 encode::sequence((
347 self.manifest_number.encode(),
348 self.this_update.encode_generalized_time(),
349 self.next_update.encode_generalized_time(),
350 self.file_hash_alg.encode_oid(),
351 encode::sequence(
352 &self.file_list
353 )
354 ))
355 }
356}
357
358
359#[derive(Clone, Debug)]
363pub struct FileListIter(Captured);
364
365impl Iterator for FileListIter {
366 type Item = FileAndHash<Bytes, Bytes>;
367
368 fn next(&mut self) -> Option<Self::Item> {
369 self.0.decode_partial(|cons| {
370 FileAndHash::take_opt_from(cons)
371 }).unwrap()
372 }
373}
374
375
376#[derive(Clone, Debug)]
383pub struct FileAndHash<F, H> {
384 file: F,
386
387 hash: H
389}
390
391impl<F, H> FileAndHash<F, H> {
393 pub fn new(file: F, hash: H) -> Self {
395 FileAndHash { file, hash }
396 }
397
398 pub fn file(&self) -> &F {
400 &self.file
401 }
402
403 pub fn hash(&self) -> &H {
405 &self.hash
406 }
407
408 pub fn into_pair(self) -> (F, H) {
410 (self.file, self.hash)
411 }
412}
413
414
415impl FileAndHash<Bytes, Bytes> {
418 fn skip_opt_in<S: decode::Source>(
420 cons: &mut decode::Constructed<S>
421 ) -> Result<Option<()>, DecodeError<S::Error>> {
422 cons.take_opt_sequence(|cons| {
423 let file = Ia5String::take_from(cons)?.into_bytes();
424 if let Err(err) = Self::validate_file_name(&file) {
425 return Err(cons.content_err(err));
426 }
427 BitString::skip_in(cons)?;
428 Ok(())
429 })
430 }
431
432 fn take_opt_from<S: decode::Source>(
434 cons: &mut decode::Constructed<S>
435 ) -> Result<Option<Self>, DecodeError<S::Error>> {
436 cons.take_opt_sequence(|cons| {
437 let file = Ia5String::take_from(cons)?.into_bytes();
438 if let Err(err) = Self::validate_file_name(&file) {
439 return Err(cons.content_err(err));
440 }
441 Ok(FileAndHash {
442 file,
443 hash: BitString::take_from(cons)?.octet_bytes(),
444 })
445 })
446 }
447
448 fn validate_file_name(name: &[u8]) -> Result<(), &'static str> {
456 fn valid_rfc9286_character(c: u8) -> bool {
457 c == b'-' || c == b'_' || c.is_ascii_alphanumeric()
458 }
459
460 let mut n = name;
461 while let Some((c, tail)) = n.split_first() {
462 n = tail;
463 if *c == b'.' {
464 break;
465 }
466 else if !valid_rfc9286_character(*c) {
467 return Err("manifest filename is not RFC 9286 4.2.2 compliant");
468 }
469 }
470
471 if n.len() != 3 || !n.iter().all(|c| c.is_ascii_alphabetic()) {
476 return Err("manifest extension is not RFC 9286 4.2.2 compliant");
477 }
478
479 Ok(())
480 }
481}
482
483impl<F: AsRef<[u8]>, H: AsRef<[u8]>> FileAndHash<F, H> {
484 pub fn encode_ref(&self) -> impl encode::Values + '_ {
486 encode::sequence((
487 OctetString::encode_slice_as(self.file.as_ref(), Tag::IA5_STRING),
488 BitString::encode_slice(self.hash.as_ref(), 0),
489 ))
490 }
491}
492
493
494impl<F: AsRef<[u8]>, H: AsRef<[u8]>> AsRef<Self> for FileAndHash<F, H> {
497 fn as_ref(&self) -> &Self {
498 self
499 }
500}
501
502
503#[derive(Clone, Debug, Eq, Hash, PartialEq)]
510pub struct ManifestHash {
511 hash: Bytes,
512 algorithm: DigestAlgorithm,
513}
514
515impl ManifestHash {
516 pub fn new(hash: Bytes, algorithm: DigestAlgorithm) -> Self {
518 Self { hash, algorithm }
519 }
520
521 pub fn verify<T: AsRef<[u8]>>(
523 &self,
524 t: T
525 ) -> Result<(), ManifestHashMismatch> {
526 if self.hash.as_ref() != self.algorithm.digest(t.as_ref()).as_ref() {
527 Err(ManifestHashMismatch(()))
528 }
529 else {
530 Ok(())
531 }
532 }
533
534 pub fn algorithm(&self) -> DigestAlgorithm {
536 self.algorithm
537 }
538
539 pub fn as_slice(&self) -> &[u8] {
541 self.hash.as_ref()
542 }
543}
544
545
546#[derive(Clone, Copy, Debug)]
550pub struct ManifestHashMismatch(());
551
552impl fmt::Display for ManifestHashMismatch {
553 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
554 f.write_str("manifest hash mismatch")
555 }
556}
557
558impl From<ManifestHashMismatch> for VerificationError {
559 fn from(_: ManifestHashMismatch) -> VerificationError {
560 VerificationError::new("manifest hash mismatch")
561 }
562}
563
564
565#[cfg(test)]
568mod test {
569 use crate::repository::tal::TalInfo;
570 use super::*;
571
572 #[test]
573 fn decode() {
574 let talinfo = TalInfo::from_name("foo".into()).into_arc();
575 let at = Time::utc(2019, 5, 1, 0, 0, 0);
576 let issuer = Cert::decode(
577 include_bytes!("../../test-data/repository/ta.cer").as_ref()
578 ).unwrap();
579 let issuer = issuer.validate_ta_at(talinfo, false, at).unwrap();
580 let obj = Manifest::decode(
581 include_bytes!("../../test-data/repository/ta.mft").as_ref(),
582 false
583 ).unwrap();
584 obj.validate_at(&issuer, false, at).unwrap();
585 let obj = Manifest::decode(
586 include_bytes!("../../test-data/repository/ca1.mft").as_ref(),
587 false
588 ).unwrap();
589 assert!(obj.validate_at(&issuer, false, at).is_err());
590 }
591
592 #[test]
593 fn verify_manifest_hash() {
594 let alg = DigestAlgorithm::sha256();
595 let hash = ManifestHash::new(
596 Bytes::copy_from_slice(alg.digest(b"foobar").as_ref()),
597 alg
598 );
599
600 assert!(hash.verify(b"foobar").is_ok());
601 assert!(hash.verify(b"barfoo").is_err());
602 }
603
604 #[test]
605 #[cfg(feature = "serde")]
606 fn compat_de_manifest() {
607 serde_json::from_slice::<Manifest>(include_bytes!(
608 "../../test-data/repository/serde-compat/manifest.json"
609 )).unwrap();
610 }
611
612 #[test]
613 fn charset_violation() {
614 assert!(
615 Manifest::decode(
616 include_bytes!(
619 "../../test-data/repository/ta.mft.bad-filename"
620 ).as_ref(),
621 false,
622 ).is_err()
623 );
624 }
625
626 #[test]
627 fn manifest_file_validation() {
628 fn test_name(x: &'static str) -> bool {
629 FileAndHash::validate_file_name(x.as_bytes()).is_ok()
630 }
631
632 assert!(test_name("correct.cer"));
633 assert!(test_name("correct.ASA"));
634 assert!(!test_name("slash//es.mft"));
635 assert!(test_name("unknownextension.abc"));
636 assert!(!test_name("new\r\nlines.gbr"));
637 assert!(test_name("dashes-and_underscores.gbr"));
638 assert!(!test_name("multiple.dots.in.file.name.roa"));
639 assert!(!test_name("too_long_extension.koen"));
640 }
641}
642
643#[cfg(all(test, feature = "softkeys"))]
644mod signer_test {
645 use std::str::FromStr;
646 use crate::repository::cert::{KeyUsage, Overclaim, TbsCert};
647 use crate::crypto::PublicKeyFormat;
648 use crate::crypto::softsigner::OpenSslSigner;
649 use crate::repository::resources::{Asn, Prefix};
650 use crate::repository::tal::TalInfo;
651 use crate::repository::x509::Validity;
652 use super::*;
653
654 fn make_test_manifest() -> Manifest {
655 let signer = OpenSslSigner::new();
656 let key = signer.create_key(PublicKeyFormat::Rsa).unwrap();
657 let pubkey = signer.get_key_info(&key).unwrap();
658 let uri = uri::Rsync::from_str("rsync://example.com/m/p").unwrap();
659
660 let mut cert = TbsCert::new(
661 12u64.into(), pubkey.to_subject_name(),
662 Validity::from_secs(86400), None, pubkey, KeyUsage::Ca,
663 Overclaim::Trim
664 );
665 cert.set_basic_ca(Some(true));
666 cert.set_ca_repository(Some(uri.clone()));
667 cert.set_rpki_manifest(Some(uri.clone()));
668 cert.build_v4_resource_blocks(|b| b.push(Prefix::new(0, 0)));
669 cert.build_v6_resource_blocks(|b| b.push(Prefix::new(0, 0)));
670 cert.build_as_resource_blocks(|b| b.push((Asn::MIN, Asn::MAX)));
671 let cert = cert.into_cert(&signer, &key).unwrap();
672
673 let content = ManifestContent::new(
674 12u64.into(), Time::now(), Time::next_week(),
675 DigestAlgorithm::default(),
676 [
677 FileAndHash::new(b"file.cer".as_ref(), b"hash".as_ref()),
678 FileAndHash::new(b"file.cer".as_ref(), b"hash".as_ref()),
679 ].iter()
680 );
681
682 let manifest = content.into_manifest(
683 SignedObjectBuilder::new(
684 12u64.into(), Validity::from_secs(86400), uri.clone(),
685 uri.clone(), uri
686 ),
687 &signer, &key
688 ).unwrap();
689 let manifest = manifest.encode_ref().to_captured(Mode::Der);
690
691 let manifest = Manifest::decode(manifest.as_slice(), true).unwrap();
692 let cert = cert.validate_ta(
693 TalInfo::from_name("foo".into()).into_arc(), true
694 ).unwrap();
695 manifest.clone().validate(&cert, true).unwrap();
696
697 manifest
698 }
699
700 #[test]
701 fn encode_manifest() {
702 make_test_manifest();
703 }
704
705 #[test]
706 #[cfg(feature = "serde")]
707 fn serde_manifest() {
708 let mft = make_test_manifest();
709 let serialized = serde_json::to_string(&mft).unwrap();
710 let deser_mft: Manifest = serde_json::from_str(&serialized).unwrap();
711
712 assert_eq!(
713 mft.to_captured().into_bytes(),
714 deser_mft.to_captured().into_bytes()
715 );
716 }
717}
718