Skip to main content

tuf/
repo_builder.rs

1//! Repository Builder
2
3use {
4    crate::{
5        crypto::{self, HashAlgorithm, PrivateKey, PublicKey},
6        database::Database,
7        error::{Error, Result},
8        metadata::{
9            Delegation, DelegationsBuilder, Metadata, MetadataDescription, MetadataPath,
10            MetadataVersion, RawSignedMetadata, RawSignedMetadataSet, RawSignedMetadataSetBuilder,
11            RootMetadata, RootMetadataBuilder, SignedMetadataBuilder, SnapshotMetadata,
12            SnapshotMetadataBuilder, TargetDescription, TargetPath, TargetsMetadata,
13            TargetsMetadataBuilder, TimestampMetadata, TimestampMetadataBuilder,
14        },
15        pouf::Pouf,
16        repository::RepositoryStorage,
17        verify::Verified,
18    },
19    chrono::{DateTime, Duration, Utc},
20    futures_io::{AsyncRead, AsyncSeek},
21    futures_util::AsyncSeekExt as _,
22    std::{collections::HashMap, io::SeekFrom, marker::PhantomData},
23};
24
25mod private {
26    use super::*;
27
28    /// Implement the [sealed] pattern to make public traits that cannot be externally modified.
29    ///
30    /// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
31    pub trait Sealed {}
32
33    impl Sealed for Root {}
34    impl<D: Pouf> Sealed for Targets<D> {}
35    impl<D: Pouf> Sealed for Snapshot<D> {}
36    impl<D: Pouf> Sealed for Timestamp<D> {}
37    impl<D: Pouf> Sealed for Done<D> {}
38}
39
40const DEFAULT_ROOT_EXPIRATION: Duration = Duration::days(365);
41const DEFAULT_TARGETS_EXPIRATION: Duration = Duration::days(90);
42const DEFAULT_SNAPSHOT_EXPIRATION: Duration = Duration::days(7);
43const DEFAULT_TIMESTAMP_EXPIRATION: Duration = Duration::days(1);
44
45/// Trait to track each of the [RepoBuilder] building states.
46///
47/// This trait is [sealed] to make
48/// sure external users cannot implement the `State` crate with unexpected states.
49///
50/// [sealed]: https://rust-lang.github.io/api-guidelines/future-proofing.html#sealed-traits-protect-against-downstream-implementations-c-sealed
51pub trait State: private::Sealed {}
52
53/// State to stage a root metadata.
54#[doc(hidden)]
55pub struct Root {
56    builder: RootMetadataBuilder,
57}
58
59impl State for Root {}
60
61/// State to stage a targets metadata.
62#[doc(hidden)]
63pub struct Targets<D: Pouf> {
64    staged_root: Option<Staged<D, RootMetadata>>,
65    targets: HashMap<TargetPath, TargetDescription>,
66    delegation_keys: Vec<PublicKey>,
67    delegation_roles: Vec<Delegation>,
68    file_hash_algorithms: Vec<HashAlgorithm>,
69    inherit_from_trusted_targets: bool,
70}
71
72impl<D: Pouf> Targets<D> {
73    fn new(staged_root: Option<Staged<D, RootMetadata>>) -> Self {
74        Self {
75            staged_root,
76            targets: HashMap::new(),
77            delegation_keys: vec![],
78            delegation_roles: vec![],
79            file_hash_algorithms: vec![HashAlgorithm::Sha256],
80            inherit_from_trusted_targets: true,
81        }
82    }
83}
84
85impl<D: Pouf> State for Targets<D> {}
86
87/// State to stage a snapshot metadata.
88#[doc(hidden)]
89pub struct Snapshot<D: Pouf> {
90    staged_root: Option<Staged<D, RootMetadata>>,
91    staged_targets: Option<Staged<D, TargetsMetadata>>,
92    include_targets_length: bool,
93    targets_hash_algorithms: Vec<HashAlgorithm>,
94    inherit_from_trusted_snapshot: bool,
95}
96
97impl<D: Pouf> State for Snapshot<D> {}
98
99impl<D: Pouf> Snapshot<D> {
100    fn new(
101        staged_root: Option<Staged<D, RootMetadata>>,
102        staged_targets: Option<Staged<D, TargetsMetadata>>,
103    ) -> Self {
104        Self {
105            staged_root,
106            staged_targets,
107            include_targets_length: false,
108            targets_hash_algorithms: vec![],
109            inherit_from_trusted_snapshot: true,
110        }
111    }
112
113    fn targets_description(&self) -> Result<Option<MetadataDescription<TargetsMetadata>>> {
114        if let Some(ref targets) = self.staged_targets {
115            let length = if self.include_targets_length {
116                Some(targets.raw.as_bytes().len())
117            } else {
118                None
119            };
120
121            let hashes = if self.targets_hash_algorithms.is_empty() {
122                HashMap::new()
123            } else {
124                crypto::calculate_hashes_from_slice(
125                    targets.raw.as_bytes(),
126                    &self.targets_hash_algorithms,
127                )?
128            };
129
130            Ok(Some(MetadataDescription::new(
131                targets.metadata.version(),
132                length,
133                hashes,
134            )?))
135        } else {
136            Ok(None)
137        }
138    }
139}
140
141/// State to stage a timestamp metadata.
142pub struct Timestamp<D: Pouf> {
143    staged_root: Option<Staged<D, RootMetadata>>,
144    staged_targets: Option<Staged<D, TargetsMetadata>>,
145    staged_snapshot: Option<Staged<D, SnapshotMetadata>>,
146    include_snapshot_length: bool,
147    snapshot_hash_algorithms: Vec<HashAlgorithm>,
148}
149
150impl<D: Pouf> Timestamp<D> {
151    fn new(state: Snapshot<D>, staged_snapshot: Option<Staged<D, SnapshotMetadata>>) -> Self {
152        Self {
153            staged_root: state.staged_root,
154            staged_targets: state.staged_targets,
155            staged_snapshot,
156            include_snapshot_length: false,
157            snapshot_hash_algorithms: vec![],
158        }
159    }
160
161    fn snapshot_description(&self) -> Result<Option<MetadataDescription<SnapshotMetadata>>> {
162        if let Some(ref snapshot) = self.staged_snapshot {
163            let length = if self.include_snapshot_length {
164                Some(snapshot.raw.as_bytes().len())
165            } else {
166                None
167            };
168
169            let hashes = if self.snapshot_hash_algorithms.is_empty() {
170                HashMap::new()
171            } else {
172                crypto::calculate_hashes_from_slice(
173                    snapshot.raw.as_bytes(),
174                    &self.snapshot_hash_algorithms,
175                )?
176            };
177
178            Ok(Some(MetadataDescription::new(
179                snapshot.metadata.version(),
180                length,
181                hashes,
182            )?))
183        } else {
184            Ok(None)
185        }
186    }
187}
188
189impl<D: Pouf> State for Timestamp<D> {}
190
191/// The final state for building repository metadata.
192pub struct Done<D: Pouf> {
193    staged_root: Option<Staged<D, RootMetadata>>,
194    staged_targets: Option<Staged<D, TargetsMetadata>>,
195    staged_snapshot: Option<Staged<D, SnapshotMetadata>>,
196    staged_timestamp: Option<Staged<D, TimestampMetadata>>,
197}
198
199impl<D: Pouf> State for Done<D> {}
200
201struct Staged<D: Pouf, M: Metadata> {
202    metadata: M,
203    raw: RawSignedMetadata<D, M>,
204}
205
206struct RepoContext<'a, D, R>
207where
208    D: Pouf,
209    R: RepositoryStorage<D>,
210{
211    repo: R,
212    db: Option<&'a Database<D>>,
213    current_time: DateTime<Utc>,
214    signing_root_keys: Vec<&'a dyn PrivateKey>,
215    signing_targets_keys: Vec<&'a dyn PrivateKey>,
216    signing_snapshot_keys: Vec<&'a dyn PrivateKey>,
217    signing_timestamp_keys: Vec<&'a dyn PrivateKey>,
218    trusted_root_keys: Vec<&'a dyn PrivateKey>,
219    trusted_targets_keys: Vec<&'a dyn PrivateKey>,
220    trusted_snapshot_keys: Vec<&'a dyn PrivateKey>,
221    trusted_timestamp_keys: Vec<&'a dyn PrivateKey>,
222    time_version: Option<u32>,
223    root_expiration_duration: Duration,
224    targets_expiration_duration: Duration,
225    snapshot_expiration_duration: Duration,
226    timestamp_expiration_duration: Duration,
227    _pouf: PhantomData<D>,
228}
229
230impl<D, R> RepoContext<'_, D, R>
231where
232    D: Pouf,
233    R: RepositoryStorage<D>,
234{
235    fn root_keys_changed(&self, root: &Verified<RootMetadata>) -> bool {
236        let root_keys_count = root.root_keys().count();
237        if root_keys_count != self.trusted_root_keys.len() {
238            return true;
239        }
240
241        for key in &self.trusted_root_keys {
242            if root.root().key_ids().get(key.public().key_id()).is_none() {
243                return true;
244            }
245        }
246
247        false
248    }
249    fn targets_keys_changed(&self, root: &Verified<RootMetadata>) -> bool {
250        for key in &self.trusted_targets_keys {
251            if root
252                .targets()
253                .key_ids()
254                .get(key.public().key_id())
255                .is_none()
256            {
257                return true;
258            }
259        }
260
261        false
262    }
263
264    fn snapshot_keys_changed(&self, root: &Verified<RootMetadata>) -> bool {
265        for key in &self.trusted_snapshot_keys {
266            if root
267                .snapshot()
268                .key_ids()
269                .get(key.public().key_id())
270                .is_none()
271            {
272                return true;
273            }
274        }
275
276        false
277    }
278
279    fn timestamp_keys_changed(&self, root: &Verified<RootMetadata>) -> bool {
280        for key in &self.trusted_timestamp_keys {
281            if root
282                .timestamp()
283                .key_ids()
284                .get(key.public().key_id())
285                .is_none()
286            {
287                return true;
288            }
289        }
290
291        false
292    }
293
294    /// The initial version number for non-root metadata.
295    fn non_root_initial_version(&self) -> u32 {
296        self.time_version.unwrap_or(1)
297    }
298
299    /// If time versioning is enabled, this updates the current time version to match the current
300    /// time. It will disable time versioning if the current timestamp is less than or equal to
301    /// zero, or it is greater than max u32.
302    fn update_time_version(&mut self) {
303        // We can use the time version if it is greater than zero and less than max u32. Otherwise
304        // fall back to default monontonic versioning.
305        let timestamp = self.current_time.timestamp();
306        if timestamp > 0 {
307            self.time_version = timestamp.try_into().ok();
308        } else {
309            self.time_version = None;
310        }
311    }
312
313    /// The next version number for non-root metadata.
314    fn non_root_next_version(
315        &self,
316        current_version: u32,
317        path: fn() -> MetadataPath,
318    ) -> Result<u32> {
319        if let Some(time_version) = self.time_version {
320            // We can only use the time version if it's larger than our current version. If not,
321            // then fall back to the next version.
322            if current_version < time_version {
323                return Ok(time_version);
324            }
325        }
326
327        current_version
328            .checked_add(1)
329            .ok_or_else(|| Error::MetadataVersionMustBeSmallerThanMaxU32(path()))
330    }
331}
332
333fn sign<'a, D, I, M>(meta: &M, keys: I) -> Result<RawSignedMetadata<D, M>>
334where
335    D: Pouf,
336    M: Metadata,
337    I: IntoIterator<Item = &'a &'a dyn PrivateKey>,
338{
339    // Sign the root.
340    let mut signed_builder = SignedMetadataBuilder::<D, _>::from_metadata(meta)?;
341    let mut has_key = false;
342    for key in keys {
343        has_key = true;
344        signed_builder = signed_builder.sign(*key)?;
345    }
346
347    // We need at least one private key to sign the metadata.
348    if !has_key {
349        return Err(Error::MissingPrivateKey {
350            role: M::ROLE.into(),
351        });
352    }
353
354    signed_builder.build().to_raw()
355}
356
357/// This helper builder simplifies the process of creating new metadata.
358pub struct RepoBuilder<'a, D, R, S = Root>
359where
360    D: Pouf,
361    R: RepositoryStorage<D>,
362    S: State,
363{
364    ctx: RepoContext<'a, D, R>,
365    state: S,
366}
367
368impl<'a, D, R> RepoBuilder<'a, D, R, Root>
369where
370    D: Pouf,
371    R: RepositoryStorage<D>,
372{
373    /// Create a [RepoBuilder] for creating metadata for a new repository.
374    ///
375    /// # Examples
376    ///
377    /// ```rust
378    /// # use {
379    /// #     futures_executor::block_on,
380    /// #     tuf::{
381    /// #         pouf::Pouf1,
382    /// #         crypto::Ed25519PrivateKey,
383    /// #         repo_builder::RepoBuilder,
384    /// #         repository::EphemeralRepository,
385    /// #     },
386    /// # };
387    /// #
388    /// # let key = Ed25519PrivateKey::from_pkcs8(
389    /// #     include_bytes!("../tests/ed25519/ed25519-1.pk8.der")
390    /// # ).unwrap();
391    /// #
392    /// # block_on(async {
393    /// let mut repo = EphemeralRepository::<Pouf1>::new();
394    /// let _metadata = RepoBuilder::create(&mut repo)
395    ///     .trusted_root_keys(&[&key])
396    ///     .trusted_targets_keys(&[&key])
397    ///     .trusted_snapshot_keys(&[&key])
398    ///     .trusted_timestamp_keys(&[&key])
399    ///     .commit()
400    ///     .await
401    ///     .unwrap();
402    /// # });
403    /// ```
404    pub fn create(repo: R) -> Self {
405        Self {
406            ctx: RepoContext {
407                repo,
408                db: None,
409                current_time: Utc::now(),
410                signing_root_keys: vec![],
411                signing_targets_keys: vec![],
412                signing_snapshot_keys: vec![],
413                signing_timestamp_keys: vec![],
414                trusted_root_keys: vec![],
415                trusted_targets_keys: vec![],
416                trusted_snapshot_keys: vec![],
417                trusted_timestamp_keys: vec![],
418                time_version: None,
419                root_expiration_duration: DEFAULT_ROOT_EXPIRATION,
420                targets_expiration_duration: DEFAULT_TARGETS_EXPIRATION,
421                snapshot_expiration_duration: DEFAULT_SNAPSHOT_EXPIRATION,
422                timestamp_expiration_duration: DEFAULT_TIMESTAMP_EXPIRATION,
423                _pouf: PhantomData,
424            },
425            state: Root {
426                builder: RootMetadataBuilder::new()
427                    .consistent_snapshot(true)
428                    .root_threshold(1)
429                    .targets_threshold(1)
430                    .snapshot_threshold(1)
431                    .timestamp_threshold(1),
432            },
433        }
434    }
435
436    /// Create a [RepoBuilder] for creating metadata based off the latest metadata in the
437    /// [Database].
438    ///
439    /// # Examples
440    ///
441    /// ```rust
442    /// # use {
443    /// #     futures_executor::block_on,
444    /// #     tuf::{
445    /// #         database::Database,
446    /// #         crypto::Ed25519PrivateKey,
447    /// #         pouf::Pouf1,
448    /// #         repo_builder::RepoBuilder,
449    /// #         repository::EphemeralRepository,
450    /// #     },
451    /// # };
452    /// #
453    /// # let key = Ed25519PrivateKey::from_pkcs8(
454    /// #     include_bytes!("../tests/ed25519/ed25519-1.pk8.der")
455    /// # ).unwrap();
456    /// #
457    /// # block_on(async {
458    ///  let mut repo = EphemeralRepository::<Pouf1>::new();
459    ///  let metadata1 = RepoBuilder::create(&mut repo)
460    ///     .trusted_root_keys(&[&key])
461    ///     .trusted_targets_keys(&[&key])
462    ///     .trusted_snapshot_keys(&[&key])
463    ///     .trusted_timestamp_keys(&[&key])
464    ///     .commit()
465    ///     .await
466    ///     .unwrap();
467    ///
468    /// let database = Database::from_trusted_metadata(&metadata1).unwrap();
469    ///
470    /// let _metadata2 = RepoBuilder::from_database(&mut repo, &database)
471    ///     .trusted_root_keys(&[&key])
472    ///     .trusted_targets_keys(&[&key])
473    ///     .trusted_snapshot_keys(&[&key])
474    ///     .trusted_timestamp_keys(&[&key])
475    ///     .stage_root()
476    ///     .unwrap()
477    ///     .commit()
478    ///     .await
479    ///     .unwrap();
480    /// # });
481    /// ```
482    pub fn from_database(repo: R, db: &'a Database<D>) -> Self {
483        let builder = {
484            let trusted_root = db.trusted_root();
485
486            RootMetadataBuilder::new()
487                .consistent_snapshot(trusted_root.consistent_snapshot())
488                .root_threshold(trusted_root.root().threshold())
489                .targets_threshold(trusted_root.targets().threshold())
490                .snapshot_threshold(trusted_root.snapshot().threshold())
491                .timestamp_threshold(trusted_root.timestamp().threshold())
492        };
493
494        Self {
495            ctx: RepoContext {
496                repo,
497                db: Some(db),
498                current_time: Utc::now(),
499                signing_root_keys: vec![],
500                signing_targets_keys: vec![],
501                signing_snapshot_keys: vec![],
502                signing_timestamp_keys: vec![],
503                trusted_root_keys: vec![],
504                trusted_targets_keys: vec![],
505                trusted_snapshot_keys: vec![],
506                trusted_timestamp_keys: vec![],
507                time_version: None,
508                root_expiration_duration: DEFAULT_ROOT_EXPIRATION,
509                targets_expiration_duration: DEFAULT_TARGETS_EXPIRATION,
510                snapshot_expiration_duration: DEFAULT_SNAPSHOT_EXPIRATION,
511                timestamp_expiration_duration: DEFAULT_TIMESTAMP_EXPIRATION,
512                _pouf: PhantomData,
513            },
514            state: Root { builder },
515        }
516    }
517
518    /// Change the time the builder will use to see if metadata is expired, and the base time to use
519    /// to compute the next expiration.
520    ///
521    /// Default is the current wall clock time in UTC.
522    pub fn current_time(mut self, current_time: DateTime<Utc>) -> Self {
523        self.ctx.current_time = current_time;
524
525        // Update our time version if enabled.
526        if self.ctx.time_version.is_some() {
527            self.ctx.update_time_version();
528        }
529
530        self
531    }
532
533    /// Create Non-root metadata based off the current UTC timestamp, instead of a monotonic
534    /// increment.
535    pub fn time_versioning(mut self, time_versioning: bool) -> Self {
536        if time_versioning {
537            self.ctx.update_time_version();
538        } else {
539            self.ctx.time_version = None;
540        }
541        self
542    }
543
544    /// Sets that the root metadata will expire after this duration past the current time.
545    ///
546    /// Defaults to 365 days.
547    ///
548    /// Note: calling this function will only change what is the metadata expiration we'll use if we
549    /// create a new root metadata if we call [RepoBuilder::stage_root], or we decide a new one is
550    /// needed when we call [RepoBuilder::stage_root_if_necessary].
551    pub fn root_expiration_duration(mut self, duration: Duration) -> Self {
552        self.ctx.root_expiration_duration = duration;
553        self
554    }
555
556    /// Sets that the targets metadata will expire after after this duration past the current time.
557    ///
558    /// Defaults to 90 days.
559    ///
560    /// Note: calling this function will only change what is the metadata expiration we'll use if we
561    /// create a new targets metadata if we call [RepoBuilder::stage_targets], or we decide a new
562    /// one is needed when we call [RepoBuilder::stage_targets_if_necessary].
563    pub fn targets_expiration_duration(mut self, duration: Duration) -> Self {
564        self.ctx.targets_expiration_duration = duration;
565        self
566    }
567
568    /// Sets that the snapshot metadata will expire after after this duration past the current time.
569    ///
570    /// Defaults to 7 days.
571    ///
572    /// Note: calling this function will only change what is the metadata expiration we'll use if we
573    /// create a new snapshot metadata if we call [RepoBuilder::stage_snapshot], or we decide a new
574    /// one is needed when we call [RepoBuilder::stage_snapshot_if_necessary].
575    pub fn snapshot_expiration_duration(mut self, duration: Duration) -> Self {
576        self.ctx.snapshot_expiration_duration = duration;
577        self
578    }
579
580    /// Sets that the timestamp metadata will expire after after this duration past the current
581    /// time.
582    ///
583    /// Defaults to 1 day.
584    ///
585    /// Note: calling this function will only change what is the metadata expiration we'll use if we
586    /// create a new timestamp metadata if we call [RepoBuilder::stage_timestamp], or we decide a
587    /// new one is needed when we call [RepoBuilder::stage_timestamp_if_necessary].
588    pub fn timestamp_expiration_duration(mut self, duration: Duration) -> Self {
589        self.ctx.timestamp_expiration_duration = duration;
590        self
591    }
592
593    /// Sign the root metadata with `keys`, but do not include the keys as trusted root keys in the
594    /// root metadata. This is typically used to support root key rotation.
595    pub fn signing_root_keys(mut self, keys: &[&'a dyn PrivateKey]) -> Self {
596        for key in keys {
597            self.ctx.signing_root_keys.push(*key);
598        }
599        self
600    }
601
602    /// Sign the targets metadata with `keys`, but do not include the keys as trusted targets keys
603    /// in the root metadata. This is typically used to support targets key rotation.
604    pub fn signing_targets_keys(mut self, keys: &[&'a dyn PrivateKey]) -> Self {
605        for key in keys {
606            self.ctx.signing_targets_keys.push(*key);
607        }
608        self
609    }
610
611    /// Sign the snapshot metadata with `keys`, but do not include the keys as trusted snapshot keys
612    /// in the root metadata. This is typically used to support snapshot key rotation.
613    pub fn signing_snapshot_keys(mut self, keys: &[&'a dyn PrivateKey]) -> Self {
614        for key in keys {
615            self.ctx.signing_snapshot_keys.push(*key);
616        }
617        self
618    }
619
620    /// Sign the timestamp metadata with `keys`, but do not include the keys as trusted timestamp
621    /// keys in the root metadata. This is typically used to support timestamp key rotation.
622    pub fn signing_timestamp_keys(mut self, keys: &[&'a dyn PrivateKey]) -> Self {
623        for key in keys {
624            self.ctx.signing_timestamp_keys.push(*key);
625        }
626        self
627    }
628
629    /// Sign the root metadata with `keys`, and include the keys as trusted root keys in the root
630    /// metadata.
631    pub fn trusted_root_keys(mut self, keys: &[&'a dyn PrivateKey]) -> Self {
632        for key in keys {
633            self.ctx.trusted_root_keys.push(*key);
634            self.state.builder = self.state.builder.root_key(key.public().clone());
635        }
636        self
637    }
638
639    /// Sign the targets metadata with `keys`, and include the keys as trusted targets keys in the
640    /// targets metadata.
641    pub fn trusted_targets_keys(mut self, keys: &[&'a dyn PrivateKey]) -> Self {
642        for key in keys {
643            self.ctx.trusted_targets_keys.push(*key);
644            self.state.builder = self.state.builder.targets_key(key.public().clone());
645        }
646        self
647    }
648
649    /// Sign the snapshot metadata with `keys`, and include the keys as trusted snapshot keys in the
650    /// root metadata.
651    pub fn trusted_snapshot_keys(mut self, keys: &[&'a dyn PrivateKey]) -> Self {
652        for key in keys {
653            self.ctx.trusted_snapshot_keys.push(*key);
654            self.state.builder = self.state.builder.snapshot_key(key.public().clone());
655        }
656        self
657    }
658
659    /// Sign the timestamp metadata with `keys`, and include the keys as
660    /// trusted timestamp keys in the root metadata.
661    pub fn trusted_timestamp_keys(mut self, keys: &[&'a dyn PrivateKey]) -> Self {
662        for key in keys {
663            self.ctx.trusted_timestamp_keys.push(*key);
664            self.state.builder = self.state.builder.timestamp_key(key.public().clone());
665        }
666        self
667    }
668
669    /// Stage a root metadata.
670    ///
671    /// If this is a new repository, the root will be staged with:
672    ///
673    /// * version: 1
674    /// * consistent_snapshot: true
675    /// * expires: 365 days from the current day.
676    /// * root_threshold: 1
677    /// * targets_threshold: 1
678    /// * snapshot_threshold: 1
679    /// * timestamp_threshold: 1
680    ///
681    /// Otherwise, it will be staged with:
682    ///
683    /// * version: 1 after the trusted root's version
684    /// * expires: 365 days from the current day.
685    /// * consistent_snapshot: match the trusted root's consistent snapshot
686    /// * root_threshold: match the trusted root's root threshold
687    /// * targets_threshold: match the trusted root's targets threshold
688    /// * snapshot_threshold: match the trusted root's snapshot threshold
689    /// * timestamp_threshold: match the trusted root's timestamp threshold
690    pub fn stage_root(self) -> Result<RepoBuilder<'a, D, R, Targets<D>>> {
691        self.stage_root_with_builder(|builder| builder)
692    }
693
694    /// Stage a new root using the default settings if:
695    ///
696    /// * There is no trusted root metadata.
697    /// * The trusted keys are different from the keys that are in the trusted root.
698    /// * The trusted root metadata has expired.
699    pub fn stage_root_if_necessary(self) -> Result<RepoBuilder<'a, D, R, Targets<D>>> {
700        if self.need_new_root() {
701            self.stage_root()
702        } else {
703            Ok(self.skip_root())
704        }
705    }
706
707    /// Skip creating the root metadata. This may cause [commit](#method.commit-4) to fail if this
708    /// is a new repository.
709    pub fn skip_root(self) -> RepoBuilder<'a, D, R, Targets<D>> {
710        RepoBuilder {
711            ctx: self.ctx,
712            state: Targets::new(None),
713        }
714    }
715
716    /// Initialize a [RootMetadataBuilder] and pass it to the closure for further configuration.
717    /// This builder will then be used to generate and stage a new [RootMetadata] for eventual
718    /// commitment to the repository.
719    ///
720    /// If this is a new repository, the builder will be initialized with the following defaults:
721    ///
722    /// * version: 1
723    /// * consistent_snapshot: true
724    /// * expires: 365 days from the current day.
725    /// * root_threshold: 1
726    /// * targets_threshold: 1
727    /// * snapshot_threshold: 1
728    /// * timestamp_threshold: 1
729    ///
730    /// Otherwise, it will be initialized with:
731    ///
732    /// * version: 1 after the trusted root's version
733    /// * expires: 365 days from the current day.
734    /// * consistent_snapshot: match the trusted root's consistent snapshot
735    /// * root_threshold: match the trusted root's root threshold
736    /// * targets_threshold: match the trusted root's targets threshold
737    /// * snapshot_threshold: match the trusted root's snapshot threshold
738    /// * timestamp_threshold: match the trusted root's timestamp threshold
739    pub fn stage_root_with_builder<F>(self, f: F) -> Result<RepoBuilder<'a, D, R, Targets<D>>>
740    where
741        F: FnOnce(RootMetadataBuilder) -> RootMetadataBuilder,
742    {
743        let next_version = if let Some(db) = self.ctx.db {
744            db.trusted_root().version().checked_add(1).ok_or_else(|| {
745                Error::MetadataVersionMustBeSmallerThanMaxU32(MetadataPath::root())
746            })?
747        } else {
748            1
749        };
750
751        let root_builder = self
752            .state
753            .builder
754            .version(next_version)
755            .expires(self.ctx.current_time + self.ctx.root_expiration_duration);
756        let root = f(root_builder).build()?;
757
758        let raw_root = sign(
759            &root,
760            self.ctx
761                .signing_root_keys
762                .iter()
763                .chain(&self.ctx.trusted_root_keys),
764        )?;
765
766        Ok(RepoBuilder {
767            ctx: self.ctx,
768            state: Targets::new(Some(Staged {
769                metadata: root,
770                raw: raw_root,
771            })),
772        })
773    }
774
775    /// Add a target that's loaded in from the reader. This will store the target in the repository,
776    /// and may stage a root metadata if necessary.
777    ///
778    /// This will hash the file with [HashAlgorithm::Sha256].
779    ///
780    /// See `RepoBuilder<Targets>::add_target` for more details.
781    pub async fn add_target<Rd>(
782        self,
783        target_path: TargetPath,
784        reader: Rd,
785    ) -> Result<RepoBuilder<'a, D, R, Targets<D>>>
786    where
787        Rd: AsyncRead + AsyncSeek + Unpin + Send,
788    {
789        self.stage_root_if_necessary()?
790            .add_target(target_path, reader)
791            .await
792    }
793
794    /// Validate and write the metadata to the repository.
795    ///
796    /// This may stage a root, targets, snapshot, and timestamp metadata if necessary.
797    ///
798    /// See [RepoBuilder::commit] for more details.
799    pub async fn commit(self) -> Result<RawSignedMetadataSet<D>> {
800        self.stage_root_if_necessary()?.commit().await
801    }
802
803    /// Check if we need a new root database.
804    fn need_new_root(&self) -> bool {
805        // We need a new root metadata if we don't have a database yet.
806        let trusted_root = if let Some(db) = self.ctx.db {
807            db.trusted_root()
808        } else {
809            return true;
810        };
811
812        // We need a new root metadata if the metadata expired.
813        if trusted_root.expires() <= &self.ctx.current_time {
814            return true;
815        }
816
817        // Sign the metadata if we passed in any old root keys.
818        if !self.ctx.signing_root_keys.is_empty() {
819            return true;
820        }
821
822        // Otherwise, see if any of the keys have changed.
823        self.ctx.root_keys_changed(trusted_root)
824            || self.ctx.targets_keys_changed(trusted_root)
825            || self.ctx.snapshot_keys_changed(trusted_root)
826            || self.ctx.timestamp_keys_changed(trusted_root)
827    }
828}
829
830impl<'a, D, R> RepoBuilder<'a, D, R, Targets<D>>
831where
832    D: Pouf,
833    R: RepositoryStorage<D>,
834{
835    /// Whether or not to include the length of the targets, and any delegated targets, in the
836    /// new snapshot.
837    ///
838    /// Default is `[HashAlgorithm::Sha256]`.
839    pub fn target_hash_algorithms(mut self, algorithms: &[HashAlgorithm]) -> Self {
840        self.state.file_hash_algorithms = algorithms.to_vec();
841        self
842    }
843
844    /// Whether or not the new targets metadata inherits targets and delegations from the trusted
845    /// targets metadata.
846    ///
847    /// Default is `true`.
848    pub fn inherit_from_trusted_targets(mut self, inherit: bool) -> Self {
849        self.state.inherit_from_trusted_targets = inherit;
850        self
851    }
852
853    /// Stage a targets metadata using the default settings.
854    pub fn stage_targets(self) -> Result<RepoBuilder<'a, D, R, Snapshot<D>>> {
855        self.stage_targets_with_builder(|builder| builder)
856    }
857
858    /// Stage a new targets using the default settings if:
859    ///
860    /// * There is no trusted targets metadata.
861    /// * The trusted targets metadata has expired.
862    pub fn stage_targets_if_necessary(self) -> Result<RepoBuilder<'a, D, R, Snapshot<D>>> {
863        if self.need_new_targets() {
864            self.stage_targets_with_builder(|builder| builder)
865        } else {
866            Ok(self.skip_targets())
867        }
868    }
869
870    /// Skip creating the targets metadata.
871    pub fn skip_targets(self) -> RepoBuilder<'a, D, R, Snapshot<D>> {
872        RepoBuilder {
873            ctx: self.ctx,
874            state: Snapshot::new(self.state.staged_root, None),
875        }
876    }
877
878    /// Add a target that's loaded in from the reader. This will store the target in the repository.
879    ///
880    /// This will hash the file with the hash specified in [RepoBuilder::target_hash_algorithms]. If
881    /// none was specified, the file will be hashed with [HashAlgorithm::Sha256].
882    pub async fn add_target<Rd>(
883        self,
884        target_path: TargetPath,
885        reader: Rd,
886    ) -> Result<RepoBuilder<'a, D, R, Targets<D>>>
887    where
888        Rd: AsyncRead + AsyncSeek + Unpin + Send,
889    {
890        self.add_target_with_custom(target_path, reader, HashMap::new())
891            .await
892    }
893
894    /// Add a target that's loaded in from the reader. This will store the target in the repository.
895    ///
896    /// This will hash the file with the hash specified in [RepoBuilder::target_hash_algorithms]. If
897    /// none was specified, the file will be hashed with [HashAlgorithm::Sha256].
898    pub async fn add_target_with_custom<Rd>(
899        mut self,
900        target_path: TargetPath,
901        mut reader: Rd,
902        custom: HashMap<String, serde_json::Value>,
903    ) -> Result<RepoBuilder<'a, D, R, Targets<D>>>
904    where
905        Rd: AsyncRead + AsyncSeek + Unpin + Send,
906    {
907        let consistent_snapshot = if let Some(ref staged_root) = self.state.staged_root {
908            staged_root.metadata.consistent_snapshot()
909        } else if let Some(db) = self.ctx.db {
910            db.trusted_root().consistent_snapshot()
911        } else {
912            return Err(Error::MetadataNotFound {
913                path: MetadataPath::root(),
914                version: MetadataVersion::None,
915            });
916        };
917
918        let target_description = TargetDescription::from_reader_with_custom(
919            &mut reader,
920            &self.state.file_hash_algorithms,
921            custom,
922        )
923        .await?;
924
925        // According to TUF section 5.5.2, when consistent snapshot is enabled, target files should be
926        // stored at `$HASH.FILENAME.EXT`. Otherwise it is stored at `FILENAME.EXT`.
927        if consistent_snapshot {
928            for digest in target_description.hashes().values() {
929                reader.seek(SeekFrom::Start(0)).await?;
930
931                let hash_prefixed_path = target_path.with_hash_prefix(digest)?;
932
933                self.ctx
934                    .repo
935                    .store_target(&hash_prefixed_path, &mut reader)
936                    .await?;
937            }
938        } else {
939            reader.seek(SeekFrom::Start(0)).await?;
940
941            self.ctx
942                .repo
943                .store_target(&target_path, &mut reader)
944                .await?;
945        }
946
947        self.state.targets.insert(target_path, target_description);
948
949        Ok(self)
950    }
951
952    /// Add a target delegation key.
953    pub fn add_delegation_key(mut self, key: PublicKey) -> Self {
954        self.state.delegation_keys.push(key);
955        self
956    }
957
958    /// Add a target delegation role.
959    pub fn add_delegation_role(mut self, delegation: Delegation) -> Self {
960        self.state.delegation_roles.push(delegation);
961        self
962    }
963
964    /// Initialize a [TargetsMetadataBuilder] and pass it to the closure for further configuration.
965    /// This builder will then be used to generate and stage a new [TargetsMetadata] for eventual
966    /// commitment to the repository.
967    ///
968    /// This builder will be initialized with:
969    ///
970    /// * version: 1 if a new repository, otherwise 1 past the trusted targets's version.
971    /// * expires: 90 days from the current day.
972    pub fn stage_targets_with_builder<F>(self, f: F) -> Result<RepoBuilder<'a, D, R, Snapshot<D>>>
973    where
974        F: FnOnce(TargetsMetadataBuilder) -> TargetsMetadataBuilder,
975    {
976        let mut targets_builder = TargetsMetadataBuilder::new()
977            .expires(self.ctx.current_time + self.ctx.targets_expiration_duration);
978
979        let mut delegations_builder = DelegationsBuilder::new();
980
981        if let Some(trusted_targets) = self.ctx.db.and_then(|db| db.trusted_targets()) {
982            let next_version = self
983                .ctx
984                .non_root_next_version(trusted_targets.version(), MetadataPath::targets)?;
985
986            targets_builder = targets_builder.version(next_version);
987
988            // Insert all the metadata from the trusted snapshot.
989            if self.state.inherit_from_trusted_targets {
990                for (target_path, target_description) in trusted_targets.targets() {
991                    targets_builder = targets_builder
992                        .insert_target_description(target_path.clone(), target_description.clone());
993                }
994
995                for key in trusted_targets.delegations().keys().values() {
996                    delegations_builder = delegations_builder.key(key.clone());
997                }
998
999                for role in trusted_targets.delegations().roles() {
1000                    delegations_builder = delegations_builder.role(role.clone());
1001                }
1002            }
1003        } else {
1004            targets_builder = targets_builder.version(self.ctx.non_root_initial_version());
1005        }
1006
1007        // Overwrite any of the old targets with the new ones.
1008        for (target_path, target_description) in self.state.targets {
1009            targets_builder = targets_builder
1010                .insert_target_description(target_path.clone(), target_description.clone());
1011        }
1012
1013        // Overwrite the old delegation keys.
1014        for key in self.state.delegation_keys {
1015            delegations_builder = delegations_builder.key(key);
1016        }
1017
1018        // Overwrite the old delegation roles.
1019        for role in self.state.delegation_roles {
1020            delegations_builder = delegations_builder.role(role);
1021        }
1022
1023        targets_builder = targets_builder.delegations(delegations_builder.build()?);
1024
1025        let targets = f(targets_builder).build()?;
1026
1027        // Sign the targets metadata.
1028        let raw_targets = sign(
1029            &targets,
1030            self.ctx
1031                .signing_targets_keys
1032                .iter()
1033                .chain(&self.ctx.trusted_targets_keys),
1034        )?;
1035
1036        Ok(RepoBuilder {
1037            ctx: self.ctx,
1038            state: Snapshot::new(
1039                self.state.staged_root,
1040                Some(Staged {
1041                    metadata: targets,
1042                    raw: raw_targets,
1043                }),
1044            ),
1045        })
1046    }
1047
1048    /// Validate and write the metadata to the repository.
1049    ///
1050    /// This may stage a targets, snapshot, and timestamp metadata if necessary.
1051    ///
1052    /// See [RepoBuilder::commit](#method.commit-4) for more details.
1053    pub async fn commit(self) -> Result<RawSignedMetadataSet<D>> {
1054        self.stage_targets_if_necessary()?.commit().await
1055    }
1056
1057    fn need_new_targets(&self) -> bool {
1058        // We need a new targets metadata if we added any targets.
1059        if !self.state.targets.is_empty() {
1060            return true;
1061        }
1062
1063        // We need a new targets metadata if we staged a new root.
1064        if self.state.staged_root.is_some() {
1065            return true;
1066        }
1067
1068        // We need a new targets metadata if we don't have a database yet.
1069        let db = if let Some(ref db) = self.ctx.db {
1070            db
1071        } else {
1072            return true;
1073        };
1074
1075        // We need a new targets metadata if the database doesn't have a targets.
1076        let trusted_targets = if let Some(trusted_targets) = db.trusted_targets() {
1077            trusted_targets
1078        } else {
1079            return true;
1080        };
1081
1082        // We need a new targets metadata if the metadata expired.
1083        if trusted_targets.expires() <= &self.ctx.current_time {
1084            return true;
1085        }
1086
1087        // Otherwise, see if the targets keys have changed.
1088        self.ctx.targets_keys_changed(db.trusted_root())
1089    }
1090}
1091
1092impl<'a, D, R> RepoBuilder<'a, D, R, Snapshot<D>>
1093where
1094    D: Pouf,
1095    R: RepositoryStorage<D>,
1096{
1097    /// Whether or not to include the length of the targets, and any delegated targets, in the
1098    /// new snapshot.
1099    ///
1100    /// Default is `false`.
1101    pub fn snapshot_includes_length(mut self, include_targets_lengths: bool) -> Self {
1102        self.state.include_targets_length = include_targets_lengths;
1103        self
1104    }
1105
1106    /// Whether or not to include the hashes of the targets, and any delegated targets, in the
1107    /// new snapshot.
1108    ///
1109    /// Default is `&[]`.
1110    pub fn snapshot_includes_hashes(mut self, hashes: &[HashAlgorithm]) -> Self {
1111        self.state.targets_hash_algorithms = hashes.to_vec();
1112        self
1113    }
1114
1115    /// Whether or not the new snapshot to inherit metafiles from the trusted snapshot.
1116    ///
1117    /// Default is `true`.
1118    pub fn inherit_from_trusted_snapshot(mut self, inherit: bool) -> Self {
1119        self.state.inherit_from_trusted_snapshot = inherit;
1120        self
1121    }
1122
1123    /// Stage a snapshot metadata using the default settings.
1124    pub fn stage_snapshot(self) -> Result<RepoBuilder<'a, D, R, Timestamp<D>>> {
1125        self.stage_snapshot_with_builder(|builder| builder)
1126    }
1127
1128    /// Stage a new snapshot using the default settings if:
1129    ///
1130    /// * There is no trusted snapshot metadata.
1131    /// * The trusted snapshot metadata has expired.
1132    pub fn stage_snapshot_if_necessary(self) -> Result<RepoBuilder<'a, D, R, Timestamp<D>>> {
1133        if self.need_new_snapshot() {
1134            self.stage_snapshot()
1135        } else {
1136            Ok(self.skip_snapshot())
1137        }
1138    }
1139
1140    /// Skip creating the snapshot metadata.
1141    pub fn skip_snapshot(self) -> RepoBuilder<'a, D, R, Timestamp<D>> {
1142        RepoBuilder {
1143            ctx: self.ctx,
1144            state: Timestamp::new(self.state, None),
1145        }
1146    }
1147
1148    /// Initialize a [SnapshotMetadataBuilder] and pass it to the closure for further configuration.
1149    /// This builder will then be used to generate and stage a new [SnapshotMetadata] for eventual
1150    /// commitment to the repository.
1151    ///
1152    /// This builder will be initialized with:
1153    ///
1154    /// * version: 1 if a new repository, otherwise 1 past the trusted snapshot's version.
1155    /// * expires: 7 days from the current day.
1156    pub fn stage_snapshot_with_builder<F>(self, f: F) -> Result<RepoBuilder<'a, D, R, Timestamp<D>>>
1157    where
1158        F: FnOnce(SnapshotMetadataBuilder) -> SnapshotMetadataBuilder,
1159    {
1160        let mut snapshot_builder = SnapshotMetadataBuilder::new()
1161            .expires(self.ctx.current_time + self.ctx.snapshot_expiration_duration);
1162
1163        if let Some(trusted_snapshot) = self.ctx.db.and_then(|db| db.trusted_snapshot()) {
1164            let next_version = self
1165                .ctx
1166                .non_root_next_version(trusted_snapshot.version(), MetadataPath::snapshot)?;
1167
1168            snapshot_builder = snapshot_builder.version(next_version);
1169
1170            // Insert all the metadata from the trusted snapshot.
1171            if self.state.inherit_from_trusted_snapshot {
1172                for (path, description) in trusted_snapshot.meta() {
1173                    snapshot_builder = snapshot_builder
1174                        .insert_metadata_description(path.clone(), description.clone());
1175                }
1176            }
1177        } else {
1178            snapshot_builder = snapshot_builder.version(self.ctx.non_root_initial_version());
1179        }
1180
1181        // Overwrite the targets entry if specified.
1182        if let Some(targets_description) = self.state.targets_description()? {
1183            snapshot_builder = snapshot_builder
1184                .insert_metadata_description(MetadataPath::targets(), targets_description);
1185        };
1186
1187        let snapshot = f(snapshot_builder).build()?;
1188        let raw_snapshot = sign(
1189            &snapshot,
1190            self.ctx
1191                .signing_snapshot_keys
1192                .iter()
1193                .chain(&self.ctx.trusted_snapshot_keys),
1194        )?;
1195
1196        Ok(RepoBuilder {
1197            ctx: self.ctx,
1198            state: Timestamp::new(
1199                self.state,
1200                Some(Staged {
1201                    metadata: snapshot,
1202                    raw: raw_snapshot,
1203                }),
1204            ),
1205        })
1206    }
1207
1208    /// Validate and write the metadata to the repository.
1209    ///
1210    /// This may stage a snapshot and timestamp metadata if necessary.
1211    ///
1212    /// See [RepoBuilder::commit](#method.commit-4) for more details.
1213    pub async fn commit(self) -> Result<RawSignedMetadataSet<D>> {
1214        self.stage_snapshot_if_necessary()?.commit().await
1215    }
1216
1217    fn need_new_snapshot(&self) -> bool {
1218        // We need a new snapshot metadata if we staged a new root.
1219        if self.state.staged_root.is_some() {
1220            return true;
1221        }
1222
1223        // We need a new snapshot metadata if we staged a new targets.
1224        if self.state.staged_targets.is_some() {
1225            return true;
1226        }
1227
1228        // We need a new snapshot metadata if we don't have a database yet.
1229        let db = if let Some(ref db) = self.ctx.db {
1230            db
1231        } else {
1232            return true;
1233        };
1234
1235        // We need a new snapshot metadata if the database doesn't have a snapshot.
1236        let trusted_snapshot = if let Some(trusted_snapshot) = db.trusted_snapshot() {
1237            trusted_snapshot
1238        } else {
1239            return true;
1240        };
1241
1242        // We need a new snapshot metadata if the metadata expired.
1243        if trusted_snapshot.expires() <= &self.ctx.current_time {
1244            return true;
1245        }
1246
1247        // Otherwise, see if the snapshot keys have changed.
1248        self.ctx.snapshot_keys_changed(db.trusted_root())
1249    }
1250}
1251
1252impl<'a, D, R> RepoBuilder<'a, D, R, Timestamp<D>>
1253where
1254    D: Pouf,
1255    R: RepositoryStorage<D>,
1256{
1257    /// Whether or not to include the length of the snapshot, and any delegated snapshot, in the
1258    /// new snapshot.
1259    pub fn timestamp_includes_length(mut self, include_snapshot_lengths: bool) -> Self {
1260        self.state.include_snapshot_length = include_snapshot_lengths;
1261        self
1262    }
1263
1264    /// Whether or not to include the hashes of the snapshot in the
1265    /// new timestamp.
1266    pub fn timestamp_includes_hashes(mut self, hashes: &[HashAlgorithm]) -> Self {
1267        self.state.snapshot_hash_algorithms = hashes.to_vec();
1268        self
1269    }
1270
1271    /// Stage a timestamp metadata using the default settings.
1272    ///
1273    /// Note: This will also:
1274    /// * stage a root metadata with the default settings if necessary.
1275    /// * stage a targets metadata if necessary.
1276    /// * stage a snapshot metadata if necessary.
1277    pub fn stage_timestamp(self) -> Result<RepoBuilder<'a, D, R, Done<D>>> {
1278        self.stage_timestamp_with_builder(|builder| builder)
1279    }
1280
1281    /// Stage a new timestamp using the default settings if:
1282    ///
1283    /// * There is no trusted timestamp metadata.
1284    /// * The trusted timestamp metadata has expired.
1285    pub fn stage_timestamp_if_necessary(self) -> Result<RepoBuilder<'a, D, R, Done<D>>> {
1286        if self.need_new_timestamp() {
1287            self.stage_timestamp()
1288        } else {
1289            Ok(self.skip_timestamp())
1290        }
1291    }
1292
1293    /// Skip creating the timestamp metadata.
1294    pub fn skip_timestamp(self) -> RepoBuilder<'a, D, R, Done<D>> {
1295        RepoBuilder {
1296            ctx: self.ctx,
1297            state: Done {
1298                staged_root: self.state.staged_root,
1299                staged_targets: self.state.staged_targets,
1300                staged_snapshot: self.state.staged_snapshot,
1301                staged_timestamp: None,
1302            },
1303        }
1304    }
1305
1306    /// Initialize a [TimestampMetadataBuilder] and pass it to the closure for further configuration.
1307    /// This builder will then be used to generate and stage a new [TimestampMetadata] for eventual
1308    /// commitment to the repository.
1309    ///
1310    /// This builder will be initialized with:
1311    ///
1312    /// * version: 1 if a new repository, otherwise 1 past the trusted snapshot's version.
1313    /// * expires: 1 day from the current day.
1314    pub fn stage_timestamp_with_builder<F>(self, f: F) -> Result<RepoBuilder<'a, D, R, Done<D>>>
1315    where
1316        F: FnOnce(TimestampMetadataBuilder) -> TimestampMetadataBuilder,
1317    {
1318        let next_version = if let Some(db) = self.ctx.db {
1319            if let Some(trusted_timestamp) = db.trusted_timestamp() {
1320                self.ctx
1321                    .non_root_next_version(trusted_timestamp.version(), MetadataPath::timestamp)?
1322            } else {
1323                self.ctx.non_root_initial_version()
1324            }
1325        } else {
1326            self.ctx.non_root_initial_version()
1327        };
1328
1329        let description = if let Some(description) = self.state.snapshot_description()? {
1330            description
1331        } else {
1332            self.ctx
1333                .db
1334                .and_then(|db| db.trusted_timestamp())
1335                .map(|timestamp| timestamp.snapshot().clone())
1336                .ok_or_else(|| Error::MetadataNotFound {
1337                    path: MetadataPath::snapshot(),
1338                    version: MetadataVersion::None,
1339                })?
1340        };
1341
1342        let timestamp_builder = TimestampMetadataBuilder::from_metadata_description(description)
1343            .version(next_version)
1344            .expires(self.ctx.current_time + self.ctx.timestamp_expiration_duration);
1345
1346        let timestamp = f(timestamp_builder).build()?;
1347        let raw_timestamp = sign(
1348            &timestamp,
1349            self.ctx
1350                .signing_timestamp_keys
1351                .iter()
1352                .chain(&self.ctx.trusted_timestamp_keys),
1353        )?;
1354
1355        Ok(RepoBuilder {
1356            ctx: self.ctx,
1357            state: Done {
1358                staged_root: self.state.staged_root,
1359                staged_targets: self.state.staged_targets,
1360                staged_snapshot: self.state.staged_snapshot,
1361                staged_timestamp: Some(Staged {
1362                    metadata: timestamp,
1363                    raw: raw_timestamp,
1364                }),
1365            },
1366        })
1367    }
1368
1369    /// See [RepoBuilder::commit](#method.commit-4) for more details.
1370    pub async fn commit(self) -> Result<RawSignedMetadataSet<D>> {
1371        self.stage_timestamp_if_necessary()?.commit().await
1372    }
1373
1374    fn need_new_timestamp(&self) -> bool {
1375        // We need a new timestamp metadata if we staged a new root.
1376        if self.state.staged_root.is_some() {
1377            return true;
1378        }
1379
1380        // We need a new timestamp metadata if we staged a new snapshot.
1381        if self.state.staged_snapshot.is_some() {
1382            return true;
1383        }
1384
1385        // We need a new timestamp metadata if we don't have a database yet.
1386        let db = if let Some(ref db) = self.ctx.db {
1387            db
1388        } else {
1389            return true;
1390        };
1391
1392        // We need a new timestamp metadata if the database doesn't have a timestamp.
1393        let trusted_timestamp = if let Some(trusted_timestamp) = db.trusted_timestamp() {
1394            trusted_timestamp
1395        } else {
1396            return true;
1397        };
1398
1399        // We need a new timestamp metadata if the metadata expired.
1400        if trusted_timestamp.expires() <= &self.ctx.current_time {
1401            return true;
1402        }
1403
1404        // Otherwise, see if the timestamp keys have changed.
1405        self.ctx.timestamp_keys_changed(db.trusted_root())
1406    }
1407}
1408
1409impl<D, R> RepoBuilder<'_, D, R, Done<D>>
1410where
1411    D: Pouf,
1412    R: RepositoryStorage<D>,
1413{
1414    /// Commit the metadata for this repository, then write all metadata to the repository. Before
1415    /// writing the metadata to `repo`, this will test that a client can update to this metadata to
1416    /// make sure it is valid.
1417    pub async fn commit(mut self) -> Result<RawSignedMetadataSet<D>> {
1418        self.validate_built_metadata()?;
1419        self.write_repo().await?;
1420
1421        let mut builder = RawSignedMetadataSetBuilder::new();
1422
1423        if let Some(root) = self.state.staged_root {
1424            builder = builder.root(root.raw);
1425        }
1426
1427        if let Some(targets) = self.state.staged_targets {
1428            builder = builder.targets(targets.raw);
1429        }
1430
1431        if let Some(snapshot) = self.state.staged_snapshot {
1432            builder = builder.snapshot(snapshot.raw);
1433        }
1434
1435        if let Some(timestamp) = self.state.staged_timestamp {
1436            builder = builder.timestamp(timestamp.raw);
1437        }
1438
1439        Ok(builder.build())
1440    }
1441
1442    /// Before we commit any metadata, make sure that we can update from our
1443    /// current TUF database to the latest version.
1444    fn validate_built_metadata(&self) -> Result<()> {
1445        // Use a TUF database to make sure we can update to the metadata we just
1446        // produced. If we were constructed with a database, create a copy of it
1447        // and make sure we can install the update.
1448        let mut db = if let Some(db) = self.ctx.db {
1449            let mut db = db.clone();
1450
1451            if let Some(ref root) = self.state.staged_root {
1452                db.update_root(&root.raw)?;
1453            }
1454
1455            db
1456        } else if let Some(ref root) = self.state.staged_root {
1457            Database::from_trusted_root(&root.raw)?
1458        } else {
1459            return Err(Error::MetadataNotFound {
1460                path: MetadataPath::root(),
1461                version: MetadataVersion::None,
1462            });
1463        };
1464
1465        if let Some(ref timestamp) = self.state.staged_timestamp {
1466            db.update_timestamp(&self.ctx.current_time, &timestamp.raw)?;
1467        }
1468
1469        if let Some(ref snapshot) = self.state.staged_snapshot {
1470            db.update_snapshot(&self.ctx.current_time, &snapshot.raw)?;
1471        }
1472
1473        if let Some(ref targets) = self.state.staged_targets {
1474            db.update_targets(&self.ctx.current_time, &targets.raw)?;
1475        }
1476
1477        Ok(())
1478    }
1479
1480    async fn write_repo(&mut self) -> Result<()> {
1481        let consistent_snapshot = if let Some(ref root) = self.state.staged_root {
1482            self.ctx
1483                .repo
1484                .store_metadata(
1485                    &MetadataPath::root(),
1486                    MetadataVersion::Number(root.metadata.version()),
1487                    &mut root.raw.as_bytes(),
1488                )
1489                .await?;
1490
1491            self.ctx
1492                .repo
1493                .store_metadata(
1494                    &MetadataPath::root(),
1495                    MetadataVersion::None,
1496                    &mut root.raw.as_bytes(),
1497                )
1498                .await?;
1499
1500            root.metadata.consistent_snapshot()
1501        } else if let Some(db) = self.ctx.db {
1502            db.trusted_root().consistent_snapshot()
1503        } else {
1504            return Err(Error::MetadataNotFound {
1505                path: MetadataPath::root(),
1506                version: MetadataVersion::None,
1507            });
1508        };
1509
1510        if let Some(ref targets) = self.state.staged_targets {
1511            let path = MetadataPath::targets();
1512            self.ctx
1513                .repo
1514                .store_metadata(
1515                    &path.clone(),
1516                    MetadataVersion::None,
1517                    &mut targets.raw.as_bytes(),
1518                )
1519                .await?;
1520
1521            if consistent_snapshot {
1522                self.ctx
1523                    .repo
1524                    .store_metadata(
1525                        &path,
1526                        MetadataVersion::Number(targets.metadata.version()),
1527                        &mut targets.raw.as_bytes(),
1528                    )
1529                    .await?;
1530            }
1531        }
1532
1533        if let Some(ref snapshot) = self.state.staged_snapshot {
1534            let path = MetadataPath::snapshot();
1535            self.ctx
1536                .repo
1537                .store_metadata(&path, MetadataVersion::None, &mut snapshot.raw.as_bytes())
1538                .await?;
1539
1540            if consistent_snapshot {
1541                self.ctx
1542                    .repo
1543                    .store_metadata(
1544                        &path,
1545                        MetadataVersion::Number(snapshot.metadata.version()),
1546                        &mut snapshot.raw.as_bytes(),
1547                    )
1548                    .await?;
1549            }
1550        }
1551
1552        if let Some(ref timestamp) = self.state.staged_timestamp {
1553            self.ctx
1554                .repo
1555                .store_metadata(
1556                    &MetadataPath::timestamp(),
1557                    MetadataVersion::None,
1558                    &mut timestamp.raw.as_bytes(),
1559                )
1560                .await?;
1561        }
1562
1563        Ok(())
1564    }
1565}
1566
1567#[cfg(test)]
1568mod tests {
1569    use {
1570        super::*,
1571        crate::{
1572            client::{Client, Config},
1573            crypto::Ed25519PrivateKey,
1574            metadata::SignedMetadata,
1575            pouf::Pouf1,
1576            repository::{EphemeralRepository, RepositoryProvider},
1577        },
1578        assert_matches::assert_matches,
1579        chrono::{
1580            offset::{TimeZone as _, Utc},
1581            DateTime,
1582        },
1583        futures_executor::block_on,
1584        futures_util::io::{AsyncReadExt, Cursor},
1585        maplit::hashmap,
1586        pretty_assertions::assert_eq,
1587        std::collections::BTreeMap,
1588        std::sync::LazyLock,
1589    };
1590
1591    static KEYS: LazyLock<Vec<Ed25519PrivateKey>> = LazyLock::new(|| {
1592        let keys: &[&[u8]] = &[
1593            include_bytes!("../tests/ed25519/ed25519-1.pk8.der"),
1594            include_bytes!("../tests/ed25519/ed25519-2.pk8.der"),
1595            include_bytes!("../tests/ed25519/ed25519-3.pk8.der"),
1596            include_bytes!("../tests/ed25519/ed25519-4.pk8.der"),
1597            include_bytes!("../tests/ed25519/ed25519-5.pk8.der"),
1598            include_bytes!("../tests/ed25519/ed25519-6.pk8.der"),
1599        ];
1600        keys.iter()
1601            .map(|b| Ed25519PrivateKey::from_pkcs8(b).unwrap())
1602            .collect()
1603    });
1604
1605    fn create_root(
1606        version: u32,
1607        consistent_snapshot: bool,
1608        expires: DateTime<Utc>,
1609    ) -> SignedMetadata<Pouf1, RootMetadata> {
1610        let root = RootMetadataBuilder::new()
1611            .version(version)
1612            .consistent_snapshot(consistent_snapshot)
1613            .expires(expires)
1614            .root_threshold(2)
1615            .root_key(KEYS[0].public().clone())
1616            .root_key(KEYS[1].public().clone())
1617            .root_key(KEYS[2].public().clone())
1618            .targets_threshold(2)
1619            .targets_key(KEYS[1].public().clone())
1620            .targets_key(KEYS[2].public().clone())
1621            .targets_key(KEYS[3].public().clone())
1622            .snapshot_threshold(2)
1623            .snapshot_key(KEYS[2].public().clone())
1624            .snapshot_key(KEYS[3].public().clone())
1625            .snapshot_key(KEYS[4].public().clone())
1626            .timestamp_threshold(2)
1627            .timestamp_key(KEYS[3].public().clone())
1628            .timestamp_key(KEYS[4].public().clone())
1629            .timestamp_key(KEYS[5].public().clone())
1630            .build()
1631            .unwrap();
1632
1633        SignedMetadataBuilder::from_metadata(&root)
1634            .unwrap()
1635            .sign(&KEYS[0])
1636            .unwrap()
1637            .sign(&KEYS[1])
1638            .unwrap()
1639            .sign(&KEYS[2])
1640            .unwrap()
1641            .build()
1642    }
1643
1644    fn create_targets(
1645        version: u32,
1646        expires: DateTime<Utc>,
1647    ) -> SignedMetadata<Pouf1, TargetsMetadata> {
1648        let targets = TargetsMetadataBuilder::new()
1649            .version(version)
1650            .expires(expires)
1651            .build()
1652            .unwrap();
1653        SignedMetadataBuilder::<Pouf1, _>::from_metadata(&targets)
1654            .unwrap()
1655            .sign(&KEYS[1])
1656            .unwrap()
1657            .sign(&KEYS[2])
1658            .unwrap()
1659            .sign(&KEYS[3])
1660            .unwrap()
1661            .build()
1662    }
1663
1664    fn create_snapshot(
1665        version: u32,
1666        expires: DateTime<Utc>,
1667        targets: &SignedMetadata<Pouf1, TargetsMetadata>,
1668        include_length_and_hashes: bool,
1669    ) -> SignedMetadata<Pouf1, SnapshotMetadata> {
1670        let description = if include_length_and_hashes {
1671            let raw_targets = targets.to_raw().unwrap();
1672            let hashes = crypto::calculate_hashes_from_slice(
1673                raw_targets.as_bytes(),
1674                &[HashAlgorithm::Sha256],
1675            )
1676            .unwrap();
1677
1678            MetadataDescription::new(version, Some(raw_targets.as_bytes().len()), hashes).unwrap()
1679        } else {
1680            MetadataDescription::new(version, None, HashMap::new()).unwrap()
1681        };
1682
1683        let snapshot = SnapshotMetadataBuilder::new()
1684            .insert_metadata_description(MetadataPath::targets(), description)
1685            .version(version)
1686            .expires(expires)
1687            .build()
1688            .unwrap();
1689        SignedMetadataBuilder::<Pouf1, _>::from_metadata(&snapshot)
1690            .unwrap()
1691            .sign(&KEYS[2])
1692            .unwrap()
1693            .sign(&KEYS[3])
1694            .unwrap()
1695            .sign(&KEYS[4])
1696            .unwrap()
1697            .build()
1698    }
1699
1700    fn create_timestamp(
1701        version: u32,
1702        expires: DateTime<Utc>,
1703        snapshot: &SignedMetadata<Pouf1, SnapshotMetadata>,
1704        include_length_and_hashes: bool,
1705    ) -> SignedMetadata<Pouf1, TimestampMetadata> {
1706        let description = if include_length_and_hashes {
1707            let raw_snapshot = snapshot.to_raw().unwrap();
1708            let hashes = crypto::calculate_hashes_from_slice(
1709                raw_snapshot.as_bytes(),
1710                &[HashAlgorithm::Sha256],
1711            )
1712            .unwrap();
1713
1714            MetadataDescription::new(version, Some(raw_snapshot.as_bytes().len()), hashes).unwrap()
1715        } else {
1716            MetadataDescription::new(version, None, HashMap::new()).unwrap()
1717        };
1718
1719        let timestamp = TimestampMetadataBuilder::from_metadata_description(description)
1720            .version(version)
1721            .expires(expires)
1722            .build()
1723            .unwrap();
1724        SignedMetadataBuilder::<Pouf1, _>::from_metadata(&timestamp)
1725            .unwrap()
1726            .sign(&KEYS[3])
1727            .unwrap()
1728            .sign(&KEYS[4])
1729            .unwrap()
1730            .sign(&KEYS[5])
1731            .unwrap()
1732            .build()
1733    }
1734
1735    fn assert_metadata(
1736        metadata: &RawSignedMetadataSet<Pouf1>,
1737        expected_root: Option<&RawSignedMetadata<Pouf1, RootMetadata>>,
1738        expected_targets: Option<&RawSignedMetadata<Pouf1, TargetsMetadata>>,
1739        expected_snapshot: Option<&RawSignedMetadata<Pouf1, SnapshotMetadata>>,
1740        expected_timestamp: Option<&RawSignedMetadata<Pouf1, TimestampMetadata>>,
1741    ) {
1742        assert_eq!(
1743            metadata.root().map(|m| m.parse_untrusted().unwrap()),
1744            expected_root.map(|m| m.parse_untrusted().unwrap())
1745        );
1746        assert_eq!(
1747            metadata.targets().map(|m| m.parse_untrusted().unwrap()),
1748            expected_targets.map(|m| m.parse_untrusted().unwrap())
1749        );
1750        assert_eq!(
1751            metadata.snapshot().map(|m| m.parse_untrusted().unwrap()),
1752            expected_snapshot.map(|m| m.parse_untrusted().unwrap())
1753        );
1754        assert_eq!(
1755            metadata.timestamp().map(|m| m.parse_untrusted().unwrap()),
1756            expected_timestamp.map(|m| m.parse_untrusted().unwrap())
1757        );
1758    }
1759
1760    fn assert_repo(
1761        repo: &EphemeralRepository<Pouf1>,
1762        expected_metadata: &BTreeMap<(MetadataPath, MetadataVersion), &[u8]>,
1763    ) {
1764        let actual_metadata = repo
1765            .metadata()
1766            .iter()
1767            .map(|(k, v)| (k.clone(), String::from_utf8_lossy(v).to_string()))
1768            .collect::<BTreeMap<_, _>>();
1769
1770        let expected_metadata = expected_metadata
1771            .iter()
1772            .map(|(k, v)| (k.clone(), String::from_utf8_lossy(v).to_string()))
1773            .collect::<BTreeMap<_, _>>();
1774
1775        assert_eq!(
1776            actual_metadata.keys().collect::<Vec<_>>(),
1777            expected_metadata.keys().collect::<Vec<_>>()
1778        );
1779        assert_eq!(actual_metadata, expected_metadata);
1780    }
1781
1782    #[test]
1783    fn test_stage_and_update_repo_not_consistent_snapshot() {
1784        block_on(check_stage_and_update_repo(false));
1785    }
1786
1787    #[test]
1788    fn test_stage_and_update_repo_consistent_snapshot() {
1789        block_on(check_stage_and_update_repo(true));
1790    }
1791
1792    async fn check_stage_and_update_repo(consistent_snapshot: bool) {
1793        // We'll write all the metadata to this remote repository.
1794        let mut remote = EphemeralRepository::<Pouf1>::new();
1795
1796        // First, create the metadata.
1797        let expires1 = Utc.with_ymd_and_hms(2038, 1, 1, 0, 0, 0).unwrap();
1798        let metadata1 = RepoBuilder::create(&mut remote)
1799            .trusted_root_keys(&[&KEYS[0], &KEYS[1], &KEYS[2]])
1800            .trusted_targets_keys(&[&KEYS[1], &KEYS[2], &KEYS[3]])
1801            .trusted_snapshot_keys(&[&KEYS[2], &KEYS[3], &KEYS[4]])
1802            .trusted_timestamp_keys(&[&KEYS[3], &KEYS[4], &KEYS[5]])
1803            .stage_root_with_builder(|builder| {
1804                builder
1805                    .expires(expires1)
1806                    .consistent_snapshot(consistent_snapshot)
1807                    .root_threshold(2)
1808                    .targets_threshold(2)
1809                    .snapshot_threshold(2)
1810                    .timestamp_threshold(2)
1811            })
1812            .unwrap()
1813            .stage_targets_with_builder(|builder| builder.expires(expires1))
1814            .unwrap()
1815            .snapshot_includes_length(true)
1816            .snapshot_includes_hashes(&[HashAlgorithm::Sha256])
1817            .stage_snapshot_with_builder(|builder| builder.expires(expires1))
1818            .unwrap()
1819            .timestamp_includes_length(true)
1820            .timestamp_includes_hashes(&[HashAlgorithm::Sha256])
1821            .stage_timestamp_with_builder(|builder| builder.expires(expires1))
1822            .unwrap()
1823            .commit()
1824            .await
1825            .unwrap();
1826
1827        // Generate the expected metadata by hand, and make sure we produced
1828        // what we expected.
1829        let signed_root1 = create_root(1, consistent_snapshot, expires1);
1830        let signed_targets1 = create_targets(1, expires1);
1831        let signed_snapshot1 = create_snapshot(1, expires1, &signed_targets1, true);
1832        let signed_timestamp1 = create_timestamp(1, expires1, &signed_snapshot1, true);
1833
1834        let raw_root1 = signed_root1.to_raw().unwrap();
1835        let raw_targets1 = signed_targets1.to_raw().unwrap();
1836        let raw_snapshot1 = signed_snapshot1.to_raw().unwrap();
1837        let raw_timestamp1 = signed_timestamp1.to_raw().unwrap();
1838
1839        assert_metadata(
1840            &metadata1,
1841            Some(&raw_root1),
1842            Some(&raw_targets1),
1843            Some(&raw_snapshot1),
1844            Some(&raw_timestamp1),
1845        );
1846
1847        // Make sure we stored the metadata correctly.
1848        let mut expected_metadata: BTreeMap<_, _> = vec![
1849            (
1850                (MetadataPath::root(), MetadataVersion::Number(1)),
1851                raw_root1.as_bytes(),
1852            ),
1853            (
1854                (MetadataPath::root(), MetadataVersion::None),
1855                raw_root1.as_bytes(),
1856            ),
1857            (
1858                (MetadataPath::targets(), MetadataVersion::None),
1859                raw_targets1.as_bytes(),
1860            ),
1861            (
1862                (MetadataPath::snapshot(), MetadataVersion::None),
1863                raw_snapshot1.as_bytes(),
1864            ),
1865            (
1866                (MetadataPath::timestamp(), MetadataVersion::None),
1867                raw_timestamp1.as_bytes(),
1868            ),
1869        ]
1870        .into_iter()
1871        .collect();
1872
1873        if consistent_snapshot {
1874            expected_metadata.extend(vec![
1875                (
1876                    (MetadataPath::targets(), MetadataVersion::Number(1)),
1877                    raw_targets1.as_bytes(),
1878                ),
1879                (
1880                    (MetadataPath::snapshot(), MetadataVersion::Number(1)),
1881                    raw_snapshot1.as_bytes(),
1882                ),
1883            ]);
1884        }
1885
1886        assert_repo(&remote, &expected_metadata);
1887
1888        // Create a client, and make sure we can update to the version we
1889        // just made.
1890        let mut client = Client::with_trusted_root(
1891            Config::default(),
1892            metadata1.root().unwrap(),
1893            EphemeralRepository::new(),
1894            remote,
1895        )
1896        .await
1897        .unwrap();
1898        client.update().await.unwrap();
1899        assert_eq!(client.database().trusted_root().version(), 1);
1900        assert_eq!(
1901            client.database().trusted_targets().map(|m| m.version()),
1902            Some(1)
1903        );
1904        assert_eq!(
1905            client.database().trusted_snapshot().map(|m| m.version()),
1906            Some(1)
1907        );
1908        assert_eq!(
1909            client.database().trusted_timestamp().map(|m| m.version()),
1910            Some(1)
1911        );
1912
1913        // Create a new metadata, derived from the tuf database we created
1914        // with the client.
1915        let expires2 = Utc.with_ymd_and_hms(2038, 1, 2, 0, 0, 0).unwrap();
1916        let mut parts = client.into_parts();
1917        let metadata2 = RepoBuilder::from_database(&mut parts.remote, &parts.database)
1918            .trusted_root_keys(&[&KEYS[0], &KEYS[1], &KEYS[2]])
1919            .trusted_targets_keys(&[&KEYS[1], &KEYS[2], &KEYS[3]])
1920            .trusted_snapshot_keys(&[&KEYS[2], &KEYS[3], &KEYS[4]])
1921            .trusted_timestamp_keys(&[&KEYS[3], &KEYS[4], &KEYS[5]])
1922            .stage_root_with_builder(|builder| builder.expires(expires2))
1923            .unwrap()
1924            .stage_targets_with_builder(|builder| builder.expires(expires2))
1925            .unwrap()
1926            .snapshot_includes_length(false)
1927            .snapshot_includes_hashes(&[])
1928            .stage_snapshot_with_builder(|builder| builder.expires(expires2))
1929            .unwrap()
1930            .timestamp_includes_length(false)
1931            .timestamp_includes_hashes(&[])
1932            .stage_timestamp_with_builder(|builder| builder.expires(expires2))
1933            .unwrap()
1934            .commit()
1935            .await
1936            .unwrap();
1937
1938        // Make sure the new metadata was generated as expected.
1939        let signed_root2 = create_root(2, consistent_snapshot, expires2);
1940        let signed_targets2 = create_targets(2, expires2);
1941        let signed_snapshot2 = create_snapshot(2, expires2, &signed_targets2, false);
1942        let signed_timestamp2 = create_timestamp(2, expires2, &signed_snapshot2, false);
1943
1944        let raw_root2 = signed_root2.to_raw().unwrap();
1945        let raw_targets2 = signed_targets2.to_raw().unwrap();
1946        let raw_snapshot2 = signed_snapshot2.to_raw().unwrap();
1947        let raw_timestamp2 = signed_timestamp2.to_raw().unwrap();
1948
1949        assert_metadata(
1950            &metadata2,
1951            Some(&raw_root2),
1952            Some(&raw_targets2),
1953            Some(&raw_snapshot2),
1954            Some(&raw_timestamp2),
1955        );
1956
1957        // Check that the new metadata was written.
1958        expected_metadata.extend(vec![
1959            (
1960                (MetadataPath::root(), MetadataVersion::Number(2)),
1961                raw_root2.as_bytes(),
1962            ),
1963            (
1964                (MetadataPath::root(), MetadataVersion::None),
1965                raw_root2.as_bytes(),
1966            ),
1967            (
1968                (MetadataPath::targets(), MetadataVersion::None),
1969                raw_targets2.as_bytes(),
1970            ),
1971            (
1972                (MetadataPath::snapshot(), MetadataVersion::None),
1973                raw_snapshot2.as_bytes(),
1974            ),
1975            (
1976                (MetadataPath::timestamp(), MetadataVersion::None),
1977                raw_timestamp2.as_bytes(),
1978            ),
1979        ]);
1980
1981        if consistent_snapshot {
1982            expected_metadata.extend(vec![
1983                (
1984                    (MetadataPath::targets(), MetadataVersion::Number(2)),
1985                    raw_targets2.as_bytes(),
1986                ),
1987                (
1988                    (MetadataPath::snapshot(), MetadataVersion::Number(2)),
1989                    raw_snapshot2.as_bytes(),
1990                ),
1991            ]);
1992        }
1993
1994        assert_repo(&parts.remote, &expected_metadata);
1995
1996        // And make sure the client can update to the latest metadata.
1997        let mut client = Client::from_parts(parts);
1998        client.update().await.unwrap();
1999        assert_eq!(client.database().trusted_root().version(), 2);
2000        assert_eq!(
2001            client.database().trusted_targets().map(|m| m.version()),
2002            Some(2)
2003        );
2004        assert_eq!(
2005            client.database().trusted_snapshot().map(|m| m.version()),
2006            Some(2)
2007        );
2008        assert_eq!(
2009            client.database().trusted_timestamp().map(|m| m.version()),
2010            Some(2)
2011        );
2012    }
2013
2014    #[test]
2015    fn commit_does_nothing_if_nothing_changed_not_consistent_snapshot() {
2016        block_on(commit_does_nothing_if_nothing_changed(false))
2017    }
2018
2019    #[test]
2020    fn commit_does_nothing_if_nothing_changed_consistent_snapshot() {
2021        block_on(commit_does_nothing_if_nothing_changed(true))
2022    }
2023
2024    async fn commit_does_nothing_if_nothing_changed(consistent_snapshot: bool) {
2025        let mut repo = EphemeralRepository::<Pouf1>::new();
2026        let metadata1 = RepoBuilder::create(&mut repo)
2027            .trusted_root_keys(&[&KEYS[0]])
2028            .trusted_targets_keys(&[&KEYS[0]])
2029            .trusted_snapshot_keys(&[&KEYS[0]])
2030            .trusted_timestamp_keys(&[&KEYS[0]])
2031            .stage_root_with_builder(|builder| builder.consistent_snapshot(consistent_snapshot))
2032            .unwrap()
2033            .commit()
2034            .await
2035            .unwrap();
2036
2037        let client_repo = EphemeralRepository::new();
2038        let mut client = Client::with_trusted_root(
2039            Config::default(),
2040            metadata1.root().unwrap(),
2041            client_repo,
2042            repo,
2043        )
2044        .await
2045        .unwrap();
2046
2047        assert!(client.update().await.unwrap());
2048        assert_eq!(client.database().trusted_root().version(), 1);
2049
2050        // Make sure doing another commit makes no changes.
2051        let mut parts = client.into_parts();
2052        let metadata2 = RepoBuilder::from_database(&mut parts.remote, &parts.database)
2053            .trusted_root_keys(&[&KEYS[0]])
2054            .trusted_targets_keys(&[&KEYS[0]])
2055            .trusted_snapshot_keys(&[&KEYS[0]])
2056            .trusted_timestamp_keys(&[&KEYS[0]])
2057            .commit()
2058            .await
2059            .unwrap();
2060
2061        assert_metadata(&metadata2, None, None, None, None);
2062
2063        let mut client = Client::from_parts(parts);
2064        assert!(!client.update().await.unwrap());
2065        assert_eq!(client.database().trusted_root().version(), 1);
2066    }
2067
2068    #[test]
2069    fn root_chain_update_not_consistent() {
2070        block_on(check_root_chain_update(false));
2071    }
2072
2073    #[test]
2074    fn root_chain_update_consistent() {
2075        block_on(check_root_chain_update(true));
2076    }
2077
2078    async fn check_root_chain_update(consistent_snapshot: bool) {
2079        let mut repo = EphemeralRepository::<Pouf1>::new();
2080
2081        // First, create the initial metadata. We initially sign the root
2082        // metadata with key 1.
2083        let metadata1 = RepoBuilder::create(&mut repo)
2084            .trusted_root_keys(&[&KEYS[1]])
2085            .trusted_targets_keys(&[&KEYS[0]])
2086            .trusted_snapshot_keys(&[&KEYS[0]])
2087            .trusted_timestamp_keys(&[&KEYS[0]])
2088            .stage_root_with_builder(|builder| builder.consistent_snapshot(consistent_snapshot))
2089            .unwrap()
2090            .commit()
2091            .await
2092            .unwrap();
2093
2094        let client_repo = EphemeralRepository::new();
2095        let mut client = Client::with_trusted_root(
2096            Config::default(),
2097            metadata1.root().unwrap(),
2098            client_repo,
2099            repo,
2100        )
2101        .await
2102        .unwrap();
2103
2104        assert!(client.update().await.unwrap());
2105        assert_eq!(client.database().trusted_root().version(), 1);
2106        assert_eq!(
2107            client
2108                .database()
2109                .trusted_root()
2110                .root_keys()
2111                .collect::<Vec<_>>(),
2112            vec![KEYS[1].public()],
2113        );
2114
2115        // Another update should not fetch anything.
2116        assert!(!client.update().await.unwrap());
2117        assert_eq!(client.database().trusted_root().version(), 1);
2118
2119        // Now bump the root to version 2. We sign the root metadata with both
2120        // key 1 and 2, but the builder should only trust key 2.
2121        let mut parts = client.into_parts();
2122        let _metadata2 = RepoBuilder::from_database(&mut parts.remote, &parts.database)
2123            .signing_root_keys(&[&KEYS[1]])
2124            .trusted_root_keys(&[&KEYS[2]])
2125            .trusted_targets_keys(&[&KEYS[0]])
2126            .trusted_snapshot_keys(&[&KEYS[0]])
2127            .trusted_timestamp_keys(&[&KEYS[0]])
2128            .commit()
2129            .await
2130            .unwrap();
2131
2132        let mut client = Client::from_parts(parts);
2133        assert!(client.update().await.unwrap());
2134        assert_eq!(client.database().trusted_root().version(), 2);
2135        assert_eq!(
2136            client.database().trusted_root().consistent_snapshot(),
2137            consistent_snapshot
2138        );
2139        assert_eq!(
2140            client
2141                .database()
2142                .trusted_root()
2143                .root_keys()
2144                .collect::<Vec<_>>(),
2145            vec![KEYS[2].public()],
2146        );
2147
2148        // Another update should not fetch anything.
2149        assert!(!client.update().await.unwrap());
2150        assert_eq!(client.database().trusted_root().version(), 2);
2151
2152        // Now bump the root to version 3. The metadata will only be signed with
2153        // key 2, and trusted by key 2.
2154        let mut parts = client.into_parts();
2155        let _metadata3 = RepoBuilder::from_database(&mut parts.remote, &parts.database)
2156            .trusted_root_keys(&[&KEYS[2]])
2157            .trusted_targets_keys(&[&KEYS[0]])
2158            .trusted_snapshot_keys(&[&KEYS[0]])
2159            .trusted_timestamp_keys(&[&KEYS[0]])
2160            .stage_root()
2161            .unwrap()
2162            .commit()
2163            .await
2164            .unwrap();
2165
2166        let mut client = Client::from_parts(parts);
2167        assert!(client.update().await.unwrap());
2168        assert_eq!(client.database().trusted_root().version(), 3);
2169        assert_eq!(
2170            client
2171                .database()
2172                .trusted_root()
2173                .root_keys()
2174                .collect::<Vec<_>>(),
2175            vec![KEYS[2].public()],
2176        );
2177
2178        // Another update should not fetch anything.
2179        assert!(!client.update().await.unwrap());
2180        assert_eq!(client.database().trusted_root().version(), 3);
2181    }
2182
2183    #[test]
2184    fn test_from_database_root_must_be_one_after_the_last() {
2185        block_on(async {
2186            let mut repo = EphemeralRepository::<Pouf1>::new();
2187            let metadata = RepoBuilder::create(&mut repo)
2188                .trusted_root_keys(&[&KEYS[0]])
2189                .trusted_targets_keys(&[&KEYS[0]])
2190                .trusted_snapshot_keys(&[&KEYS[0]])
2191                .trusted_timestamp_keys(&[&KEYS[0]])
2192                .commit()
2193                .await
2194                .unwrap();
2195
2196            let db = Database::from_trusted_metadata(&metadata).unwrap();
2197
2198            assert_matches!(
2199                RepoBuilder::from_database(&mut repo, &db)
2200                    .trusted_root_keys(&[&KEYS[0]])
2201                    .trusted_targets_keys(&[&KEYS[0]])
2202                    .trusted_snapshot_keys(&[&KEYS[0]])
2203                    .trusted_timestamp_keys(&[&KEYS[0]])
2204                    .stage_root_with_builder(|builder| builder.version(3))
2205                    .unwrap()
2206                    .commit()
2207                    .await,
2208                Err(Error::AttemptedMetadataRollBack {
2209                    role,
2210                    trusted_version: 1,
2211                    new_version: 3,
2212                })
2213                if role == MetadataPath::root()
2214            );
2215        })
2216    }
2217
2218    #[test]
2219    fn test_add_target_not_consistent_snapshot() {
2220        block_on(async move {
2221            let mut repo = EphemeralRepository::<Pouf1>::new();
2222
2223            let hash_algs = &[HashAlgorithm::Sha256, HashAlgorithm::Sha512];
2224
2225            let target_path1 = TargetPath::new("foo/default").unwrap();
2226            let target_path1_hashed = TargetPath::new(
2227                "foo/522dd05a607a520657daa19c061a0271224030307117c2e661505e14601d1e44.default",
2228            )
2229            .unwrap();
2230            let target_file1: &[u8] = b"things fade, alternatives exclude";
2231
2232            let target_path2 = TargetPath::new("foo/custom").unwrap();
2233            let target_path2_hashed = TargetPath::new(
2234                "foo/b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9.custom",
2235            )
2236            .unwrap();
2237            let target_file2: &[u8] = b"hello world";
2238
2239            let metadata = RepoBuilder::create(&mut repo)
2240                .trusted_root_keys(&[&KEYS[0]])
2241                .trusted_targets_keys(&[&KEYS[0]])
2242                .trusted_snapshot_keys(&[&KEYS[0]])
2243                .trusted_timestamp_keys(&[&KEYS[0]])
2244                .add_target(target_path1.clone(), Cursor::new(target_file1))
2245                .await
2246                .unwrap()
2247                .target_hash_algorithms(hash_algs)
2248                .add_target(target_path2.clone(), Cursor::new(target_file2))
2249                .await
2250                .unwrap()
2251                .commit()
2252                .await
2253                .unwrap();
2254
2255            // Make sure the targets were written correctly.
2256            let mut rdr = repo.fetch_target(&target_path1_hashed).await.unwrap();
2257            let mut buf = vec![];
2258            rdr.read_to_end(&mut buf).await.unwrap();
2259            drop(rdr);
2260
2261            assert_eq!(&buf, target_file1);
2262
2263            let mut rdr = repo.fetch_target(&target_path2_hashed).await.unwrap();
2264            let mut buf = vec![];
2265            rdr.read_to_end(&mut buf).await.unwrap();
2266            drop(rdr);
2267
2268            assert_eq!(&buf, target_file2);
2269
2270            let mut client = Client::with_trusted_root(
2271                Config::default(),
2272                metadata.root().unwrap(),
2273                EphemeralRepository::new(),
2274                repo,
2275            )
2276            .await
2277            .unwrap();
2278
2279            client.update().await.unwrap();
2280
2281            // Make sure the target descriptions are correct.
2282            assert_eq!(
2283                client
2284                    .fetch_target_description(&target_path1)
2285                    .await
2286                    .unwrap(),
2287                TargetDescription::from_slice(target_file1, &[HashAlgorithm::Sha256]).unwrap(),
2288            );
2289
2290            assert_eq!(
2291                client
2292                    .fetch_target_description(&target_path2)
2293                    .await
2294                    .unwrap(),
2295                TargetDescription::from_slice(target_file2, hash_algs).unwrap(),
2296            );
2297
2298            // Make sure we can fetch the targets.
2299            let mut rdr = client.fetch_target(&target_path1).await.unwrap();
2300            let mut buf = vec![];
2301            rdr.read_to_end(&mut buf).await.unwrap();
2302            assert_eq!(&buf, target_file1);
2303            drop(rdr);
2304
2305            let mut rdr = client.fetch_target(&target_path2).await.unwrap();
2306            let mut buf = vec![];
2307            rdr.read_to_end(&mut buf).await.unwrap();
2308            assert_eq!(&buf, target_file2);
2309        })
2310    }
2311
2312    #[test]
2313    fn test_add_target_consistent_snapshot() {
2314        block_on(async move {
2315            let mut repo = EphemeralRepository::<Pouf1>::new();
2316
2317            let hash_algs = &[HashAlgorithm::Sha256, HashAlgorithm::Sha512];
2318
2319            let target_path1 = TargetPath::new("foo/bar").unwrap();
2320            let target_file1: &[u8] = b"things fade, alternatives exclude";
2321
2322            let target_path2 = TargetPath::new("baz").unwrap();
2323            let target_file2: &[u8] = b"hello world";
2324            let target_custom2 = hashmap! {
2325                "hello".into() => "world".into(),
2326            };
2327
2328            let metadata = RepoBuilder::create(&mut repo)
2329                .trusted_root_keys(&[&KEYS[0]])
2330                .trusted_targets_keys(&[&KEYS[0]])
2331                .trusted_snapshot_keys(&[&KEYS[0]])
2332                .trusted_timestamp_keys(&[&KEYS[0]])
2333                .stage_root_with_builder(|builder| builder.consistent_snapshot(true))
2334                .unwrap()
2335                .target_hash_algorithms(hash_algs)
2336                .add_target(target_path1.clone(), Cursor::new(target_file1))
2337                .await
2338                .unwrap()
2339                .add_target_with_custom(
2340                    target_path2.clone(),
2341                    Cursor::new(target_file2),
2342                    target_custom2.clone(),
2343                )
2344                .await
2345                .unwrap()
2346                .commit()
2347                .await
2348                .unwrap();
2349
2350            // Make sure the target was written correctly with hash prefixes.
2351            for (target_path, target_file) in &[
2352                (&target_path1, &target_file1),
2353                (&target_path2, &target_file2),
2354            ] {
2355                for hash_alg in hash_algs {
2356                    let hash = crypto::calculate_hash(target_file, hash_alg);
2357                    let target_path = target_path.with_hash_prefix(&hash).unwrap();
2358
2359                    let mut rdr = repo.fetch_target(&target_path).await.unwrap();
2360                    let mut buf = vec![];
2361                    rdr.read_to_end(&mut buf).await.unwrap();
2362
2363                    assert_eq!(&buf, *target_file);
2364                }
2365            }
2366
2367            let mut client = Client::with_trusted_root(
2368                Config::default(),
2369                metadata.root().unwrap(),
2370                EphemeralRepository::new(),
2371                repo,
2372            )
2373            .await
2374            .unwrap();
2375
2376            client.update().await.unwrap();
2377
2378            // Make sure the target descriptions ar correct.
2379            assert_eq!(
2380                client
2381                    .fetch_target_description(&target_path1)
2382                    .await
2383                    .unwrap(),
2384                TargetDescription::from_slice(target_file1, hash_algs).unwrap(),
2385            );
2386
2387            assert_eq!(
2388                client
2389                    .fetch_target_description(&target_path2)
2390                    .await
2391                    .unwrap(),
2392                TargetDescription::from_slice_with_custom(target_file2, hash_algs, target_custom2)
2393                    .unwrap(),
2394            );
2395
2396            // Make sure we can fetch the targets.
2397            for (target_path, target_file) in &[
2398                (&target_path1, &target_file1),
2399                (&target_path2, &target_file2),
2400            ] {
2401                let mut rdr = client.fetch_target(target_path).await.unwrap();
2402                let mut buf = vec![];
2403                rdr.read_to_end(&mut buf).await.unwrap();
2404                assert_eq!(&buf, *target_file);
2405            }
2406        })
2407    }
2408
2409    #[test]
2410    fn test_do_not_require_all_keys_to_be_online() {
2411        block_on(async {
2412            let mut remote = EphemeralRepository::<Pouf1>::new();
2413
2414            // First, write some metadata to the repo.
2415            let expires1 = Utc.with_ymd_and_hms(2038, 1, 1, 0, 0, 0).unwrap();
2416            let metadata1 = RepoBuilder::create(&mut remote)
2417                .trusted_root_keys(&[&KEYS[0]])
2418                .trusted_targets_keys(&[&KEYS[1]])
2419                .trusted_snapshot_keys(&[&KEYS[2]])
2420                .trusted_timestamp_keys(&[&KEYS[3]])
2421                .stage_root_with_builder(|builder| {
2422                    builder.consistent_snapshot(true).expires(expires1)
2423                })
2424                .unwrap()
2425                .stage_targets_with_builder(|builder| builder.expires(expires1))
2426                .unwrap()
2427                .stage_snapshot_with_builder(|builder| builder.expires(expires1))
2428                .unwrap()
2429                .stage_timestamp_with_builder(|builder| builder.expires(expires1))
2430                .unwrap()
2431                .commit()
2432                .await
2433                .unwrap();
2434
2435            // We wrote all the metadata.
2436            assert!(metadata1.root().is_some());
2437            assert!(metadata1.timestamp().is_some());
2438            assert!(metadata1.snapshot().is_some());
2439            assert!(metadata1.targets().is_some());
2440
2441            let mut expected_metadata: BTreeMap<_, _> = vec![
2442                (
2443                    (MetadataPath::root(), MetadataVersion::Number(1)),
2444                    metadata1.root().unwrap().as_bytes(),
2445                ),
2446                (
2447                    (MetadataPath::root(), MetadataVersion::None),
2448                    metadata1.root().unwrap().as_bytes(),
2449                ),
2450                (
2451                    (MetadataPath::targets(), MetadataVersion::Number(1)),
2452                    metadata1.targets().unwrap().as_bytes(),
2453                ),
2454                (
2455                    (MetadataPath::targets(), MetadataVersion::None),
2456                    metadata1.targets().unwrap().as_bytes(),
2457                ),
2458                (
2459                    (MetadataPath::snapshot(), MetadataVersion::Number(1)),
2460                    metadata1.snapshot().unwrap().as_bytes(),
2461                ),
2462                (
2463                    (MetadataPath::snapshot(), MetadataVersion::None),
2464                    metadata1.snapshot().unwrap().as_bytes(),
2465                ),
2466                (
2467                    (MetadataPath::timestamp(), MetadataVersion::None),
2468                    metadata1.timestamp().unwrap().as_bytes(),
2469                ),
2470            ]
2471            .into_iter()
2472            .collect();
2473
2474            assert_repo(&remote, &expected_metadata);
2475
2476            let mut db = Database::from_trusted_metadata(&metadata1).unwrap();
2477
2478            // Next, write another batch, but only have the timestamp, snapshot, and targets keys.
2479            let expires2 = Utc.with_ymd_and_hms(2038, 1, 2, 0, 0, 0).unwrap();
2480            let metadata2 = RepoBuilder::from_database(&mut remote, &db)
2481                .trusted_targets_keys(&[&KEYS[1]])
2482                .trusted_snapshot_keys(&[&KEYS[2]])
2483                .trusted_timestamp_keys(&[&KEYS[3]])
2484                .skip_root()
2485                .stage_targets_with_builder(|builder| builder.expires(expires2))
2486                .unwrap()
2487                .stage_snapshot_with_builder(|builder| builder.expires(expires2))
2488                .unwrap()
2489                .stage_timestamp_with_builder(|builder| builder.expires(expires2))
2490                .unwrap()
2491                .commit()
2492                .await
2493                .unwrap();
2494
2495            assert!(db.update_metadata(&metadata2).unwrap());
2496
2497            assert!(metadata2.root().is_none());
2498            assert!(metadata2.targets().is_some());
2499            assert!(metadata2.snapshot().is_some());
2500            assert!(metadata2.timestamp().is_some());
2501
2502            expected_metadata.extend(vec![
2503                (
2504                    (MetadataPath::targets(), MetadataVersion::Number(2)),
2505                    metadata2.targets().unwrap().as_bytes(),
2506                ),
2507                (
2508                    (MetadataPath::targets(), MetadataVersion::None),
2509                    metadata2.targets().unwrap().as_bytes(),
2510                ),
2511                (
2512                    (MetadataPath::snapshot(), MetadataVersion::Number(2)),
2513                    metadata2.snapshot().unwrap().as_bytes(),
2514                ),
2515                (
2516                    (MetadataPath::snapshot(), MetadataVersion::None),
2517                    metadata2.snapshot().unwrap().as_bytes(),
2518                ),
2519                (
2520                    (MetadataPath::timestamp(), MetadataVersion::None),
2521                    metadata2.timestamp().unwrap().as_bytes(),
2522                ),
2523            ]);
2524
2525            assert_repo(&remote, &expected_metadata);
2526
2527            // Now, only have the timestamp and snapshot keys online.
2528            let expires3 = Utc.with_ymd_and_hms(2038, 1, 3, 0, 0, 0).unwrap();
2529            let metadata3 = RepoBuilder::from_database(&mut remote, &db)
2530                .trusted_snapshot_keys(&[&KEYS[2]])
2531                .trusted_timestamp_keys(&[&KEYS[3]])
2532                .skip_root()
2533                .skip_targets()
2534                .stage_snapshot_with_builder(|builder| builder.expires(expires3))
2535                .unwrap()
2536                .stage_timestamp_with_builder(|builder| builder.expires(expires3))
2537                .unwrap()
2538                .commit()
2539                .await
2540                .unwrap();
2541
2542            assert!(db.update_metadata(&metadata3).unwrap());
2543
2544            // We only have timestamp and snapshot.
2545            assert!(metadata3.root().is_none());
2546            assert!(metadata3.targets().is_none());
2547            assert!(metadata3.snapshot().is_some());
2548            assert!(metadata3.timestamp().is_some());
2549
2550            expected_metadata.extend(vec![
2551                (
2552                    (MetadataPath::snapshot(), MetadataVersion::Number(3)),
2553                    metadata3.snapshot().unwrap().as_bytes(),
2554                ),
2555                (
2556                    (MetadataPath::snapshot(), MetadataVersion::None),
2557                    metadata3.snapshot().unwrap().as_bytes(),
2558                ),
2559                (
2560                    (MetadataPath::timestamp(), MetadataVersion::None),
2561                    metadata3.timestamp().unwrap().as_bytes(),
2562                ),
2563            ]);
2564
2565            assert_repo(&remote, &expected_metadata);
2566
2567            // Finally, only have the timestamp keys online.
2568            let expires4 = Utc.with_ymd_and_hms(2038, 1, 4, 0, 0, 0).unwrap();
2569            let metadata4 = RepoBuilder::from_database(&mut remote, &db)
2570                .trusted_timestamp_keys(&[&KEYS[3]])
2571                .skip_root()
2572                .skip_targets()
2573                .skip_snapshot()
2574                .stage_timestamp_with_builder(|builder| builder.expires(expires4))
2575                .unwrap()
2576                .commit()
2577                .await
2578                .unwrap();
2579
2580            assert!(db.update_metadata(&metadata4).unwrap());
2581
2582            // We only have timestamp and snapshot.
2583            assert!(metadata4.root().is_none());
2584            assert!(metadata4.targets().is_none());
2585            assert!(metadata4.snapshot().is_none());
2586            assert!(metadata4.timestamp().is_some());
2587
2588            expected_metadata.extend(vec![(
2589                (MetadataPath::timestamp(), MetadataVersion::None),
2590                metadata4.timestamp().unwrap().as_bytes(),
2591            )]);
2592
2593            assert_repo(&remote, &expected_metadata);
2594        })
2595    }
2596
2597    #[test]
2598    fn test_builder_inherits_from_trusted_targets() {
2599        block_on(async move {
2600            let mut repo = EphemeralRepository::<Pouf1>::new();
2601
2602            let expires = Utc.with_ymd_and_hms(2038, 1, 4, 0, 0, 0).unwrap();
2603            let hash_algs = &[HashAlgorithm::Sha256, HashAlgorithm::Sha512];
2604            let delegation_key = &KEYS[0];
2605            let delegation_path = MetadataPath::new("delegations").unwrap();
2606
2607            let target_path1 = TargetPath::new("target1").unwrap();
2608            let target_file1: &[u8] = b"target1 file";
2609
2610            let delegated_target_path1 = TargetPath::new("delegations/delegation1").unwrap();
2611            let delegated_target_file1: &[u8] = b"delegation1 file";
2612
2613            let delegation1 = Delegation::builder(delegation_path.clone())
2614                .key(delegation_key.public())
2615                .delegate_path(TargetPath::new("delegations/").unwrap())
2616                .build()
2617                .unwrap();
2618
2619            let delegated_targets1 = TargetsMetadataBuilder::new()
2620                .insert_target_from_slice(
2621                    delegated_target_path1,
2622                    delegated_target_file1,
2623                    &[HashAlgorithm::Sha256],
2624                )
2625                .unwrap()
2626                .signed::<Pouf1>(delegation_key)
2627                .unwrap();
2628            let raw_delegated_targets = delegated_targets1.to_raw().unwrap();
2629
2630            let metadata1 = RepoBuilder::create(&mut repo)
2631                .trusted_root_keys(&[&KEYS[0]])
2632                .trusted_targets_keys(&[&KEYS[0]])
2633                .trusted_snapshot_keys(&[&KEYS[0]])
2634                .trusted_timestamp_keys(&[&KEYS[0]])
2635                .stage_root()
2636                .unwrap()
2637                .target_hash_algorithms(hash_algs)
2638                .add_target(target_path1.clone(), Cursor::new(target_file1))
2639                .await
2640                .unwrap()
2641                .add_delegation_key(delegation_key.public().clone())
2642                .add_delegation_role(delegation1.clone())
2643                .stage_targets()
2644                .unwrap()
2645                .stage_snapshot_with_builder(|builder| {
2646                    builder.insert_metadata_description(
2647                        delegation_path.clone(),
2648                        MetadataDescription::from_slice(
2649                            raw_delegated_targets.as_bytes(),
2650                            1,
2651                            &[HashAlgorithm::Sha256],
2652                        )
2653                        .unwrap(),
2654                    )
2655                })
2656                .unwrap()
2657                .commit()
2658                .await
2659                .unwrap();
2660
2661            // Next, create a new commit where we add a new target and delegation. This should copy
2662            // over the old targets and delegations.
2663            let mut database = Database::from_trusted_metadata(&metadata1).unwrap();
2664
2665            let target_path2 = TargetPath::new("bar").unwrap();
2666            let target_file2: &[u8] = b"bar file";
2667
2668            let delegated_target_path2 = TargetPath::new("delegations/delegation2").unwrap();
2669            let delegated_target_file2: &[u8] = b"delegation2 file";
2670
2671            let delegation2 = Delegation::builder(delegation_path.clone())
2672                .key(delegation_key.public())
2673                .delegate_path(TargetPath::new("delegations/").unwrap())
2674                .build()
2675                .unwrap();
2676
2677            let delegated_targets2 = TargetsMetadataBuilder::new()
2678                .insert_target_from_slice(
2679                    delegated_target_path2,
2680                    delegated_target_file2,
2681                    &[HashAlgorithm::Sha256],
2682                )
2683                .unwrap()
2684                .signed::<Pouf1>(delegation_key)
2685                .unwrap();
2686            let raw_delegated_targets = delegated_targets2.to_raw().unwrap();
2687
2688            let metadata2 = RepoBuilder::from_database(&mut repo, &database)
2689                .trusted_root_keys(&[&KEYS[0]])
2690                .trusted_targets_keys(&[&KEYS[0]])
2691                .trusted_snapshot_keys(&[&KEYS[0]])
2692                .trusted_timestamp_keys(&[&KEYS[0]])
2693                .stage_root()
2694                .unwrap()
2695                .target_hash_algorithms(hash_algs)
2696                .add_target(target_path2.clone(), Cursor::new(target_file2))
2697                .await
2698                .unwrap()
2699                .add_delegation_role(delegation2.clone())
2700                .stage_targets_with_builder(|b| b.expires(expires))
2701                .unwrap()
2702                .stage_snapshot_with_builder(|builder| {
2703                    builder.insert_metadata_description(
2704                        delegation_path.clone(),
2705                        MetadataDescription::from_slice(
2706                            raw_delegated_targets.as_bytes(),
2707                            1,
2708                            &[HashAlgorithm::Sha256],
2709                        )
2710                        .unwrap(),
2711                    )
2712                })
2713                .unwrap()
2714                .commit()
2715                .await
2716                .unwrap();
2717
2718            database.update_metadata(&metadata2).unwrap();
2719
2720            assert_eq!(
2721                &**database.trusted_targets().unwrap(),
2722                &TargetsMetadataBuilder::new()
2723                    .version(2)
2724                    .expires(expires)
2725                    .insert_target_from_slice(target_path1.clone(), target_file1, hash_algs)
2726                    .unwrap()
2727                    .insert_target_from_slice(target_path2.clone(), target_file2, hash_algs)
2728                    .unwrap()
2729                    .delegations(
2730                        DelegationsBuilder::new()
2731                            .key(delegation_key.public().clone())
2732                            .role(delegation1)
2733                            .role(delegation2)
2734                            .build()
2735                            .unwrap()
2736                    )
2737                    .build()
2738                    .unwrap()
2739            )
2740        })
2741    }
2742
2743    #[test]
2744    fn test_builder_rotating_keys_refreshes_metadata() {
2745        block_on(async move {
2746            let mut repo = EphemeralRepository::<Pouf1>::new();
2747
2748            let metadata1 = RepoBuilder::create(&mut repo)
2749                .trusted_root_keys(&[&KEYS[0]])
2750                .trusted_targets_keys(&[&KEYS[0]])
2751                .trusted_snapshot_keys(&[&KEYS[0]])
2752                .trusted_timestamp_keys(&[&KEYS[0]])
2753                .commit()
2754                .await
2755                .unwrap();
2756
2757            let mut db = Database::from_trusted_metadata(&metadata1).unwrap();
2758
2759            // Because of [update-root], rotating any root keys should make a new timestamp and
2760            // snapshot.
2761            //
2762            // FIXME(#297): This also purges targets, even though that's not conforming to the spec.
2763            //
2764            // [update-root]: https://theupdateframework.github.io/specification/v1.0.30/#update-root
2765            let metadata2 = RepoBuilder::from_database(&mut repo, &db)
2766                .trusted_root_keys(&[&KEYS[0]])
2767                .trusted_targets_keys(&[&KEYS[0]])
2768                .trusted_snapshot_keys(&[&KEYS[0]])
2769                .trusted_timestamp_keys(&[&KEYS[1]])
2770                .commit()
2771                .await
2772                .unwrap();
2773
2774            assert!(metadata2.root().is_some());
2775            assert!(metadata2.targets().is_some());
2776            assert!(metadata2.snapshot().is_some());
2777            assert!(metadata2.timestamp().is_some());
2778
2779            db.update_metadata(&metadata2).unwrap();
2780
2781            assert_eq!(db.trusted_root().version(), 2);
2782            assert_eq!(db.trusted_targets().unwrap().version(), 2);
2783            assert_eq!(db.trusted_snapshot().unwrap().version(), 2);
2784            assert_eq!(db.trusted_timestamp().unwrap().version(), 2);
2785
2786            // Note that rotating the timestamp keys purges all the metadata, so add it back in.
2787
2788            // Rotating the snapshot key should make new metadata.
2789            let metadata3 = RepoBuilder::from_database(&mut repo, &db)
2790                .trusted_root_keys(&[&KEYS[0]])
2791                .trusted_targets_keys(&[&KEYS[0]])
2792                .trusted_snapshot_keys(&[&KEYS[1]])
2793                .trusted_timestamp_keys(&[&KEYS[1]])
2794                .commit()
2795                .await
2796                .unwrap();
2797
2798            assert!(metadata3.root().is_some());
2799            assert!(metadata2.targets().is_some());
2800            assert!(metadata2.snapshot().is_some());
2801            assert!(metadata2.timestamp().is_some());
2802
2803            db.update_metadata(&metadata3).unwrap();
2804
2805            assert_eq!(db.trusted_root().version(), 3);
2806            assert_eq!(db.trusted_targets().unwrap().version(), 3);
2807            assert_eq!(db.trusted_snapshot().unwrap().version(), 3);
2808            assert_eq!(db.trusted_timestamp().unwrap().version(), 3);
2809
2810            // Rotating the targets key should make a new targets, snapshot, and timestamp.
2811            let metadata4 = RepoBuilder::from_database(&mut repo, &db)
2812                .trusted_root_keys(&[&KEYS[0]])
2813                .trusted_targets_keys(&[&KEYS[1]])
2814                .trusted_snapshot_keys(&[&KEYS[1]])
2815                .trusted_timestamp_keys(&[&KEYS[1]])
2816                .commit()
2817                .await
2818                .unwrap();
2819
2820            assert!(metadata4.root().is_some());
2821            assert!(metadata4.targets().is_some());
2822            assert!(metadata4.snapshot().is_some());
2823            assert!(metadata4.timestamp().is_some());
2824
2825            db.update_metadata(&metadata4).unwrap();
2826
2827            assert_eq!(db.trusted_root().version(), 4);
2828            assert_eq!(db.trusted_targets().unwrap().version(), 4);
2829            assert_eq!(db.trusted_snapshot().unwrap().version(), 4);
2830            assert_eq!(db.trusted_timestamp().unwrap().version(), 4);
2831
2832            // Rotating the root key should make a new targets, snapshot, and timestamp.
2833            let metadata5 = RepoBuilder::from_database(&mut repo, &db)
2834                .signing_root_keys(&[&KEYS[0]])
2835                .trusted_root_keys(&[&KEYS[1]])
2836                .trusted_targets_keys(&[&KEYS[1]])
2837                .trusted_snapshot_keys(&[&KEYS[1]])
2838                .trusted_timestamp_keys(&[&KEYS[1]])
2839                .commit()
2840                .await
2841                .unwrap();
2842
2843            assert!(metadata5.root().is_some());
2844            assert!(metadata5.targets().is_some());
2845            assert!(metadata5.snapshot().is_some());
2846            assert!(metadata5.timestamp().is_some());
2847
2848            db.update_metadata(&metadata5).unwrap();
2849
2850            assert_eq!(db.trusted_root().version(), 5);
2851            assert_eq!(db.trusted_targets().unwrap().version(), 5);
2852            assert_eq!(db.trusted_snapshot().unwrap().version(), 5);
2853            assert_eq!(db.trusted_timestamp().unwrap().version(), 5);
2854        })
2855    }
2856
2857    #[test]
2858    fn test_builder_expired_metadata_refreshes_metadata() {
2859        block_on(async move {
2860            let mut repo = EphemeralRepository::<Pouf1>::new();
2861
2862            let epoch = Utc.timestamp_opt(0, 0).unwrap();
2863            let root_expires = Duration::seconds(40);
2864            let targets_expires = Duration::seconds(30);
2865            let snapshot_expires = Duration::seconds(20);
2866            let timestamp_expires = Duration::seconds(10);
2867
2868            let current_time = epoch;
2869            let metadata1 = RepoBuilder::create(&mut repo)
2870                .current_time(current_time)
2871                .trusted_root_keys(&[&KEYS[0]])
2872                .trusted_targets_keys(&[&KEYS[0]])
2873                .trusted_snapshot_keys(&[&KEYS[0]])
2874                .trusted_timestamp_keys(&[&KEYS[0]])
2875                .root_expiration_duration(root_expires)
2876                .targets_expiration_duration(targets_expires)
2877                .snapshot_expiration_duration(snapshot_expires)
2878                .timestamp_expiration_duration(timestamp_expires)
2879                .commit()
2880                .await
2881                .unwrap();
2882
2883            let mut db =
2884                Database::from_trusted_metadata_with_start_time(&metadata1, &current_time).unwrap();
2885
2886            // Advance time to past the timestamp expiration.
2887            let current_time = epoch + timestamp_expires + Duration::seconds(1);
2888            let metadata2 = RepoBuilder::from_database(&mut repo, &db)
2889                .current_time(current_time)
2890                .trusted_root_keys(&[&KEYS[0]])
2891                .trusted_targets_keys(&[&KEYS[0]])
2892                .trusted_snapshot_keys(&[&KEYS[0]])
2893                .trusted_timestamp_keys(&[&KEYS[0]])
2894                .commit()
2895                .await
2896                .unwrap();
2897
2898            assert!(metadata2.root().is_none());
2899            assert!(metadata2.targets().is_none());
2900            assert!(metadata2.snapshot().is_none());
2901            assert!(metadata2.timestamp().is_some());
2902
2903            db.update_metadata_with_start_time(&metadata2, &current_time)
2904                .unwrap();
2905
2906            assert_eq!(db.trusted_root().version(), 1);
2907            assert_eq!(db.trusted_targets().unwrap().version(), 1);
2908            assert_eq!(db.trusted_snapshot().unwrap().version(), 1);
2909            assert_eq!(db.trusted_timestamp().unwrap().version(), 2);
2910
2911            // Advance time to past the snapshot expiration.
2912            let current_time = epoch + snapshot_expires + Duration::seconds(1);
2913            let metadata3 = RepoBuilder::from_database(&mut repo, &db)
2914                .current_time(current_time)
2915                .trusted_root_keys(&[&KEYS[0]])
2916                .trusted_targets_keys(&[&KEYS[0]])
2917                .trusted_snapshot_keys(&[&KEYS[0]])
2918                .trusted_timestamp_keys(&[&KEYS[0]])
2919                .commit()
2920                .await
2921                .unwrap();
2922
2923            assert!(metadata3.root().is_none());
2924            assert!(metadata3.targets().is_none());
2925            assert!(metadata3.snapshot().is_some());
2926            assert!(metadata3.timestamp().is_some());
2927
2928            db.update_metadata_with_start_time(&metadata3, &current_time)
2929                .unwrap();
2930
2931            assert_eq!(db.trusted_root().version(), 1);
2932            assert_eq!(db.trusted_targets().unwrap().version(), 1);
2933            assert_eq!(db.trusted_snapshot().unwrap().version(), 2);
2934            assert_eq!(db.trusted_timestamp().unwrap().version(), 3);
2935
2936            // Advance time to past the targets expiration.
2937            let current_time = epoch + targets_expires + Duration::seconds(1);
2938            let metadata4 = RepoBuilder::from_database(&mut repo, &db)
2939                .current_time(current_time)
2940                .trusted_root_keys(&[&KEYS[0]])
2941                .trusted_targets_keys(&[&KEYS[0]])
2942                .trusted_snapshot_keys(&[&KEYS[0]])
2943                .trusted_timestamp_keys(&[&KEYS[0]])
2944                .commit()
2945                .await
2946                .unwrap();
2947
2948            assert!(metadata4.root().is_none());
2949            assert!(metadata4.targets().is_some());
2950            assert!(metadata4.snapshot().is_some());
2951            assert!(metadata4.timestamp().is_some());
2952
2953            db.update_metadata_with_start_time(&metadata4, &current_time)
2954                .unwrap();
2955
2956            assert_eq!(db.trusted_root().version(), 1);
2957            assert_eq!(db.trusted_targets().unwrap().version(), 2);
2958            assert_eq!(db.trusted_snapshot().unwrap().version(), 3);
2959            assert_eq!(db.trusted_timestamp().unwrap().version(), 4);
2960
2961            // Advance time to past the root expiration.
2962            //
2963            // Because of [update-root], rotating any root keys should make a new timestamp and
2964            // snapshot.
2965            //
2966            // [update-root]: https://theupdateframework.github.io/specification/v1.0.30/#update-root
2967            let current_time = epoch + root_expires + Duration::seconds(1);
2968            let metadata5 = RepoBuilder::from_database(&mut repo, &db)
2969                .current_time(current_time)
2970                .trusted_root_keys(&[&KEYS[0]])
2971                .trusted_targets_keys(&[&KEYS[0]])
2972                .trusted_snapshot_keys(&[&KEYS[0]])
2973                .trusted_timestamp_keys(&[&KEYS[0]])
2974                .commit()
2975                .await
2976                .unwrap();
2977
2978            assert!(metadata5.root().is_some());
2979            assert!(metadata5.targets().is_some());
2980            assert!(metadata5.snapshot().is_some());
2981            assert!(metadata5.timestamp().is_some());
2982
2983            db.update_metadata_with_start_time(&metadata5, &current_time)
2984                .unwrap();
2985
2986            assert_eq!(db.trusted_root().version(), 2);
2987            assert_eq!(db.trusted_targets().unwrap().version(), 3);
2988            assert_eq!(db.trusted_snapshot().unwrap().version(), 4);
2989            assert_eq!(db.trusted_timestamp().unwrap().version(), 5);
2990        })
2991    }
2992
2993    #[test]
2994    fn test_adding_target_refreshes_metadata() {
2995        block_on(async move {
2996            let mut repo = EphemeralRepository::<Pouf1>::new();
2997
2998            let metadata1 = RepoBuilder::create(&mut repo)
2999                .trusted_root_keys(&[&KEYS[0]])
3000                .trusted_targets_keys(&[&KEYS[0]])
3001                .trusted_snapshot_keys(&[&KEYS[0]])
3002                .trusted_timestamp_keys(&[&KEYS[0]])
3003                .commit()
3004                .await
3005                .unwrap();
3006
3007            let mut db = Database::from_trusted_metadata(&metadata1).unwrap();
3008
3009            let target_path = TargetPath::new("foo").unwrap();
3010            let target_file: &[u8] = b"foo file";
3011
3012            let metadata2 = RepoBuilder::from_database(&mut repo, &db)
3013                .trusted_root_keys(&[&KEYS[0]])
3014                .trusted_targets_keys(&[&KEYS[0]])
3015                .trusted_snapshot_keys(&[&KEYS[0]])
3016                .trusted_timestamp_keys(&[&KEYS[0]])
3017                .add_target(target_path, Cursor::new(target_file))
3018                .await
3019                .unwrap()
3020                .commit()
3021                .await
3022                .unwrap();
3023
3024            assert!(metadata2.root().is_none());
3025            assert!(metadata2.targets().is_some());
3026            assert!(metadata2.snapshot().is_some());
3027            assert!(metadata2.timestamp().is_some());
3028
3029            db.update_metadata(&metadata2).unwrap();
3030
3031            assert_eq!(db.trusted_root().version(), 1);
3032            assert_eq!(db.trusted_targets().unwrap().version(), 2);
3033            assert_eq!(db.trusted_snapshot().unwrap().version(), 2);
3034            assert_eq!(db.trusted_timestamp().unwrap().version(), 2);
3035        })
3036    }
3037
3038    #[test]
3039    fn test_time_versioning() {
3040        block_on(async move {
3041            let mut repo = EphemeralRepository::<Pouf1>::new();
3042
3043            let current_time = Utc.timestamp_opt(5, 0).unwrap();
3044            let metadata = RepoBuilder::create(&mut repo)
3045                .current_time(current_time)
3046                .time_versioning(true)
3047                .trusted_root_keys(&[&KEYS[0]])
3048                .trusted_targets_keys(&[&KEYS[0]])
3049                .trusted_snapshot_keys(&[&KEYS[0]])
3050                .trusted_timestamp_keys(&[&KEYS[0]])
3051                .commit()
3052                .await
3053                .unwrap();
3054
3055            let mut db =
3056                Database::from_trusted_metadata_with_start_time(&metadata, &current_time).unwrap();
3057
3058            // The initial version should be the current time.
3059            assert_eq!(db.trusted_root().version(), 1);
3060            assert_eq!(db.trusted_targets().map(|m| m.version()), Some(5));
3061            assert_eq!(db.trusted_snapshot().map(|m| m.version()), Some(5));
3062            assert_eq!(db.trusted_timestamp().map(|m| m.version()), Some(5));
3063
3064            // Generating metadata for the same timestamp should advance it by 1.
3065            let metadata = RepoBuilder::from_database(&mut repo, &db)
3066                .current_time(current_time)
3067                .time_versioning(true)
3068                .trusted_root_keys(&[&KEYS[0]])
3069                .trusted_targets_keys(&[&KEYS[0]])
3070                .trusted_snapshot_keys(&[&KEYS[0]])
3071                .trusted_timestamp_keys(&[&KEYS[0]])
3072                .stage_root()
3073                .unwrap()
3074                .stage_targets()
3075                .unwrap()
3076                .commit()
3077                .await
3078                .unwrap();
3079
3080            db.update_metadata_with_start_time(&metadata, &current_time)
3081                .unwrap();
3082
3083            assert_eq!(db.trusted_root().version(), 2);
3084            assert_eq!(db.trusted_targets().map(|m| m.version()), Some(6));
3085            assert_eq!(db.trusted_snapshot().map(|m| m.version()), Some(6));
3086            assert_eq!(db.trusted_timestamp().map(|m| m.version()), Some(6));
3087
3088            // Generating metadata for a new timestamp should advance the versions to that amount.
3089            let current_time = Utc.timestamp_opt(10, 0).unwrap();
3090            let metadata = RepoBuilder::from_database(&mut repo, &db)
3091                .current_time(current_time)
3092                .time_versioning(true)
3093                .trusted_root_keys(&[&KEYS[0]])
3094                .trusted_targets_keys(&[&KEYS[0]])
3095                .trusted_snapshot_keys(&[&KEYS[0]])
3096                .trusted_timestamp_keys(&[&KEYS[0]])
3097                .stage_root()
3098                .unwrap()
3099                .stage_targets()
3100                .unwrap()
3101                .commit()
3102                .await
3103                .unwrap();
3104
3105            db.update_metadata_with_start_time(&metadata, &current_time)
3106                .unwrap();
3107
3108            assert_eq!(db.trusted_root().version(), 3);
3109            assert_eq!(db.trusted_targets().map(|m| m.version()), Some(10));
3110            assert_eq!(db.trusted_snapshot().map(|m| m.version()), Some(10));
3111            assert_eq!(db.trusted_timestamp().map(|m| m.version()), Some(10));
3112        })
3113    }
3114
3115    #[test]
3116    fn test_time_versioning_falls_back_to_monotonic() {
3117        block_on(async move {
3118            let mut repo = EphemeralRepository::<Pouf1>::new();
3119
3120            // zero timestamp should initialize to 1.
3121            let current_time = Utc.timestamp_opt(0, 0).unwrap();
3122            let metadata = RepoBuilder::create(&mut repo)
3123                .current_time(current_time)
3124                .time_versioning(true)
3125                .trusted_root_keys(&[&KEYS[0]])
3126                .trusted_targets_keys(&[&KEYS[0]])
3127                .trusted_snapshot_keys(&[&KEYS[0]])
3128                .trusted_timestamp_keys(&[&KEYS[0]])
3129                .commit()
3130                .await
3131                .unwrap();
3132
3133            let mut db =
3134                Database::from_trusted_metadata_with_start_time(&metadata, &current_time).unwrap();
3135
3136            assert_eq!(db.trusted_root().version(), 1);
3137            assert_eq!(db.trusted_targets().map(|m| m.version()), Some(1));
3138            assert_eq!(db.trusted_snapshot().map(|m| m.version()), Some(1));
3139            assert_eq!(db.trusted_timestamp().map(|m| m.version()), Some(1));
3140
3141            // A sub-second timestamp should advance the version by 1.
3142            let current_time = Utc.timestamp_opt(0, 3).unwrap();
3143            let metadata = RepoBuilder::from_database(&mut repo, &db)
3144                .current_time(current_time)
3145                .time_versioning(true)
3146                .trusted_root_keys(&[&KEYS[0]])
3147                .trusted_targets_keys(&[&KEYS[0]])
3148                .trusted_snapshot_keys(&[&KEYS[0]])
3149                .trusted_timestamp_keys(&[&KEYS[0]])
3150                .stage_root()
3151                .unwrap()
3152                .stage_targets()
3153                .unwrap()
3154                .commit()
3155                .await
3156                .unwrap();
3157
3158            db.update_metadata_with_start_time(&metadata, &current_time)
3159                .unwrap();
3160
3161            assert_eq!(db.trusted_root().version(), 2);
3162            assert_eq!(db.trusted_targets().map(|m| m.version()), Some(2));
3163            assert_eq!(db.trusted_snapshot().map(|m| m.version()), Some(2));
3164            assert_eq!(db.trusted_timestamp().map(|m| m.version()), Some(2));
3165        })
3166    }
3167
3168    #[test]
3169    fn test_builder_errs_if_no_keys() {
3170        block_on(async move {
3171            let repo = EphemeralRepository::<Pouf1>::new();
3172
3173            let metadata = RepoBuilder::create(&repo)
3174                .trusted_root_keys(&[&KEYS[0]])
3175                .trusted_targets_keys(&[&KEYS[0]])
3176                .trusted_snapshot_keys(&[&KEYS[0]])
3177                .trusted_timestamp_keys(&[&KEYS[0]])
3178                .commit()
3179                .await
3180                .unwrap();
3181
3182            let db = Database::from_trusted_metadata(&metadata).unwrap();
3183
3184            match RepoBuilder::from_database(&repo, &db).stage_root() {
3185                Err(Error::MetadataRoleDoesNotHaveEnoughKeyIds {
3186                    role,
3187                    key_ids: 0,
3188                    threshold: 1,
3189                }) if role == MetadataPath::root() => {}
3190                Err(err) => panic!("unexpected error: {}", err),
3191                Ok(_) => panic!("unexpected success"),
3192            }
3193
3194            match RepoBuilder::from_database(&repo, &db)
3195                .trusted_root_keys(&[&KEYS[0]])
3196                .stage_root_if_necessary()
3197                .unwrap()
3198                .stage_targets()
3199            {
3200                Err(Error::MissingPrivateKey { role }) if role == MetadataPath::targets() => {}
3201                Err(err) => panic!("unexpected error: {}", err),
3202                Ok(_) => panic!("unexpected success"),
3203            }
3204
3205            match RepoBuilder::from_database(&repo, &db)
3206                .trusted_root_keys(&[&KEYS[0]])
3207                .trusted_targets_keys(&[&KEYS[0]])
3208                .stage_root_if_necessary()
3209                .unwrap()
3210                .stage_targets_if_necessary()
3211                .unwrap()
3212                .stage_snapshot()
3213            {
3214                Err(Error::MissingPrivateKey { role }) if role == MetadataPath::snapshot() => {}
3215                Err(err) => panic!("unexpected error: {}", err),
3216                Ok(_) => panic!("unexpected success"),
3217            }
3218
3219            match RepoBuilder::from_database(&repo, &db)
3220                .trusted_root_keys(&[&KEYS[0]])
3221                .trusted_targets_keys(&[&KEYS[0]])
3222                .trusted_snapshot_keys(&[&KEYS[0]])
3223                .stage_root_if_necessary()
3224                .unwrap()
3225                .stage_targets_if_necessary()
3226                .unwrap()
3227                .stage_snapshot_if_necessary()
3228                .unwrap()
3229                .stage_timestamp()
3230            {
3231                Err(Error::MissingPrivateKey { role }) if role == MetadataPath::timestamp() => {}
3232                Err(err) => panic!("unexpected error: {}", err),
3233                Ok(_) => panic!("unexpected success"),
3234            }
3235        })
3236    }
3237}