1use chrono::{offset::Utc, DateTime};
51use futures_io::AsyncRead;
52use log::{error, warn};
53use std::future::Future;
54use std::pin::Pin;
55
56use crate::crypto::{self, HashAlgorithm, HashValue, PublicKey};
57use crate::database::Database;
58use crate::error::{Error, Result};
59use crate::metadata::{
60 Metadata, MetadataPath, MetadataVersion, RawSignedMetadata, RootMetadata, SnapshotMetadata,
61 TargetDescription, TargetPath, TargetsMetadata,
62};
63use crate::pouf::Pouf;
64use crate::repository::{Repository, RepositoryProvider, RepositoryStorage};
65use crate::verify::Verified;
66
67#[derive(Debug)]
69pub struct Client<D, L, R>
70where
71 D: Pouf,
72 L: RepositoryProvider<D> + RepositoryStorage<D>,
73 R: RepositoryProvider<D>,
74{
75 config: Config,
76 tuf: Database<D>,
77 local: Repository<L, D>,
78 remote: Repository<R, D>,
79}
80
81impl<D, L, R> Client<D, L, R>
82where
83 D: Pouf,
84 L: RepositoryProvider<D> + RepositoryStorage<D>,
85 R: RepositoryProvider<D>,
86{
87 pub async fn with_trusted_local(config: Config, local: L, remote: R) -> Result<Self> {
145 let (local, remote) = (Repository::new(local), Repository::new(remote));
146 let root_path = MetadataPath::root();
147
148 let root_version = MetadataVersion::Number(1);
150
151 let raw_root: RawSignedMetadata<_, RootMetadata> = local
152 .fetch_metadata(&root_path, root_version, config.max_root_length, vec![])
153 .await?;
154
155 let tuf = Database::from_trusted_root(&raw_root)?;
156
157 Self::new(config, tuf, local, remote).await
158 }
159
160 pub async fn with_trusted_root(
210 config: Config,
211 trusted_root: &RawSignedMetadata<D, RootMetadata>,
212 local: L,
213 remote: R,
214 ) -> Result<Self> {
215 let (local, remote) = (Repository::new(local), Repository::new(remote));
216 let tuf = Database::from_trusted_root(trusted_root)?;
217
218 Self::new(config, tuf, local, remote).await
219 }
220
221 pub async fn with_trusted_root_keys<'a, I>(
281 config: Config,
282 root_version: MetadataVersion,
283 root_threshold: u32,
284 trusted_root_keys: I,
285 local: L,
286 remote: R,
287 ) -> Result<Self>
288 where
289 I: IntoIterator<Item = &'a PublicKey>,
290 {
291 let (mut local, remote) = (Repository::new(local), Repository::new(remote));
292
293 let root_path = MetadataPath::root();
294 let (fetched, raw_root) = fetch_metadata_from_local_or_else_remote(
295 &root_path,
296 root_version,
297 config.max_root_length,
298 vec![],
299 &local,
300 &remote,
301 )
302 .await?;
303
304 let tuf =
305 Database::from_root_with_trusted_keys(&raw_root, root_threshold, trusted_root_keys)?;
306
307 let root_version = MetadataVersion::Number(tuf.trusted_root().version());
309
310 if fetched {
312 local
325 .store_metadata(&root_path, root_version, &raw_root)
326 .await?;
327
328 }
330
331 Self::new(config, tuf, local, remote).await
332 }
333
334 pub fn from_database(config: Config, tuf: Database<D>, local: L, remote: R) -> Self {
336 Self {
337 config,
338 tuf,
339 local: Repository::new(local),
340 remote: Repository::new(remote),
341 }
342 }
343
344 pub fn from_parts(parts: Parts<D, L, R>) -> Self {
350 let Parts {
351 config,
352 database,
353 local,
354 remote,
355 } = parts;
356 Self {
357 config,
358 tuf: database,
359 local: Repository::new(local),
360 remote: Repository::new(remote),
361 }
362 }
363
364 async fn new(
366 config: Config,
367 mut tuf: Database<D>,
368 local: Repository<L, D>,
369 remote: Repository<R, D>,
370 ) -> Result<Self> {
371 let start_time = Utc::now();
372
373 let res = async {
374 let _r =
375 Self::update_root_with_repos(&start_time, &config, &mut tuf, None, &local).await?;
376 let _ts =
377 Self::update_timestamp_with_repos(&start_time, &config, &mut tuf, None, &local)
378 .await?;
379 let _sn = Self::update_snapshot_with_repos(
380 &start_time,
381 &config,
382 &mut tuf,
383 None,
384 &local,
385 false,
386 )
387 .await?;
388 let _ta = Self::update_targets_with_repos(
389 &start_time,
390 &config,
391 &mut tuf,
392 None,
393 &local,
394 false,
395 )
396 .await?;
397
398 Ok(())
399 }
400 .await;
401
402 match res {
403 Ok(()) | Err(Error::MetadataNotFound { .. }) => {}
404 Err(err) => {
405 warn!("error loading local metadata: : {}", err);
406 }
407 }
408
409 Ok(Client {
410 tuf,
411 config,
412 local,
413 remote,
414 })
415 }
416
417 pub async fn update(&mut self) -> Result<bool> {
421 self.update_with_start_time(&Utc::now()).await
422 }
423
424 pub async fn update_with_start_time(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
431 let r = self.update_root(start_time).await?;
432 let ts = self.update_timestamp(start_time).await?;
433 let sn = self.update_snapshot(start_time).await?;
434 let ta = self.update_targets(start_time).await?;
435
436 Ok(r || ts || sn || ta)
437 }
438
439 pub fn into_parts(self) -> Parts<D, L, R> {
441 let Client {
442 config,
443 tuf,
444 local,
445 remote,
446 } = self;
447 Parts {
448 config,
449 database: tuf,
450 local: local.into_inner(),
451 remote: remote.into_inner(),
452 }
453 }
454
455 pub fn database(&self) -> &Database<D> {
457 &self.tuf
458 }
459
460 pub fn database_mut(&mut self) -> &mut Database<D> {
462 &mut self.tuf
463 }
464
465 pub fn local_repo(&self) -> &L {
467 self.local.as_inner()
468 }
469
470 pub fn local_repo_mut(&mut self) -> &mut L {
472 self.local.as_inner_mut()
473 }
474
475 pub fn remote_repo(&self) -> &R {
477 self.remote.as_inner()
478 }
479
480 pub fn remote_repo_mut(&mut self) -> &mut R {
482 self.remote.as_inner_mut()
483 }
484
485 pub async fn update_root(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
489 Self::update_root_with_repos(
490 start_time,
491 &self.config,
492 &mut self.tuf,
493 Some(&mut self.local),
494 &self.remote,
495 )
496 .await
497 }
498
499 async fn update_root_with_repos<Remote>(
500 start_time: &DateTime<Utc>,
501 config: &Config,
502 tuf: &mut Database<D>,
503 mut local: Option<&mut Repository<L, D>>,
504 remote: &Repository<Remote, D>,
505 ) -> Result<bool>
506 where
507 Remote: RepositoryProvider<D>,
508 {
509 let root_path = MetadataPath::root();
510
511 let mut updated = false;
512
513 loop {
514 let next_version = MetadataVersion::Number(tuf.trusted_root().version() + 1);
532 let res = remote
533 .fetch_metadata(&root_path, next_version, config.max_root_length, vec![])
534 .await;
535
536 let raw_signed_root = match res {
537 Ok(raw_signed_root) => raw_signed_root,
538 Err(Error::MetadataNotFound { .. }) => {
539 break;
540 }
541 Err(err) => {
542 return Err(err);
543 }
544 };
545
546 updated = true;
547
548 tuf.update_root(&raw_signed_root)?;
549
550 if let Some(ref mut local) = local {
557 local
558 .store_metadata(&root_path, MetadataVersion::None, &raw_signed_root)
559 .await?;
560
561 local
563 .store_metadata(&root_path, next_version, &raw_signed_root)
564 .await?;
565 }
566
567 }
572
573 if tuf.trusted_root().expires() <= start_time {
585 error!("Root metadata expired, potential freeze attack");
586 return Err(Error::ExpiredMetadata {
587 path: MetadataPath::root(),
588 expiration: *tuf.trusted_root().expires(),
589 now: *start_time,
590 });
591 }
592
593 Ok(updated)
600 }
601
602 async fn update_timestamp(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
604 Self::update_timestamp_with_repos(
605 start_time,
606 &self.config,
607 &mut self.tuf,
608 Some(&mut self.local),
609 &self.remote,
610 )
611 .await
612 }
613
614 async fn update_timestamp_with_repos<Remote>(
615 start_time: &DateTime<Utc>,
616 config: &Config,
617 tuf: &mut Database<D>,
618 local: Option<&mut Repository<L, D>>,
619 remote: &Repository<Remote, D>,
620 ) -> Result<bool>
621 where
622 Remote: RepositoryProvider<D>,
623 {
624 let timestamp_path = MetadataPath::timestamp();
625
626 let raw_signed_timestamp = remote
635 .fetch_metadata(
636 ×tamp_path,
637 MetadataVersion::None,
638 config.max_timestamp_length,
639 vec![],
640 )
641 .await?;
642
643 if tuf
644 .update_timestamp(start_time, &raw_signed_timestamp)?
645 .is_some()
646 {
647 if let Some(local) = local {
654 local
655 .store_metadata(
656 ×tamp_path,
657 MetadataVersion::None,
658 &raw_signed_timestamp,
659 )
660 .await?;
661 }
662
663 Ok(true)
664 } else {
665 Ok(false)
666 }
667 }
668
669 async fn update_snapshot(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
671 let consistent_snapshot = self.tuf.trusted_root().consistent_snapshot();
672 Self::update_snapshot_with_repos(
673 start_time,
674 &self.config,
675 &mut self.tuf,
676 Some(&mut self.local),
677 &self.remote,
678 consistent_snapshot,
679 )
680 .await
681 }
682
683 async fn update_snapshot_with_repos<Remote>(
684 start_time: &DateTime<Utc>,
685 config: &Config,
686 tuf: &mut Database<D>,
687 local: Option<&mut Repository<L, D>>,
688 remote: &Repository<Remote, D>,
689 consistent_snapshots: bool,
690 ) -> Result<bool>
691 where
692 Remote: RepositoryProvider<D>,
693 {
694 let snapshot_description = match tuf.trusted_timestamp() {
695 Some(ts) => Ok(ts.snapshot()),
696 None => Err(Error::MetadataNotFound {
697 path: MetadataPath::timestamp(),
698 version: MetadataVersion::None,
699 }),
700 }?
701 .clone();
702
703 if snapshot_description.version()
704 <= tuf.trusted_snapshot().map(|s| s.version()).unwrap_or(0)
705 {
706 return Ok(false);
707 }
708
709 let version = if consistent_snapshots {
710 MetadataVersion::Number(snapshot_description.version())
711 } else {
712 MetadataVersion::None
713 };
714
715 let snapshot_path = MetadataPath::snapshot();
716
717 let snapshot_length = snapshot_description.length().or(config.max_snapshot_length);
722
723 let snapshot_hashes = crypto::retain_supported_hashes(snapshot_description.hashes());
728
729 let raw_signed_snapshot = remote
730 .fetch_metadata(&snapshot_path, version, snapshot_length, snapshot_hashes)
731 .await?;
732
733 if tuf.update_snapshot(start_time, &raw_signed_snapshot)? {
736 if let Some(local) = local {
741 local
742 .store_metadata(&snapshot_path, MetadataVersion::None, &raw_signed_snapshot)
743 .await?;
744 }
745
746 Ok(true)
747 } else {
748 Ok(false)
749 }
750 }
751
752 async fn update_targets(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
754 let consistent_snapshot = self.tuf.trusted_root().consistent_snapshot();
755 Self::update_targets_with_repos(
756 start_time,
757 &self.config,
758 &mut self.tuf,
759 Some(&mut self.local),
760 &self.remote,
761 consistent_snapshot,
762 )
763 .await
764 }
765
766 async fn update_targets_with_repos<Remote>(
767 start_time: &DateTime<Utc>,
768 config: &Config,
769 tuf: &mut Database<D>,
770 local: Option<&mut Repository<L, D>>,
771 remote: &Repository<Remote, D>,
772 consistent_snapshot: bool,
773 ) -> Result<bool>
774 where
775 Remote: RepositoryProvider<D>,
776 {
777 let targets_description = match tuf.trusted_snapshot() {
778 Some(sn) => match sn.meta().get(&MetadataPath::targets()) {
779 Some(d) => Ok(d),
780 None => Err(Error::MissingMetadataDescription {
781 parent_role: MetadataPath::snapshot(),
782 child_role: MetadataPath::targets(),
783 }),
784 },
785 None => Err(Error::MetadataNotFound {
786 path: MetadataPath::snapshot(),
787 version: MetadataVersion::None,
788 }),
789 }?
790 .clone();
791
792 if targets_description.version() <= tuf.trusted_targets().map(|t| t.version()).unwrap_or(0)
793 {
794 return Ok(false);
795 }
796
797 let version = if consistent_snapshot {
798 MetadataVersion::Number(targets_description.version())
799 } else {
800 MetadataVersion::None
801 };
802
803 let targets_path = MetadataPath::targets();
804
805 let targets_length = targets_description.length().or(config.max_targets_length);
810
811 let target_hashes = crypto::retain_supported_hashes(targets_description.hashes());
816
817 let raw_signed_targets = remote
818 .fetch_metadata(&targets_path, version, targets_length, target_hashes)
819 .await?;
820
821 if tuf.update_targets(start_time, &raw_signed_targets)? {
822 if let Some(local) = local {
829 local
830 .store_metadata(&targets_path, MetadataVersion::None, &raw_signed_targets)
831 .await?;
832 }
833
834 Ok(true)
835 } else {
836 Ok(false)
837 }
838 }
839
840 pub async fn fetch_target(
846 &mut self,
847 target: &TargetPath,
848 ) -> Result<impl AsyncRead + Send + Unpin + '_> {
849 self.fetch_target_with_start_time(target, &Utc::now()).await
850 }
851
852 pub async fn fetch_target_with_start_time(
858 &mut self,
859 target: &TargetPath,
860 start_time: &DateTime<Utc>,
861 ) -> Result<impl AsyncRead + Send + Unpin + '_> {
862 let target_description = self
863 .fetch_target_description_with_start_time(target, start_time)
864 .await?;
865
866 self.remote
868 .fetch_target(
869 self.tuf.trusted_root().consistent_snapshot(),
870 target,
871 target_description,
872 )
873 .await
874 }
875
876 pub async fn fetch_target_to_local(&mut self, target: &TargetPath) -> Result<()> {
882 self.fetch_target_to_local_with_start_time(target, &Utc::now())
883 .await
884 }
885
886 pub async fn fetch_target_to_local_with_start_time(
892 &mut self,
893 target: &TargetPath,
894 start_time: &DateTime<Utc>,
895 ) -> Result<()> {
896 let target_description = self
897 .fetch_target_description_with_start_time(target, start_time)
898 .await?;
899
900 let Client {
905 tuf, local, remote, ..
906 } = self;
907
908 let mut read = remote
910 .fetch_target(
911 tuf.trusted_root().consistent_snapshot(),
912 target,
913 target_description,
914 )
915 .await?;
916
917 local.store_target(target, &mut read).await
918 }
919
920 pub async fn fetch_target_description(
922 &mut self,
923 target: &TargetPath,
924 ) -> Result<TargetDescription> {
925 self.fetch_target_description_with_start_time(target, &Utc::now())
926 .await
927 }
928
929 pub async fn fetch_target_description_with_start_time(
931 &mut self,
932 target: &TargetPath,
933 start_time: &DateTime<Utc>,
934 ) -> Result<TargetDescription> {
935 let snapshot = self
936 .tuf
937 .trusted_snapshot()
938 .ok_or_else(|| Error::MetadataNotFound {
939 path: MetadataPath::snapshot(),
940 version: MetadataVersion::None,
941 })?
942 .clone();
943
944 let (_, target_description) = self
953 .lookup_target_description(start_time, false, 0, target, &snapshot, None)
954 .await;
955
956 target_description
957 }
958
959 async fn lookup_target_description(
960 &mut self,
961 start_time: &DateTime<Utc>,
962 default_terminate: bool,
963 current_depth: u32,
964 target: &TargetPath,
965 snapshot: &SnapshotMetadata,
966 targets: Option<(&Verified<TargetsMetadata>, MetadataPath)>,
967 ) -> (bool, Result<TargetDescription>) {
968 if current_depth > self.config.max_delegation_depth {
969 warn!(
970 "Walking the delegation graph would have exceeded the configured max depth: {}",
971 self.config.max_delegation_depth
972 );
973 return (
974 default_terminate,
975 Err(Error::TargetNotFound(target.clone())),
976 );
977 }
978
979 let (targets, targets_role) = match targets {
982 Some((t, role)) => (t.clone(), role),
983 None => match self.tuf.trusted_targets() {
984 Some(t) => (t.clone(), MetadataPath::targets()),
985 None => {
986 return (
987 default_terminate,
988 Err(Error::MetadataNotFound {
989 path: MetadataPath::targets(),
990 version: MetadataVersion::None,
991 }),
992 );
993 }
994 },
995 };
996
997 if let Some(t) = targets.targets().get(target) {
998 return (default_terminate, Ok(t.clone()));
999 }
1000
1001 for delegation in targets.delegations().roles() {
1002 if !delegation.paths().iter().any(|p| target.is_child(p)) {
1003 if delegation.terminating() {
1004 return (true, Err(Error::TargetNotFound(target.clone())));
1005 } else {
1006 continue;
1007 }
1008 }
1009
1010 let role_meta = match snapshot.meta().get(delegation.name()) {
1011 Some(m) => m,
1012 None if delegation.terminating() => {
1013 return (true, Err(Error::TargetNotFound(target.clone())));
1014 }
1015 None => {
1016 continue;
1017 }
1018 };
1019
1020 let version = if self.tuf.trusted_root().consistent_snapshot() {
1034 MetadataVersion::Number(role_meta.version())
1035 } else {
1036 MetadataVersion::None
1037 };
1038
1039 let role_length = role_meta.length().or(self.config.max_targets_length);
1040
1041 let role_hashes = crypto::retain_supported_hashes(role_meta.hashes());
1046
1047 let raw_signed_meta = match self
1048 .remote
1049 .fetch_metadata(delegation.name(), version, role_length, role_hashes)
1050 .await
1051 {
1052 Ok(m) => m,
1053 Err(e) => {
1054 warn!("Failed to fetch metadata {:?}: {:?}", delegation.name(), e);
1055 if delegation.terminating() {
1056 return (true, Err(e));
1057 } else {
1058 continue;
1059 }
1060 }
1061 };
1062
1063 match self.tuf.update_delegated_targets(
1064 start_time,
1065 &targets_role,
1066 delegation.name(),
1067 &raw_signed_meta,
1068 ) {
1069 Ok(_) => {
1070 match self
1077 .local
1078 .store_metadata(delegation.name(), MetadataVersion::None, &raw_signed_meta)
1079 .await
1080 {
1081 Ok(_) => (),
1082 Err(e) => {
1083 warn!(
1084 "Error storing metadata {:?} locally: {:?}",
1085 delegation.name(),
1086 e
1087 )
1088 }
1089 }
1090
1091 let meta = self
1092 .tuf
1093 .trusted_delegations()
1094 .get(delegation.name())
1095 .unwrap()
1096 .clone();
1097 let f: Pin<Box<dyn Future<Output = _>>> =
1098 Box::pin(self.lookup_target_description(
1099 start_time,
1100 delegation.terminating(),
1101 current_depth + 1,
1102 target,
1103 snapshot,
1104 Some((&meta, delegation.name().clone())),
1105 ));
1106 let (term, res) = f.await;
1107
1108 if term && res.is_err() {
1109 return (true, res);
1110 }
1111
1112 }
1114 Err(_) if !delegation.terminating() => continue,
1115 Err(e) => return (true, Err(e)),
1116 };
1117 }
1118
1119 (
1120 default_terminate,
1121 Err(Error::TargetNotFound(target.clone())),
1122 )
1123 }
1124}
1125
1126#[non_exhaustive]
1131#[derive(Debug)]
1132pub struct Parts<D, L, R>
1133where
1134 D: Pouf,
1135 L: RepositoryProvider<D> + RepositoryStorage<D>,
1136 R: RepositoryProvider<D>,
1137{
1138 pub config: Config,
1140
1141 pub database: Database<D>,
1143
1144 pub local: L,
1147
1148 pub remote: R,
1150}
1151
1152async fn fetch_metadata_from_local_or_else_remote<'a, D, L, R, M>(
1155 path: &'a MetadataPath,
1156 version: MetadataVersion,
1157 max_length: Option<usize>,
1158 hashes: Vec<(&'static HashAlgorithm, HashValue)>,
1159 local: &'a Repository<L, D>,
1160 remote: &'a Repository<R, D>,
1161) -> Result<(bool, RawSignedMetadata<D, M>)>
1162where
1163 D: Pouf,
1164 L: RepositoryProvider<D> + RepositoryStorage<D>,
1165 R: RepositoryProvider<D>,
1166 M: Metadata + 'static,
1167{
1168 match local
1169 .fetch_metadata(path, version, max_length, hashes.clone())
1170 .await
1171 {
1172 Ok(raw_meta) => Ok((false, raw_meta)),
1173 Err(Error::MetadataNotFound { .. }) => {
1174 let raw_meta = remote
1175 .fetch_metadata(path, version, max_length, hashes)
1176 .await?;
1177 Ok((true, raw_meta))
1178 }
1179 Err(err) => Err(err),
1180 }
1181}
1182
1183#[derive(Clone, Debug, PartialEq, Eq)]
1201pub struct Config {
1202 max_root_length: Option<usize>,
1203 max_timestamp_length: Option<usize>,
1204 max_snapshot_length: Option<usize>,
1205 max_targets_length: Option<usize>,
1206 max_delegation_depth: u32,
1207}
1208
1209impl Config {
1210 pub fn build() -> ConfigBuilder {
1212 ConfigBuilder::default()
1213 }
1214
1215 pub fn max_root_length(&self) -> &Option<usize> {
1217 &self.max_root_length
1218 }
1219
1220 pub fn max_timestamp_length(&self) -> &Option<usize> {
1222 &self.max_timestamp_length
1223 }
1224
1225 pub fn max_snapshot_length(&self) -> &Option<usize> {
1227 &self.max_snapshot_length
1228 }
1229
1230 pub fn max_targets_length(&self) -> &Option<usize> {
1232 &self.max_targets_length
1233 }
1234
1235 pub fn max_delegation_depth(&self) -> u32 {
1237 self.max_delegation_depth
1238 }
1239}
1240
1241impl Default for Config {
1242 fn default() -> Self {
1243 Config {
1244 max_root_length: Some(500 * 1024),
1245 max_timestamp_length: Some(16 * 1024),
1246 max_snapshot_length: Some(2000000),
1247 max_targets_length: Some(5000000),
1248 max_delegation_depth: 8,
1249 }
1250 }
1251}
1252
1253#[derive(Debug, Default, PartialEq, Eq)]
1255pub struct ConfigBuilder {
1256 cfg: Config,
1257}
1258
1259impl ConfigBuilder {
1260 pub fn finish(self) -> Result<Config> {
1262 Ok(self.cfg)
1263 }
1264
1265 pub fn max_root_length(mut self, max: Option<usize>) -> Self {
1267 self.cfg.max_root_length = max;
1268 self
1269 }
1270
1271 pub fn max_timestamp_length(mut self, max: Option<usize>) -> Self {
1273 self.cfg.max_timestamp_length = max;
1274 self
1275 }
1276
1277 pub fn max_snapshot_length(mut self, max: Option<usize>) -> Self {
1279 self.cfg.max_snapshot_length = max;
1280 self
1281 }
1282
1283 pub fn max_targets_length(mut self, max: Option<usize>) -> Self {
1285 self.cfg.max_targets_length = max;
1286 self
1287 }
1288
1289 pub fn max_delegation_depth(mut self, max: u32) -> Self {
1291 self.cfg.max_delegation_depth = max;
1292 self
1293 }
1294}
1295
1296#[cfg(test)]
1297mod test {
1298 use super::*;
1299 use crate::crypto::{Ed25519PrivateKey, HashAlgorithm, PrivateKey};
1300 use crate::metadata::{
1301 MetadataDescription, MetadataPath, MetadataVersion, RootMetadataBuilder,
1302 SnapshotMetadataBuilder, TargetsMetadataBuilder, TimestampMetadataBuilder,
1303 };
1304 use crate::pouf::Pouf1;
1305 use crate::repo_builder::RepoBuilder;
1306 use crate::repository::{
1307 fetch_metadata_to_string, EphemeralRepository, ErrorRepository, Track, TrackRepository,
1308 };
1309 use assert_matches::assert_matches;
1310 use chrono::prelude::*;
1311 use futures_executor::block_on;
1312 use maplit::hashmap;
1313 use pretty_assertions::assert_eq;
1314 use serde_json::json;
1315 use std::collections::HashMap;
1316 use std::iter::once;
1317 use std::sync::LazyLock;
1318
1319 static KEYS: LazyLock<Vec<Ed25519PrivateKey>> = LazyLock::new(|| {
1320 let keys: &[&[u8]] = &[
1321 include_bytes!("../tests/ed25519/ed25519-1.pk8.der"),
1322 include_bytes!("../tests/ed25519/ed25519-2.pk8.der"),
1323 include_bytes!("../tests/ed25519/ed25519-3.pk8.der"),
1324 include_bytes!("../tests/ed25519/ed25519-4.pk8.der"),
1325 include_bytes!("../tests/ed25519/ed25519-5.pk8.der"),
1326 include_bytes!("../tests/ed25519/ed25519-6.pk8.der"),
1327 ];
1328 keys.iter()
1329 .map(|b| Ed25519PrivateKey::from_pkcs8(b).unwrap())
1330 .collect()
1331 });
1332
1333 #[allow(clippy::enum_variant_names)]
1334 enum ConstructorMode {
1335 WithTrustedLocal,
1336 WithTrustedRoot,
1337 WithTrustedRootKeys,
1338 FromDatabase,
1339 }
1340
1341 #[test]
1342 fn client_constructors_err_with_not_found() {
1343 block_on(async {
1344 let mut local = EphemeralRepository::<Pouf1>::new();
1345 let remote = EphemeralRepository::<Pouf1>::new();
1346
1347 let private_key =
1348 Ed25519PrivateKey::from_pkcs8(&Ed25519PrivateKey::pkcs8().unwrap()).unwrap();
1349 let public_key = private_key.public().clone();
1350
1351 assert_matches!(
1352 Client::with_trusted_local(Config::default(), &mut local, &remote).await,
1353 Err(Error::MetadataNotFound { path, version })
1354 if path == MetadataPath::root() && version == MetadataVersion::Number(1)
1355 );
1356
1357 assert_matches!(
1358 Client::with_trusted_root_keys(
1359 Config::default(),
1360 MetadataVersion::Number(1),
1361 1,
1362 once(&public_key),
1363 local,
1364 &remote,
1365 )
1366 .await,
1367 Err(Error::MetadataNotFound { path, version })
1368 if path == MetadataPath::root() && version == MetadataVersion::Number(1)
1369 );
1370 })
1371 }
1372
1373 #[test]
1374 fn client_constructors_err_with_invalid_keys() {
1375 block_on(async {
1376 let mut remote = EphemeralRepository::<Pouf1>::new();
1377
1378 let good_private_key = &KEYS[0];
1379 let bad_private_key = &KEYS[1];
1380
1381 let _ = RepoBuilder::create(&mut remote)
1382 .trusted_root_keys(&[good_private_key])
1383 .trusted_targets_keys(&[good_private_key])
1384 .trusted_snapshot_keys(&[good_private_key])
1385 .trusted_timestamp_keys(&[good_private_key])
1386 .commit()
1387 .await
1388 .unwrap();
1389
1390 assert_matches!(
1391 Client::with_trusted_root_keys(
1392 Config::default(),
1393 MetadataVersion::Number(1),
1394 1,
1395 once(bad_private_key.public()),
1396 EphemeralRepository::new(),
1397 &remote,
1398 )
1399 .await,
1400 Err(Error::MetadataMissingSignatures { role, number_of_valid_signatures: 0, threshold: 1 })
1401 if role == MetadataPath::root()
1402 );
1403 })
1404 }
1405
1406 #[test]
1407 fn with_trusted_local_loads_metadata_from_local_repo() {
1408 block_on(constructors_load_metadata_from_local_repo(
1409 ConstructorMode::WithTrustedLocal,
1410 ))
1411 }
1412
1413 #[test]
1414 fn with_trusted_root_loads_metadata_from_local_repo() {
1415 block_on(constructors_load_metadata_from_local_repo(
1416 ConstructorMode::WithTrustedRoot,
1417 ))
1418 }
1419
1420 #[test]
1421 fn with_trusted_root_keys_loads_metadata_from_local_repo() {
1422 block_on(constructors_load_metadata_from_local_repo(
1423 ConstructorMode::WithTrustedRootKeys,
1424 ))
1425 }
1426
1427 #[test]
1428 fn from_database_loads_metadata_from_local_repo() {
1429 block_on(constructors_load_metadata_from_local_repo(
1430 ConstructorMode::FromDatabase,
1431 ))
1432 }
1433
1434 async fn constructors_load_metadata_from_local_repo(constructor_mode: ConstructorMode) {
1435 let mut local = EphemeralRepository::<Pouf1>::new();
1437 let metadata1 = RepoBuilder::create(&mut local)
1438 .current_time(Utc.timestamp_opt(0, 0).unwrap())
1439 .trusted_root_keys(&[&KEYS[0]])
1440 .trusted_targets_keys(&[&KEYS[0]])
1441 .trusted_snapshot_keys(&[&KEYS[0]])
1442 .trusted_timestamp_keys(&[&KEYS[0]])
1443 .stage_root_with_builder(|bld| bld.consistent_snapshot(true))
1444 .unwrap()
1445 .commit()
1446 .await
1447 .unwrap();
1448
1449 let mut remote = EphemeralRepository::<Pouf1>::new();
1451 let metadata2 = RepoBuilder::create(&mut remote)
1452 .trusted_root_keys(&[&KEYS[0]])
1453 .trusted_targets_keys(&[&KEYS[0]])
1454 .trusted_snapshot_keys(&[&KEYS[0]])
1455 .trusted_timestamp_keys(&[&KEYS[0]])
1456 .stage_root_with_builder(|bld| bld.version(2).consistent_snapshot(true))
1457 .unwrap()
1458 .stage_targets_with_builder(|bld| bld.version(2))
1459 .unwrap()
1460 .stage_snapshot_with_builder(|bld| bld.version(2))
1461 .unwrap()
1462 .stage_timestamp_with_builder(|bld| bld.version(2))
1463 .unwrap()
1464 .commit()
1465 .await
1466 .unwrap();
1467
1468 let track_local = TrackRepository::new(local);
1470 let track_remote = TrackRepository::new(remote);
1471
1472 let mut client = match constructor_mode {
1475 ConstructorMode::WithTrustedLocal => {
1476 Client::with_trusted_local(Config::default(), track_local, track_remote)
1477 .await
1478 .unwrap()
1479 }
1480 ConstructorMode::WithTrustedRoot => Client::with_trusted_root(
1481 Config::default(),
1482 metadata1.root().unwrap(),
1483 track_local,
1484 track_remote,
1485 )
1486 .await
1487 .unwrap(),
1488 ConstructorMode::WithTrustedRootKeys => Client::with_trusted_root_keys(
1489 Config::default(),
1490 MetadataVersion::Number(1),
1491 1,
1492 once(&KEYS[0].public().clone()),
1493 track_local,
1494 track_remote,
1495 )
1496 .await
1497 .unwrap(),
1498 ConstructorMode::FromDatabase => Client::from_database(
1499 Config::default(),
1500 Database::from_trusted_root(metadata1.root().unwrap()).unwrap(),
1501 track_local,
1502 track_remote,
1503 ),
1504 };
1505
1506 assert_eq!(client.tuf.trusted_root().version(), 1);
1507 assert_eq!(client.remote_repo().take_tracks(), vec![]);
1508
1509 match constructor_mode {
1514 ConstructorMode::WithTrustedLocal => {
1515 assert_eq!(
1516 client.local_repo().take_tracks(),
1517 vec![
1518 Track::fetch_meta_found(
1519 MetadataVersion::Number(1),
1520 metadata1.root().unwrap()
1521 ),
1522 Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(2)),
1523 ],
1524 );
1525 }
1526 ConstructorMode::WithTrustedRoot => {
1527 assert_eq!(
1528 client.local_repo().take_tracks(),
1529 vec![Track::FetchErr(
1530 MetadataPath::root(),
1531 MetadataVersion::Number(2)
1532 )],
1533 );
1534 }
1535 ConstructorMode::WithTrustedRootKeys => {
1536 assert_eq!(
1537 client.local_repo().take_tracks(),
1538 vec![
1539 Track::fetch_meta_found(
1540 MetadataVersion::Number(1),
1541 metadata1.root().unwrap()
1542 ),
1543 Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(2)),
1544 ],
1545 );
1546 }
1547 ConstructorMode::FromDatabase => {
1548 assert_eq!(client.local_repo().take_tracks(), vec![],);
1549 }
1550 };
1551
1552 assert_matches!(client.update().await, Ok(true));
1553 assert_eq!(client.tuf.trusted_root().version(), 2);
1554
1555 assert_eq!(
1558 client.remote_repo().take_tracks(),
1559 vec![
1560 Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.root().unwrap()),
1561 Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(3)),
1562 Track::fetch_meta_found(MetadataVersion::None, metadata2.timestamp().unwrap()),
1563 Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.snapshot().unwrap()),
1564 Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.targets().unwrap()),
1565 ],
1566 );
1567 assert_eq!(
1568 client.local_repo().take_tracks(),
1569 vec![
1570 Track::store_meta(MetadataVersion::None, metadata2.root().unwrap()),
1571 Track::store_meta(MetadataVersion::Number(2), metadata2.root().unwrap()),
1572 Track::store_meta(MetadataVersion::None, metadata2.timestamp().unwrap()),
1573 Track::store_meta(MetadataVersion::None, metadata2.snapshot().unwrap()),
1574 Track::store_meta(MetadataVersion::None, metadata2.targets().unwrap()),
1575 ],
1576 );
1577
1578 assert_matches!(client.update().await, Ok(false));
1580 assert_eq!(client.tuf.trusted_root().version(), 2);
1581
1582 assert_eq!(
1584 client.remote_repo().take_tracks(),
1585 vec![
1586 Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(3)),
1587 Track::fetch_meta_found(MetadataVersion::None, metadata2.timestamp().unwrap()),
1588 ]
1589 );
1590 assert_eq!(client.local_repo().take_tracks(), vec![]);
1591 }
1592
1593 #[test]
1594 fn constructor_succeeds_with_missing_metadata() {
1595 block_on(async {
1596 let mut local = EphemeralRepository::<Pouf1>::new();
1597 let remote = EphemeralRepository::<Pouf1>::new();
1598
1599 let metadata1 = RepoBuilder::create(&mut local)
1601 .trusted_root_keys(&[&KEYS[0]])
1602 .trusted_targets_keys(&[&KEYS[0]])
1603 .trusted_snapshot_keys(&[&KEYS[0]])
1604 .trusted_timestamp_keys(&[&KEYS[0]])
1605 .stage_root_with_builder(|bld| bld.consistent_snapshot(true))
1606 .unwrap()
1607 .skip_targets()
1608 .skip_snapshot()
1609 .skip_timestamp()
1610 .commit()
1611 .await
1612 .unwrap();
1613
1614 let track_local = TrackRepository::new(local);
1615 let track_remote = TrackRepository::new(remote);
1616
1617 let client = Client::with_trusted_root(
1619 Config::default(),
1620 metadata1.root().unwrap(),
1621 track_local,
1622 track_remote,
1623 )
1624 .await
1625 .unwrap();
1626
1627 assert_eq!(client.tuf.trusted_root().version(), 1);
1628
1629 assert_eq!(client.remote_repo().take_tracks(), vec![]);
1631
1632 assert_eq!(
1635 client.local_repo().take_tracks(),
1636 vec![
1637 Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(2)),
1638 Track::FetchErr(MetadataPath::timestamp(), MetadataVersion::None)
1639 ],
1640 );
1641
1642 let mut parts = client.into_parts();
1644 let metadata2 = RepoBuilder::create(parts.remote.as_inner_mut())
1645 .trusted_root_keys(&[&KEYS[0]])
1646 .trusted_targets_keys(&[&KEYS[0]])
1647 .trusted_snapshot_keys(&[&KEYS[0]])
1648 .trusted_timestamp_keys(&[&KEYS[0]])
1649 .stage_root_with_builder(|bld| bld.version(2).consistent_snapshot(true))
1650 .unwrap()
1651 .stage_targets_with_builder(|bld| bld.version(2))
1652 .unwrap()
1653 .stage_snapshot_with_builder(|bld| bld.version(2))
1654 .unwrap()
1655 .stage_timestamp_with_builder(|bld| bld.version(2))
1656 .unwrap()
1657 .commit()
1658 .await
1659 .unwrap();
1660
1661 let mut client = Client::from_parts(parts);
1662 assert_matches!(client.update().await, Ok(true));
1663 assert_eq!(client.tuf.trusted_root().version(), 2);
1664
1665 assert_eq!(
1667 client.remote_repo().take_tracks(),
1668 vec![
1669 Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.root().unwrap()),
1670 Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(3)),
1671 Track::fetch_meta_found(MetadataVersion::None, metadata2.timestamp().unwrap()),
1672 Track::fetch_meta_found(
1673 MetadataVersion::Number(2),
1674 metadata2.snapshot().unwrap()
1675 ),
1676 Track::fetch_meta_found(
1677 MetadataVersion::Number(2),
1678 metadata2.targets().unwrap()
1679 ),
1680 ],
1681 );
1682 assert_eq!(
1683 client.local_repo().take_tracks(),
1684 vec![
1685 Track::store_meta(MetadataVersion::None, metadata2.root().unwrap()),
1686 Track::store_meta(MetadataVersion::Number(2), metadata2.root().unwrap()),
1687 Track::store_meta(MetadataVersion::None, metadata2.timestamp().unwrap()),
1688 Track::store_meta(MetadataVersion::None, metadata2.snapshot().unwrap()),
1689 Track::store_meta(MetadataVersion::None, metadata2.targets().unwrap()),
1690 ],
1691 );
1692 })
1693 }
1694
1695 #[test]
1696 fn constructor_succeeds_with_expired_metadata() {
1697 block_on(async {
1698 let mut local = EphemeralRepository::<Pouf1>::new();
1699 let remote = EphemeralRepository::<Pouf1>::new();
1700
1701 let metadata1 = RepoBuilder::create(&mut local)
1703 .current_time(Utc.timestamp_opt(0, 0).unwrap())
1704 .trusted_root_keys(&[&KEYS[0]])
1705 .trusted_targets_keys(&[&KEYS[0]])
1706 .trusted_snapshot_keys(&[&KEYS[0]])
1707 .trusted_timestamp_keys(&[&KEYS[0]])
1708 .stage_root_with_builder(|bld| bld.version(1).consistent_snapshot(true))
1709 .unwrap()
1710 .commit()
1711 .await
1712 .unwrap();
1713
1714 let metadata2 = RepoBuilder::create(&mut local)
1715 .current_time(Utc.timestamp_opt(0, 0).unwrap())
1716 .trusted_root_keys(&[&KEYS[0]])
1717 .trusted_targets_keys(&[&KEYS[0]])
1718 .trusted_snapshot_keys(&[&KEYS[0]])
1719 .trusted_timestamp_keys(&[&KEYS[0]])
1720 .stage_root_with_builder(|bld| bld.version(2).consistent_snapshot(true))
1721 .unwrap()
1722 .stage_targets_with_builder(|bld| bld.version(2))
1723 .unwrap()
1724 .stage_snapshot_with_builder(|bld| bld.version(2))
1725 .unwrap()
1726 .stage_timestamp_with_builder(|bld| bld.version(2))
1727 .unwrap()
1728 .commit()
1729 .await
1730 .unwrap();
1731
1732 let track_local = TrackRepository::new(local);
1734 let track_remote = TrackRepository::new(remote);
1735
1736 let client = Client::with_trusted_root(
1737 Config::default(),
1738 metadata1.root().unwrap(),
1739 track_local,
1740 track_remote,
1741 )
1742 .await
1743 .unwrap();
1744
1745 assert_eq!(client.tuf.trusted_root().version(), 2);
1746
1747 assert_eq!(client.remote_repo().take_tracks(), vec![]);
1749
1750 assert_eq!(
1753 client.local_repo().take_tracks(),
1754 vec![
1755 Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.root().unwrap()),
1756 Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(3))
1757 ],
1758 );
1759
1760 let mut parts = client.into_parts();
1762 let _metadata3 = RepoBuilder::create(&mut parts.remote)
1763 .trusted_root_keys(&[&KEYS[0]])
1764 .trusted_targets_keys(&[&KEYS[0]])
1765 .trusted_snapshot_keys(&[&KEYS[0]])
1766 .trusted_timestamp_keys(&[&KEYS[0]])
1767 .stage_root_with_builder(|bld| {
1768 bld.version(3)
1769 .consistent_snapshot(true)
1770 .expires(Utc.with_ymd_and_hms(2038, 1, 1, 0, 0, 0).unwrap())
1771 })
1772 .unwrap()
1773 .stage_targets_with_builder(|bld| bld.version(2))
1774 .unwrap()
1775 .stage_snapshot_with_builder(|bld| bld.version(2))
1776 .unwrap()
1777 .stage_timestamp_with_builder(|bld| bld.version(2))
1778 .unwrap()
1779 .commit()
1780 .await
1781 .unwrap();
1782
1783 let mut client = Client::from_parts(parts);
1784 assert_matches!(client.update().await, Ok(true));
1785 assert_eq!(client.tuf.trusted_root().version(), 3);
1786 })
1787 }
1788
1789 #[test]
1790 fn constructor_succeeds_with_malformed_metadata() {
1791 block_on(async {
1792 let local = EphemeralRepository::<Pouf1>::new();
1794 let junk_timestamp = "junk timestamp";
1795
1796 local
1797 .store_metadata(
1798 &MetadataPath::timestamp(),
1799 MetadataVersion::None,
1800 &mut junk_timestamp.as_bytes(),
1801 )
1802 .await
1803 .unwrap();
1804
1805 let mut remote = EphemeralRepository::<Pouf1>::new();
1807 let metadata1 = RepoBuilder::create(&mut remote)
1808 .trusted_root_keys(&[&KEYS[0]])
1809 .trusted_targets_keys(&[&KEYS[0]])
1810 .trusted_snapshot_keys(&[&KEYS[0]])
1811 .trusted_timestamp_keys(&[&KEYS[0]])
1812 .commit()
1813 .await
1814 .unwrap();
1815
1816 let track_local = TrackRepository::new(local);
1818 let track_remote = TrackRepository::new(remote);
1819
1820 let mut client = Client::with_trusted_root(
1821 Config::default(),
1822 metadata1.root().unwrap(),
1823 track_local,
1824 track_remote,
1825 )
1826 .await
1827 .unwrap();
1828
1829 assert_eq!(client.tuf.trusted_root().version(), 1);
1830
1831 assert_eq!(client.remote_repo().take_tracks(), vec![]);
1833
1834 assert_eq!(
1837 client.local_repo().take_tracks(),
1838 vec![
1839 Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(2)),
1840 Track::FetchFound {
1841 path: MetadataPath::timestamp(),
1842 version: MetadataVersion::None,
1843 metadata: junk_timestamp.into(),
1844 },
1845 ],
1846 );
1847
1848 assert_matches!(client.update().await, Ok(true));
1850 })
1851 }
1852
1853 #[test]
1854 fn root_chain_update_consistent_snapshot_false() {
1855 block_on(root_chain_update(false))
1856 }
1857
1858 #[test]
1859 fn root_chain_update_consistent_snapshot_true() {
1860 block_on(root_chain_update(true))
1861 }
1862
1863 async fn root_chain_update(consistent_snapshot: bool) {
1864 let mut repo = EphemeralRepository::<Pouf1>::new();
1865
1866 let metadata1 = RepoBuilder::create(&mut repo)
1869 .trusted_root_keys(&[&KEYS[0]])
1870 .signing_targets_keys(&[&KEYS[1], &KEYS[2]])
1871 .trusted_targets_keys(&[&KEYS[0]])
1872 .signing_snapshot_keys(&[&KEYS[1], &KEYS[2]])
1873 .trusted_snapshot_keys(&[&KEYS[0]])
1874 .signing_timestamp_keys(&[&KEYS[1], &KEYS[2]])
1875 .trusted_timestamp_keys(&[&KEYS[0]])
1876 .stage_root_with_builder(|bld| bld.consistent_snapshot(consistent_snapshot))
1877 .unwrap()
1878 .commit()
1879 .await
1880 .unwrap();
1881
1882 let root_path = MetadataPath::root();
1883 let timestamp_path = MetadataPath::timestamp();
1884
1885 let targets_version;
1886 let snapshot_version;
1887 if consistent_snapshot {
1888 targets_version = MetadataVersion::Number(1);
1889 snapshot_version = MetadataVersion::Number(1);
1890 } else {
1891 targets_version = MetadataVersion::None;
1892 snapshot_version = MetadataVersion::None;
1893 };
1894
1895 let track_local = TrackRepository::new(EphemeralRepository::new());
1897 let track_remote = TrackRepository::new(repo);
1898
1899 let mut client = Client::with_trusted_root_keys(
1900 Config::default(),
1901 MetadataVersion::Number(1),
1902 1,
1903 once(&KEYS[0].public().clone()),
1904 track_local,
1905 track_remote,
1906 )
1907 .await
1908 .unwrap();
1909
1910 assert_eq!(
1912 client.remote_repo().take_tracks(),
1913 vec![Track::fetch_found(
1914 &root_path,
1915 MetadataVersion::Number(1),
1916 metadata1.root().unwrap().as_bytes()
1917 ),]
1918 );
1919 assert_eq!(
1920 client.local_repo().take_tracks(),
1921 vec![
1922 Track::FetchErr(root_path.clone(), MetadataVersion::Number(1)),
1923 Track::store_meta(MetadataVersion::Number(1), metadata1.root().unwrap()),
1924 Track::FetchErr(root_path.clone(), MetadataVersion::Number(2)),
1925 Track::FetchErr(timestamp_path.clone(), MetadataVersion::None),
1926 ]
1927 );
1928
1929 assert_matches!(client.update().await, Ok(true));
1930 assert_eq!(client.tuf.trusted_root().version(), 1);
1931
1932 assert_eq!(
1934 client.remote_repo().take_tracks(),
1935 vec![
1936 Track::FetchErr(root_path.clone(), MetadataVersion::Number(2)),
1937 Track::fetch_meta_found(MetadataVersion::None, metadata1.timestamp().unwrap()),
1938 Track::fetch_meta_found(snapshot_version, metadata1.snapshot().unwrap()),
1939 Track::fetch_meta_found(targets_version, metadata1.targets().unwrap()),
1940 ]
1941 );
1942 assert_eq!(
1943 client.local_repo().take_tracks(),
1944 vec![
1945 Track::store_meta(MetadataVersion::None, metadata1.timestamp().unwrap()),
1946 Track::store_meta(MetadataVersion::None, metadata1.snapshot().unwrap()),
1947 Track::store_meta(MetadataVersion::None, metadata1.targets().unwrap()),
1948 ],
1949 );
1950
1951 assert_matches!(client.update().await, Ok(false));
1953 assert_eq!(client.tuf.trusted_root().version(), 1);
1954
1955 assert_eq!(
1957 client.remote_repo().take_tracks(),
1958 vec![
1959 Track::FetchErr(root_path.clone(), MetadataVersion::Number(2)),
1960 Track::fetch_meta_found(MetadataVersion::None, metadata1.timestamp().unwrap()),
1961 ]
1962 );
1963 assert_eq!(client.local_repo().take_tracks(), vec![]);
1964
1965 let mut parts = client.into_parts();
1973 let metadata2 = RepoBuilder::create(parts.remote.as_inner_mut())
1974 .signing_root_keys(&[&KEYS[0]])
1975 .trusted_root_keys(&[&KEYS[1]])
1976 .trusted_targets_keys(&[&KEYS[1]])
1977 .trusted_snapshot_keys(&[&KEYS[1]])
1978 .trusted_timestamp_keys(&[&KEYS[1]])
1979 .stage_root_with_builder(|bld| bld.version(2).consistent_snapshot(consistent_snapshot))
1980 .unwrap()
1981 .skip_targets()
1982 .skip_snapshot()
1983 .skip_timestamp()
1984 .commit()
1985 .await
1986 .unwrap();
1987
1988 let metadata3 = RepoBuilder::create(parts.remote.as_inner_mut())
1990 .signing_root_keys(&[&KEYS[1]])
1991 .trusted_root_keys(&[&KEYS[2]])
1992 .trusted_targets_keys(&[&KEYS[2]])
1993 .trusted_snapshot_keys(&[&KEYS[2]])
1994 .trusted_timestamp_keys(&[&KEYS[2]])
1995 .stage_root_with_builder(|bld| bld.version(3).consistent_snapshot(consistent_snapshot))
1996 .unwrap()
1997 .skip_targets()
1998 .skip_snapshot()
1999 .skip_timestamp()
2000 .commit()
2001 .await
2002 .unwrap();
2003
2004 let mut client = Client::from_parts(parts);
2007 assert_matches!(client.update().await, Ok(true));
2008 assert_eq!(client.tuf.trusted_root().version(), 3);
2009
2010 assert_eq!(
2014 client.remote_repo().take_tracks(),
2015 vec![
2016 Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.root().unwrap()),
2017 Track::fetch_meta_found(MetadataVersion::Number(3), metadata3.root().unwrap()),
2018 Track::FetchErr(root_path.clone(), MetadataVersion::Number(4)),
2019 Track::fetch_meta_found(MetadataVersion::None, metadata1.timestamp().unwrap()),
2020 Track::fetch_meta_found(snapshot_version, metadata1.snapshot().unwrap()),
2021 Track::fetch_meta_found(targets_version, metadata1.targets().unwrap()),
2022 ]
2023 );
2024 assert_eq!(
2025 client.local_repo().take_tracks(),
2026 vec![
2027 Track::store_meta(MetadataVersion::None, metadata2.root().unwrap()),
2028 Track::store_meta(MetadataVersion::Number(2), metadata2.root().unwrap()),
2029 Track::store_meta(MetadataVersion::None, metadata3.root().unwrap()),
2030 Track::store_meta(MetadataVersion::Number(3), metadata3.root().unwrap()),
2031 Track::store_meta(MetadataVersion::None, metadata1.timestamp().unwrap()),
2032 Track::store_meta(MetadataVersion::None, metadata1.snapshot().unwrap()),
2033 Track::store_meta(MetadataVersion::None, metadata1.targets().unwrap()),
2034 ],
2035 );
2036 }
2037
2038 #[test]
2039 fn test_fetch_target_description_standard() {
2040 block_on(test_fetch_target_description(
2041 "standard/metadata".to_string(),
2042 TargetDescription::from_slice(
2043 "target with no custom metadata".as_bytes(),
2044 &[HashAlgorithm::Sha256],
2045 )
2046 .unwrap(),
2047 ));
2048 }
2049
2050 #[test]
2051 fn test_fetch_target_description_custom_empty() {
2052 block_on(test_fetch_target_description(
2053 "custom-empty".to_string(),
2054 TargetDescription::from_slice_with_custom(
2055 "target with empty custom metadata".as_bytes(),
2056 &[HashAlgorithm::Sha256],
2057 hashmap!(),
2058 )
2059 .unwrap(),
2060 ));
2061 }
2062
2063 #[test]
2064 fn test_fetch_target_description_custom() {
2065 block_on(test_fetch_target_description(
2066 "custom/metadata".to_string(),
2067 TargetDescription::from_slice_with_custom(
2068 "target with lots of custom metadata".as_bytes(),
2069 &[HashAlgorithm::Sha256],
2070 hashmap!(
2071 "string".to_string() => json!("string"),
2072 "bool".to_string() => json!(true),
2073 "int".to_string() => json!(42),
2074 "object".to_string() => json!({
2075 "string": json!("string"),
2076 "bool": json!(true),
2077 "int": json!(42),
2078 }),
2079 "array".to_string() => json!([1, 2, 3]),
2080 ),
2081 )
2082 .unwrap(),
2083 ));
2084 }
2085
2086 async fn test_fetch_target_description(path: String, expected_description: TargetDescription) {
2087 let mut remote = EphemeralRepository::<Pouf1>::new();
2089
2090 let metadata = RepoBuilder::create(&mut remote)
2091 .trusted_root_keys(&[&KEYS[0]])
2092 .trusted_targets_keys(&[&KEYS[0]])
2093 .trusted_snapshot_keys(&[&KEYS[0]])
2094 .trusted_timestamp_keys(&[&KEYS[0]])
2095 .stage_root()
2096 .unwrap()
2097 .stage_targets_with_builder(|bld| {
2098 bld.insert_target_description(
2099 TargetPath::new(path.clone()).unwrap(),
2100 expected_description.clone(),
2101 )
2102 })
2103 .unwrap()
2104 .commit()
2105 .await
2106 .unwrap();
2107
2108 let mut client = Client::with_trusted_root(
2110 Config::default(),
2111 metadata.root().unwrap(),
2112 EphemeralRepository::new(),
2113 remote,
2114 )
2115 .await
2116 .unwrap();
2117
2118 assert_matches!(client.update().await, Ok(true));
2119
2120 let description = client
2122 .fetch_target_description(&TargetPath::new(path).unwrap())
2123 .await
2124 .unwrap();
2125
2126 assert_eq!(description, expected_description);
2127 }
2128
2129 #[test]
2130 fn update_eventually_succeeds_if_cannot_write_to_repo() {
2131 block_on(async {
2132 let mut remote = EphemeralRepository::<Pouf1>::new();
2133
2134 let _ = RepoBuilder::create(&mut remote)
2136 .trusted_root_keys(&[&KEYS[0]])
2137 .trusted_targets_keys(&[&KEYS[0]])
2138 .trusted_snapshot_keys(&[&KEYS[0]])
2139 .trusted_timestamp_keys(&[&KEYS[0]])
2140 .commit()
2141 .await
2142 .unwrap();
2143
2144 let local = ErrorRepository::new(EphemeralRepository::new());
2146 let mut client = Client::with_trusted_root_keys(
2147 Config::default(),
2148 MetadataVersion::Number(1),
2149 1,
2150 once(&KEYS[0].public().clone()),
2151 local,
2152 remote,
2153 )
2154 .await
2155 .unwrap();
2156
2157 assert_matches!(client.update().await, Ok(true));
2159
2160 let mut parts = client.into_parts();
2162 assert_eq!(parts.database.trusted_root().version(), 1);
2163 assert_eq!(parts.database.trusted_timestamp().unwrap().version(), 1);
2164 assert_eq!(parts.database.trusted_snapshot().unwrap().version(), 1);
2165 assert_eq!(parts.database.trusted_targets().unwrap().version(), 1);
2166
2167 let _ = RepoBuilder::create(&mut parts.remote)
2169 .trusted_root_keys(&[&KEYS[0]])
2170 .trusted_targets_keys(&[&KEYS[0]])
2171 .trusted_snapshot_keys(&[&KEYS[0]])
2172 .trusted_timestamp_keys(&[&KEYS[0]])
2173 .stage_root_with_builder(|bld| bld.version(2))
2174 .unwrap()
2175 .stage_targets_with_builder(|bld| bld.version(2))
2176 .unwrap()
2177 .stage_snapshot_with_builder(|bld| bld.version(2))
2178 .unwrap()
2179 .stage_timestamp_with_builder(|bld| bld.version(2))
2180 .unwrap()
2181 .commit()
2182 .await
2183 .unwrap();
2184
2185 parts.local.fail_metadata_stores(true);
2187
2188 let mut client = Client::from_parts(parts);
2190 assert_matches!(client.update().await, Err(Error::Encoding(_)));
2191
2192 assert_eq!(client.database().trusted_root().version(), 2);
2195 assert_eq!(client.database().trusted_timestamp(), None);
2196 assert_eq!(client.database().trusted_snapshot(), None);
2197 assert_eq!(client.database().trusted_targets(), None);
2198
2199 assert_matches!(client.update().await, Err(Error::Encoding(_)));
2202 assert_eq!(client.database().trusted_root().version(), 2);
2203 assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2204 assert_eq!(client.database().trusted_snapshot(), None);
2205 assert_eq!(client.database().trusted_targets(), None);
2206
2207 assert_matches!(client.update().await, Err(Error::Encoding(_)));
2208 assert_eq!(client.database().trusted_root().version(), 2);
2209 assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2210 assert_eq!(client.database().trusted_snapshot().unwrap().version(), 2);
2211 assert_eq!(client.database().trusted_targets(), None);
2212
2213 assert_matches!(client.update().await, Err(Error::Encoding(_)));
2214 assert_eq!(client.database().trusted_root().version(), 2);
2215 assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2216 assert_eq!(client.database().trusted_snapshot().unwrap().version(), 2);
2217 assert_eq!(client.database().trusted_targets().unwrap().version(), 2);
2218
2219 assert_matches!(client.update().await, Ok(false));
2220 assert_eq!(client.database().trusted_root().version(), 2);
2221 assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2222 assert_eq!(client.database().trusted_snapshot().unwrap().version(), 2);
2223 assert_eq!(client.database().trusted_targets().unwrap().version(), 2);
2224 });
2225 }
2226
2227 #[test]
2228 fn test_local_and_remote_repo_methods() {
2229 block_on(async {
2230 let local = EphemeralRepository::<Pouf1>::new();
2231 let mut remote = EphemeralRepository::<Pouf1>::new();
2232
2233 let metadata1 = RepoBuilder::create(&mut remote)
2234 .trusted_root_keys(&[&KEYS[0]])
2235 .trusted_targets_keys(&[&KEYS[0]])
2236 .trusted_snapshot_keys(&[&KEYS[0]])
2237 .trusted_timestamp_keys(&[&KEYS[0]])
2238 .stage_root()
2239 .unwrap()
2240 .stage_targets()
2241 .unwrap()
2242 .stage_snapshot()
2243 .unwrap()
2244 .stage_timestamp_with_builder(|bld| bld.version(1))
2245 .unwrap()
2246 .commit()
2247 .await
2248 .unwrap();
2249
2250 let mut client = Client::with_trusted_root(
2251 Config::default(),
2252 metadata1.root().unwrap(),
2253 local,
2254 remote,
2255 )
2256 .await
2257 .unwrap();
2258
2259 client.update().await.unwrap();
2260
2261 let metadata2 = RepoBuilder::from_database(
2263 &mut EphemeralRepository::<Pouf1>::new(),
2264 client.database(),
2265 )
2266 .trusted_root_keys(&[&KEYS[0]])
2267 .trusted_targets_keys(&[&KEYS[0]])
2268 .trusted_snapshot_keys(&[&KEYS[0]])
2269 .trusted_timestamp_keys(&[&KEYS[0]])
2270 .skip_root()
2271 .skip_targets()
2272 .skip_snapshot()
2273 .stage_timestamp_with_builder(|bld| bld.version(2))
2274 .unwrap()
2275 .commit()
2276 .await
2277 .unwrap();
2278
2279 client
2281 .local_repo_mut()
2282 .store_metadata(
2283 &MetadataPath::timestamp(),
2284 MetadataVersion::None,
2285 &mut metadata2.timestamp().unwrap().as_bytes(),
2286 )
2287 .await
2288 .unwrap();
2289
2290 client
2291 .remote_repo_mut()
2292 .store_metadata(
2293 &MetadataPath::timestamp(),
2294 MetadataVersion::None,
2295 &mut metadata2.timestamp().unwrap().as_bytes(),
2296 )
2297 .await
2298 .unwrap();
2299
2300 let timestamp2 =
2302 String::from_utf8(metadata2.timestamp().unwrap().as_bytes().to_vec()).unwrap();
2303
2304 assert_eq!(
2305 ×tamp2,
2306 &fetch_metadata_to_string(
2307 client.local_repo(),
2308 &MetadataPath::timestamp(),
2309 MetadataVersion::None,
2310 )
2311 .await
2312 .unwrap(),
2313 );
2314
2315 assert_eq!(
2316 ×tamp2,
2317 &fetch_metadata_to_string(
2318 client.remote_repo(),
2319 &MetadataPath::timestamp(),
2320 MetadataVersion::None,
2321 )
2322 .await
2323 .unwrap(),
2324 );
2325
2326 client.database_mut().update_metadata(&metadata2).unwrap();
2328 assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2329 })
2330 }
2331
2332 #[test]
2333 fn client_can_update_with_unknown_len_and_hashes() {
2334 block_on(async {
2335 let repo = EphemeralRepository::<Pouf1>::new();
2336
2337 let root = RootMetadataBuilder::new()
2338 .consistent_snapshot(true)
2339 .root_key(KEYS[0].public().clone())
2340 .targets_key(KEYS[1].public().clone())
2341 .snapshot_key(KEYS[2].public().clone())
2342 .timestamp_key(KEYS[3].public().clone())
2343 .signed::<Pouf1>(&KEYS[0])
2344 .unwrap()
2345 .to_raw()
2346 .unwrap();
2347
2348 repo.store_metadata(
2349 &MetadataPath::root(),
2350 MetadataVersion::Number(1),
2351 &mut root.as_bytes(),
2352 )
2353 .await
2354 .unwrap();
2355
2356 let targets = TargetsMetadataBuilder::new()
2357 .signed::<Pouf1>(&KEYS[1])
2358 .unwrap()
2359 .to_raw()
2360 .unwrap();
2361
2362 repo.store_metadata(
2363 &MetadataPath::targets(),
2364 MetadataVersion::Number(1),
2365 &mut targets.as_bytes(),
2366 )
2367 .await
2368 .unwrap();
2369
2370 let targets_description = MetadataDescription::new(1, None, HashMap::new()).unwrap();
2373
2374 let snapshot = SnapshotMetadataBuilder::new()
2375 .insert_metadata_description(MetadataPath::targets(), targets_description)
2376 .signed::<Pouf1>(&KEYS[2])
2377 .unwrap()
2378 .to_raw()
2379 .unwrap();
2380
2381 repo.store_metadata(
2382 &MetadataPath::snapshot(),
2383 MetadataVersion::Number(1),
2384 &mut snapshot.as_bytes(),
2385 )
2386 .await
2387 .unwrap();
2388
2389 let snapshot_description = MetadataDescription::new(1, None, HashMap::new()).unwrap();
2392
2393 let timestamp =
2394 TimestampMetadataBuilder::from_metadata_description(snapshot_description)
2395 .signed::<Pouf1>(&KEYS[3])
2396 .unwrap()
2397 .to_raw()
2398 .unwrap();
2399
2400 repo.store_metadata(
2401 &MetadataPath::timestamp(),
2402 MetadataVersion::None,
2403 &mut timestamp.as_bytes(),
2404 )
2405 .await
2406 .unwrap();
2407
2408 let mut client = Client::with_trusted_root_keys(
2409 Config::default(),
2410 MetadataVersion::Number(1),
2411 1,
2412 once(&KEYS[0].public().clone()),
2413 EphemeralRepository::new(),
2414 repo,
2415 )
2416 .await
2417 .unwrap();
2418
2419 assert_matches!(client.update().await, Ok(true));
2420 })
2421 }
2422}