Skip to main content

tuf/
client.rs

1//! Clients for high level interactions with TUF repositories.
2//!
3//! # Example
4//!
5//! ```no_run
6//! # use futures_executor::block_on;
7//! # use hyper::client::Client as HttpClient;
8//! # use std::path::PathBuf;
9//! # use std::str::FromStr;
10//! # use tuf::{Result, Database};
11//! # use tuf::crypto::PublicKey;
12//! # use tuf::client::{Client, Config};
13//! # use tuf::metadata::{RootMetadata, Role, MetadataPath, MetadataVersion};
14//! # use tuf::pouf::Pouf1;
15//! # use tuf::repository::{FileSystemRepository, HttpRepositoryBuilder};
16//! #
17//! # const PUBLIC_KEY: &'static [u8] = include_bytes!("../tests/ed25519/ed25519-1.pub");
18//! #
19//! # fn load_root_public_keys() -> Vec<PublicKey> {
20//! #      vec![PublicKey::from_ed25519(PUBLIC_KEY).unwrap()]
21//! # }
22//! #
23//! # fn main() -> Result<()> {
24//! # block_on(async {
25//! let root_public_keys = load_root_public_keys();
26//! let local = FileSystemRepository::<Pouf1>::new(PathBuf::from("~/.rustup"));
27//!
28//! let remote = HttpRepositoryBuilder::new_with_uri(
29//!     "https://static.rust-lang.org/".parse::<http::Uri>().unwrap(),
30//!     HttpClient::new(),
31//! )
32//! .user_agent("rustup/1.4.0")
33//! .build();
34//!
35//! let mut client = Client::with_trusted_root_keys(
36//!     Config::default(),
37//!     MetadataVersion::Number(1),
38//!     1,
39//!     &root_public_keys,
40//!     local,
41//!     remote,
42//! ).await?;
43//!
44//! let _ = client.update().await?;
45//! # Ok(())
46//! # })
47//! # }
48//! ```
49
50use chrono::{offset::Utc, DateTime};
51use futures_io::AsyncRead;
52use log::{error, warn};
53use std::future::Future;
54use std::pin::Pin;
55
56use crate::crypto::{self, HashAlgorithm, HashValue, PublicKey};
57use crate::database::Database;
58use crate::error::{Error, Result};
59use crate::metadata::{
60    Metadata, MetadataPath, MetadataVersion, RawSignedMetadata, RootMetadata, SnapshotMetadata,
61    TargetDescription, TargetPath, TargetsMetadata,
62};
63use crate::pouf::Pouf;
64use crate::repository::{Repository, RepositoryProvider, RepositoryStorage};
65use crate::verify::Verified;
66
67/// A client that interacts with TUF repositories.
68#[derive(Debug)]
69pub struct Client<D, L, R>
70where
71    D: Pouf,
72    L: RepositoryProvider<D> + RepositoryStorage<D>,
73    R: RepositoryProvider<D>,
74{
75    config: Config,
76    tuf: Database<D>,
77    local: Repository<L, D>,
78    remote: Repository<R, D>,
79}
80
81impl<D, L, R> Client<D, L, R>
82where
83    D: Pouf,
84    L: RepositoryProvider<D> + RepositoryStorage<D>,
85    R: RepositoryProvider<D>,
86{
87    /// Create a new TUF client. It will attempt to load the latest root metadata from the local
88    /// repo and use it as the initial trusted root metadata, or it will return an error if it
89    /// cannot do so.
90    ///
91    /// **WARNING**: This is trust-on-first-use (TOFU) and offers weaker security guarantees than
92    /// the related methods [`Client::with_trusted_root`], [`Client::with_trusted_root_keys`].
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// # use chrono::offset::{Utc, TimeZone};
98    /// # use futures_executor::block_on;
99    /// # use tuf::{
100    /// #     Error,
101    /// #     pouf::Pouf1,
102    /// #     client::{Client, Config},
103    /// #     crypto::{Ed25519PrivateKey, PrivateKey, SignatureScheme},
104    /// #     metadata::{MetadataPath, MetadataVersion, Role, RootMetadataBuilder},
105    /// #     repository::{EphemeralRepository, RepositoryStorage},
106    /// # };
107    /// # fn main() -> Result<(), Error> {
108    /// # block_on(async {
109    /// # let private_key = Ed25519PrivateKey::from_pkcs8(
110    /// #     &Ed25519PrivateKey::pkcs8()?,
111    /// # )?;
112    /// # let public_key = private_key.public().clone();
113    /// let mut local = EphemeralRepository::<Pouf1>::new();
114    /// let remote = EphemeralRepository::<Pouf1>::new();
115    ///
116    /// let root_version = 1;
117    /// let root = RootMetadataBuilder::new()
118    ///     .version(root_version)
119    ///     .expires(Utc.with_ymd_and_hms(2038, 1, 1, 0, 0, 0).unwrap())
120    ///     .root_key(public_key.clone())
121    ///     .snapshot_key(public_key.clone())
122    ///     .targets_key(public_key.clone())
123    ///     .timestamp_key(public_key.clone())
124    ///     .signed::<Pouf1>(&private_key)?;
125    ///
126    /// let root_path = MetadataPath::root();
127    /// let root_version = MetadataVersion::Number(root_version);
128    ///
129    /// local.store_metadata(
130    ///     &root_path,
131    ///     root_version,
132    ///     &mut root.to_raw().unwrap().as_bytes()
133    /// ).await?;
134    ///
135    /// let client = Client::with_trusted_local(
136    ///     Config::default(),
137    ///     local,
138    ///     remote,
139    /// ).await?;
140    /// # Ok(())
141    /// # })
142    /// # }
143    /// ```
144    pub async fn with_trusted_local(config: Config, local: L, remote: R) -> Result<Self> {
145        let (local, remote) = (Repository::new(local), Repository::new(remote));
146        let root_path = MetadataPath::root();
147
148        // FIXME should this be MetadataVersion::None so we bootstrap with the latest version?
149        let root_version = MetadataVersion::Number(1);
150
151        let raw_root: RawSignedMetadata<_, RootMetadata> = local
152            .fetch_metadata(&root_path, root_version, config.max_root_length, vec![])
153            .await?;
154
155        let tuf = Database::from_trusted_root(&raw_root)?;
156
157        Self::new(config, tuf, local, remote).await
158    }
159
160    /// Create a new TUF client. It will trust this initial root metadata.
161    ///
162    /// # Examples
163    ///
164    /// ```
165    /// # use chrono::offset::{Utc, TimeZone};
166    /// # use futures_executor::block_on;
167    /// # use tuf::{
168    /// #     Error,
169    /// #     pouf::Pouf1,
170    /// #     client::{Client, Config},
171    /// #     crypto::{Ed25519PrivateKey, KeyType, PrivateKey, SignatureScheme},
172    /// #     metadata::{MetadataPath, MetadataVersion, Role, RootMetadataBuilder},
173    /// #     repository::{EphemeralRepository},
174    /// # };
175    /// # fn main() -> Result<(), Error> {
176    /// # block_on(async {
177    /// # let private_key = Ed25519PrivateKey::from_pkcs8(
178    /// #     &Ed25519PrivateKey::pkcs8()?,
179    /// # )?;
180    /// # let public_key = private_key.public().clone();
181    /// let local = EphemeralRepository::<Pouf1>::new();
182    /// let remote = EphemeralRepository::<Pouf1>::new();
183    ///
184    /// let root_version = 1;
185    /// let root_threshold = 1;
186    /// let raw_root = RootMetadataBuilder::new()
187    ///     .version(root_version)
188    ///     .expires(Utc.with_ymd_and_hms(2038, 1, 1, 0, 0, 0).unwrap())
189    ///     .root_key(public_key.clone())
190    ///     .root_threshold(root_threshold)
191    ///     .snapshot_key(public_key.clone())
192    ///     .targets_key(public_key.clone())
193    ///     .timestamp_key(public_key.clone())
194    ///     .signed::<Pouf1>(&private_key)
195    ///     .unwrap()
196    ///     .to_raw()
197    ///     .unwrap();
198    ///
199    /// let client = Client::with_trusted_root(
200    ///     Config::default(),
201    ///     &raw_root,
202    ///     local,
203    ///     remote,
204    /// ).await?;
205    /// # Ok(())
206    /// # })
207    /// # }
208    /// ```
209    pub async fn with_trusted_root(
210        config: Config,
211        trusted_root: &RawSignedMetadata<D, RootMetadata>,
212        local: L,
213        remote: R,
214    ) -> Result<Self> {
215        let (local, remote) = (Repository::new(local), Repository::new(remote));
216        let tuf = Database::from_trusted_root(trusted_root)?;
217
218        Self::new(config, tuf, local, remote).await
219    }
220
221    /// Create a new TUF client. It will attempt to load initial root metadata from the local and remote
222    /// repositories using the provided keys to pin the verification.
223    ///
224    /// # Examples
225    ///
226    /// ```
227    /// # use chrono::offset::{Utc, TimeZone};
228    /// # use futures_executor::block_on;
229    /// # use std::iter::once;
230    /// # use tuf::{
231    /// #     Error,
232    /// #     pouf::Pouf1,
233    /// #     client::{Client, Config},
234    /// #     crypto::{Ed25519PrivateKey, KeyType, PrivateKey, SignatureScheme},
235    /// #     metadata::{MetadataPath, MetadataVersion, Role, RootMetadataBuilder},
236    /// #     repository::{EphemeralRepository, RepositoryStorage},
237    /// # };
238    /// # fn main() -> Result<(), Error> {
239    /// # block_on(async {
240    /// # let private_key = Ed25519PrivateKey::from_pkcs8(
241    /// #     &Ed25519PrivateKey::pkcs8()?,
242    /// # )?;
243    /// # let public_key = private_key.public().clone();
244    /// let local = EphemeralRepository::<Pouf1>::new();
245    /// let mut remote = EphemeralRepository::<Pouf1>::new();
246    ///
247    /// let root_version = 1;
248    /// let root_threshold = 1;
249    /// let root = RootMetadataBuilder::new()
250    ///     .version(root_version)
251    ///     .expires(Utc.with_ymd_and_hms(2038, 1, 1, 0, 0, 0).unwrap())
252    ///     .root_key(public_key.clone())
253    ///     .root_threshold(root_threshold)
254    ///     .snapshot_key(public_key.clone())
255    ///     .targets_key(public_key.clone())
256    ///     .timestamp_key(public_key.clone())
257    ///     .signed::<Pouf1>(&private_key)?;
258    ///
259    /// let root_path = MetadataPath::root();
260    /// let root_version = MetadataVersion::Number(root_version);
261    ///
262    /// remote.store_metadata(
263    ///     &root_path,
264    ///     root_version,
265    ///     &mut root.to_raw().unwrap().as_bytes()
266    /// ).await?;
267    ///
268    /// let client = Client::with_trusted_root_keys(
269    ///     Config::default(),
270    ///     root_version,
271    ///     root_threshold,
272    ///     once(&public_key),
273    ///     local,
274    ///     remote,
275    /// ).await?;
276    /// # Ok(())
277    /// # })
278    /// # }
279    /// ```
280    pub async fn with_trusted_root_keys<'a, I>(
281        config: Config,
282        root_version: MetadataVersion,
283        root_threshold: u32,
284        trusted_root_keys: I,
285        local: L,
286        remote: R,
287    ) -> Result<Self>
288    where
289        I: IntoIterator<Item = &'a PublicKey>,
290    {
291        let (mut local, remote) = (Repository::new(local), Repository::new(remote));
292
293        let root_path = MetadataPath::root();
294        let (fetched, raw_root) = fetch_metadata_from_local_or_else_remote(
295            &root_path,
296            root_version,
297            config.max_root_length,
298            vec![],
299            &local,
300            &remote,
301        )
302        .await?;
303
304        let tuf =
305            Database::from_root_with_trusted_keys(&raw_root, root_threshold, trusted_root_keys)?;
306
307        // FIXME(#253) verify the trusted root version matches the provided version.
308        let root_version = MetadataVersion::Number(tuf.trusted_root().version());
309
310        // Only store the metadata after we have validated it.
311        if fetched {
312            // NOTE(#301): The spec only states that the unversioned root metadata needs to be
313            // written to non-volatile storage. This enables a method like
314            // `Client::with_trusted_local` to initialize trust with the latest root version.
315            // However, this doesn't work well when trust is established with an externally
316            // provided root, such as with `Clietn::with_trusted_root` or
317            // `Client::with_trusted_root_keys`. In those cases, it's possible those initial roots
318            // could be multiple versions behind the latest cached root metadata. So we'd most
319            // likely never use the locally cached `root.json`.
320            //
321            // Instead, as an extension to the spec, we'll write the `$VERSION.root.json` metadata
322            // to the local store. This will eventually enable us to initialize metadata from the
323            // local store (see #301).
324            local
325                .store_metadata(&root_path, root_version, &raw_root)
326                .await?;
327
328            // FIXME: should we also store the root as `MetadataVersion::None`?
329        }
330
331        Self::new(config, tuf, local, remote).await
332    }
333
334    /// Create a new TUF client. It will trust and update the TUF database.
335    pub fn from_database(config: Config, tuf: Database<D>, local: L, remote: R) -> Self {
336        Self {
337            config,
338            tuf,
339            local: Repository::new(local),
340            remote: Repository::new(remote),
341        }
342    }
343
344    /// Construct a client with the given parts.
345    ///
346    /// Note: Since this was created by a prior [Client], it does not try to load
347    /// metadata from the included local repository, since we would have done
348    /// that when the prior [Client] was constructed.
349    pub fn from_parts(parts: Parts<D, L, R>) -> Self {
350        let Parts {
351            config,
352            database,
353            local,
354            remote,
355        } = parts;
356        Self {
357            config,
358            tuf: database,
359            local: Repository::new(local),
360            remote: Repository::new(remote),
361        }
362    }
363
364    /// Create a new TUF client. It will trust this TUF database.
365    async fn new(
366        config: Config,
367        mut tuf: Database<D>,
368        local: Repository<L, D>,
369        remote: Repository<R, D>,
370    ) -> Result<Self> {
371        let start_time = Utc::now();
372
373        let res = async {
374            let _r =
375                Self::update_root_with_repos(&start_time, &config, &mut tuf, None, &local).await?;
376            let _ts =
377                Self::update_timestamp_with_repos(&start_time, &config, &mut tuf, None, &local)
378                    .await?;
379            let _sn = Self::update_snapshot_with_repos(
380                &start_time,
381                &config,
382                &mut tuf,
383                None,
384                &local,
385                false,
386            )
387            .await?;
388            let _ta = Self::update_targets_with_repos(
389                &start_time,
390                &config,
391                &mut tuf,
392                None,
393                &local,
394                false,
395            )
396            .await?;
397
398            Ok(())
399        }
400        .await;
401
402        match res {
403            Ok(()) | Err(Error::MetadataNotFound { .. }) => {}
404            Err(err) => {
405                warn!("error loading local metadata: : {}", err);
406            }
407        }
408
409        Ok(Client {
410            tuf,
411            config,
412            local,
413            remote,
414        })
415    }
416
417    /// Update TUF metadata from the remote repository.
418    ///
419    /// Returns `true` if an update occurred and `false` otherwise.
420    pub async fn update(&mut self) -> Result<bool> {
421        self.update_with_start_time(&Utc::now()).await
422    }
423
424    /// Update TUF metadata from the remote repository, using the specified time to determine if
425    /// the metadata is expired.
426    ///
427    /// Returns `true` if an update occurred and `false` otherwise.
428    ///
429    /// **WARNING**: Using an older time opens up users to a freeze attack.
430    pub async fn update_with_start_time(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
431        let r = self.update_root(start_time).await?;
432        let ts = self.update_timestamp(start_time).await?;
433        let sn = self.update_snapshot(start_time).await?;
434        let ta = self.update_targets(start_time).await?;
435
436        Ok(r || ts || sn || ta)
437    }
438
439    /// Consumes the [Client] and returns the inner [Database] and other parts.
440    pub fn into_parts(self) -> Parts<D, L, R> {
441        let Client {
442            config,
443            tuf,
444            local,
445            remote,
446        } = self;
447        Parts {
448            config,
449            database: tuf,
450            local: local.into_inner(),
451            remote: remote.into_inner(),
452        }
453    }
454
455    /// Returns a reference to the TUF database.
456    pub fn database(&self) -> &Database<D> {
457        &self.tuf
458    }
459
460    /// Returns a mutable reference to the TUF database.
461    pub fn database_mut(&mut self) -> &mut Database<D> {
462        &mut self.tuf
463    }
464
465    /// Returns a refrerence to the local repository.
466    pub fn local_repo(&self) -> &L {
467        self.local.as_inner()
468    }
469
470    /// Returns a mutable reference to the local repository.
471    pub fn local_repo_mut(&mut self) -> &mut L {
472        self.local.as_inner_mut()
473    }
474
475    /// Returns a refrerence to the remote repository.
476    pub fn remote_repo(&self) -> &R {
477        self.remote.as_inner()
478    }
479
480    /// Returns a mutable reference to the remote repository.
481    pub fn remote_repo_mut(&mut self) -> &mut R {
482        self.remote.as_inner_mut()
483    }
484
485    /// Update TUF root metadata from the remote repository.
486    ///
487    /// Returns `true` if an update occurred and `false` otherwise.
488    pub async fn update_root(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
489        Self::update_root_with_repos(
490            start_time,
491            &self.config,
492            &mut self.tuf,
493            Some(&mut self.local),
494            &self.remote,
495        )
496        .await
497    }
498
499    async fn update_root_with_repos<Remote>(
500        start_time: &DateTime<Utc>,
501        config: &Config,
502        tuf: &mut Database<D>,
503        mut local: Option<&mut Repository<L, D>>,
504        remote: &Repository<Remote, D>,
505    ) -> Result<bool>
506    where
507        Remote: RepositoryProvider<D>,
508    {
509        let root_path = MetadataPath::root();
510
511        let mut updated = false;
512
513        loop {
514            /////////////////////////////////////////
515            // TUF-1.0.9 §5.1.2:
516            //
517            //     Try downloading version N+1 of the root metadata file, up to some W number of
518            //     bytes (because the size is unknown). The value for W is set by the authors of
519            //     the application using TUF. For example, W may be tens of kilobytes. The filename
520            //     used to download the root metadata file is of the fixed form
521            //     VERSION_NUMBER.FILENAME.EXT (e.g., 42.root.json). If this file is not available,
522            //     or we have downloaded more than Y number of root metadata files (because the
523            //     exact number is as yet unknown), then go to step 5.1.9. The value for Y is set
524            //     by the authors of the application using TUF. For example, Y may be 2^10.
525
526            // FIXME(#306) We do not have an upper bound on the number of root metadata we'll
527            // fetch. This means that an attacker that's stolen the root keys could cause a client
528            // to fall into an infinite loop (but if an attacker has stolen the root keys, the
529            // client probably has worse problems to worry about).
530
531            let next_version = MetadataVersion::Number(tuf.trusted_root().version() + 1);
532            let res = remote
533                .fetch_metadata(&root_path, next_version, config.max_root_length, vec![])
534                .await;
535
536            let raw_signed_root = match res {
537                Ok(raw_signed_root) => raw_signed_root,
538                Err(Error::MetadataNotFound { .. }) => {
539                    break;
540                }
541                Err(err) => {
542                    return Err(err);
543                }
544            };
545
546            updated = true;
547
548            tuf.update_root(&raw_signed_root)?;
549
550            /////////////////////////////////////////
551            // TUF-1.0.9 §5.1.7:
552            //
553            //     Persist root metadata. The client MUST write the file to non-volatile storage as
554            //     FILENAME.EXT (e.g. root.json).
555
556            if let Some(ref mut local) = local {
557                local
558                    .store_metadata(&root_path, MetadataVersion::None, &raw_signed_root)
559                    .await?;
560
561                // NOTE(#301): See the comment in `Client::with_trusted_root_keys`.
562                local
563                    .store_metadata(&root_path, next_version, &raw_signed_root)
564                    .await?;
565            }
566
567            /////////////////////////////////////////
568            // TUF-1.0.9 §5.1.8:
569            //
570            //     Repeat steps 5.1.1 to 5.1.8.
571        }
572
573        /////////////////////////////////////////
574        // TUF-1.0.9 §5.1.9:
575        //
576        //     Check for a freeze attack. The latest known time MUST be lower than the expiration
577        //     timestamp in the trusted root metadata file (version N). If the trusted root
578        //     metadata file has expired, abort the update cycle, report the potential freeze
579        //     attack. On the next update cycle, begin at step 5.0 and version N of the root
580        //     metadata file.
581
582        // TODO: Consider moving the root metadata expiration check into `tuf::Database`, since that's
583        // where we check timestamp/snapshot/targets/delegations for expiration.
584        if tuf.trusted_root().expires() <= start_time {
585            error!("Root metadata expired, potential freeze attack");
586            return Err(Error::ExpiredMetadata {
587                path: MetadataPath::root(),
588                expiration: *tuf.trusted_root().expires(),
589                now: *start_time,
590            });
591        }
592
593        /////////////////////////////////////////
594        // TUF-1.0.5 §5.1.10:
595        //
596        //     Set whether consistent snapshots are used as per the trusted root metadata file (see
597        //     Section 4.3).
598
599        Ok(updated)
600    }
601
602    /// Returns `true` if an update occurred and `false` otherwise.
603    async fn update_timestamp(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
604        Self::update_timestamp_with_repos(
605            start_time,
606            &self.config,
607            &mut self.tuf,
608            Some(&mut self.local),
609            &self.remote,
610        )
611        .await
612    }
613
614    async fn update_timestamp_with_repos<Remote>(
615        start_time: &DateTime<Utc>,
616        config: &Config,
617        tuf: &mut Database<D>,
618        local: Option<&mut Repository<L, D>>,
619        remote: &Repository<Remote, D>,
620    ) -> Result<bool>
621    where
622        Remote: RepositoryProvider<D>,
623    {
624        let timestamp_path = MetadataPath::timestamp();
625
626        /////////////////////////////////////////
627        // TUF-1.0.9 §5.2:
628        //
629        //     Download the timestamp metadata file, up to X number of bytes (because the size is
630        //     unknown). The value for X is set by the authors of the application using TUF. For
631        //     example, X may be tens of kilobytes. The filename used to download the timestamp
632        //     metadata file is of the fixed form FILENAME.EXT (e.g., timestamp.json).
633
634        let raw_signed_timestamp = remote
635            .fetch_metadata(
636                &timestamp_path,
637                MetadataVersion::None,
638                config.max_timestamp_length,
639                vec![],
640            )
641            .await?;
642
643        if tuf
644            .update_timestamp(start_time, &raw_signed_timestamp)?
645            .is_some()
646        {
647            /////////////////////////////////////////
648            // TUF-1.0.9 §5.2.4:
649            //
650            //     Persist timestamp metadata. The client MUST write the file to non-volatile
651            //     storage as FILENAME.EXT (e.g. timestamp.json).
652
653            if let Some(local) = local {
654                local
655                    .store_metadata(
656                        &timestamp_path,
657                        MetadataVersion::None,
658                        &raw_signed_timestamp,
659                    )
660                    .await?;
661            }
662
663            Ok(true)
664        } else {
665            Ok(false)
666        }
667    }
668
669    /// Returns `true` if an update occurred and `false` otherwise.
670    async fn update_snapshot(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
671        let consistent_snapshot = self.tuf.trusted_root().consistent_snapshot();
672        Self::update_snapshot_with_repos(
673            start_time,
674            &self.config,
675            &mut self.tuf,
676            Some(&mut self.local),
677            &self.remote,
678            consistent_snapshot,
679        )
680        .await
681    }
682
683    async fn update_snapshot_with_repos<Remote>(
684        start_time: &DateTime<Utc>,
685        config: &Config,
686        tuf: &mut Database<D>,
687        local: Option<&mut Repository<L, D>>,
688        remote: &Repository<Remote, D>,
689        consistent_snapshots: bool,
690    ) -> Result<bool>
691    where
692        Remote: RepositoryProvider<D>,
693    {
694        let snapshot_description = match tuf.trusted_timestamp() {
695            Some(ts) => Ok(ts.snapshot()),
696            None => Err(Error::MetadataNotFound {
697                path: MetadataPath::timestamp(),
698                version: MetadataVersion::None,
699            }),
700        }?
701        .clone();
702
703        if snapshot_description.version()
704            <= tuf.trusted_snapshot().map(|s| s.version()).unwrap_or(0)
705        {
706            return Ok(false);
707        }
708
709        let version = if consistent_snapshots {
710            MetadataVersion::Number(snapshot_description.version())
711        } else {
712            MetadataVersion::None
713        };
714
715        let snapshot_path = MetadataPath::snapshot();
716
717        // https://theupdateframework.github.io/specification/v1.0.26/#update-snapshot 5.5.1:
718
719        // Download snapshot metadata file, up to either the number of bytes specified in the
720        // timestamp metadata file, or some Y number of bytes.
721        let snapshot_length = snapshot_description.length().or(config.max_snapshot_length);
722
723        // https://theupdateframework.github.io/specification/v1.0.26/#update-snapshot 5.5.2:
724        //
725        // [...] The hashes of the new snapshot metadata file MUST match the hashes, if any, listed
726        // in the trusted timestamp metadata.
727        let snapshot_hashes = crypto::retain_supported_hashes(snapshot_description.hashes());
728
729        let raw_signed_snapshot = remote
730            .fetch_metadata(&snapshot_path, version, snapshot_length, snapshot_hashes)
731            .await?;
732
733        // https://theupdateframework.github.io/specification/v1.0.26/#update-snapshot 5.5.3 through
734        // 5.5.6 are checked in [Database].
735        if tuf.update_snapshot(start_time, &raw_signed_snapshot)? {
736            // https://theupdateframework.github.io/specification/v1.0.26/#update-snapshot 5.5.7:
737            //
738            // Persist snapshot metadata. The client MUST write the file to non-volatile storage as
739            // FILENAME.EXT (e.g. snapshot.json).
740            if let Some(local) = local {
741                local
742                    .store_metadata(&snapshot_path, MetadataVersion::None, &raw_signed_snapshot)
743                    .await?;
744            }
745
746            Ok(true)
747        } else {
748            Ok(false)
749        }
750    }
751
752    /// Returns `true` if an update occurred and `false` otherwise.
753    async fn update_targets(&mut self, start_time: &DateTime<Utc>) -> Result<bool> {
754        let consistent_snapshot = self.tuf.trusted_root().consistent_snapshot();
755        Self::update_targets_with_repos(
756            start_time,
757            &self.config,
758            &mut self.tuf,
759            Some(&mut self.local),
760            &self.remote,
761            consistent_snapshot,
762        )
763        .await
764    }
765
766    async fn update_targets_with_repos<Remote>(
767        start_time: &DateTime<Utc>,
768        config: &Config,
769        tuf: &mut Database<D>,
770        local: Option<&mut Repository<L, D>>,
771        remote: &Repository<Remote, D>,
772        consistent_snapshot: bool,
773    ) -> Result<bool>
774    where
775        Remote: RepositoryProvider<D>,
776    {
777        let targets_description = match tuf.trusted_snapshot() {
778            Some(sn) => match sn.meta().get(&MetadataPath::targets()) {
779                Some(d) => Ok(d),
780                None => Err(Error::MissingMetadataDescription {
781                    parent_role: MetadataPath::snapshot(),
782                    child_role: MetadataPath::targets(),
783                }),
784            },
785            None => Err(Error::MetadataNotFound {
786                path: MetadataPath::snapshot(),
787                version: MetadataVersion::None,
788            }),
789        }?
790        .clone();
791
792        if targets_description.version() <= tuf.trusted_targets().map(|t| t.version()).unwrap_or(0)
793        {
794            return Ok(false);
795        }
796
797        let version = if consistent_snapshot {
798            MetadataVersion::Number(targets_description.version())
799        } else {
800            MetadataVersion::None
801        };
802
803        let targets_path = MetadataPath::targets();
804
805        // https://theupdateframework.github.io/specification/v1.0.26/#update-targets 5.6.1:
806        //
807        // Download the top-level targets metadata file, up to either the number of bytes specified
808        // in the snapshot metadata file, or some Z number of bytes. [...]
809        let targets_length = targets_description.length().or(config.max_targets_length);
810
811        // https://theupdateframework.github.io/specification/v1.0.26/#update-targets 5.6.2:
812        //
813        // Check against snapshot role’s targets hash. The hashes of the new targets metadata file
814        // MUST match the hashes, if any, listed in the trusted snapshot metadata. [...]
815        let target_hashes = crypto::retain_supported_hashes(targets_description.hashes());
816
817        let raw_signed_targets = remote
818            .fetch_metadata(&targets_path, version, targets_length, target_hashes)
819            .await?;
820
821        if tuf.update_targets(start_time, &raw_signed_targets)? {
822            /////////////////////////////////////////
823            // TUF-1.0.9 §5.4.4:
824            //
825            //     Persist targets metadata. The client MUST write the file to non-volatile storage
826            //     as FILENAME.EXT (e.g. targets.json).
827
828            if let Some(local) = local {
829                local
830                    .store_metadata(&targets_path, MetadataVersion::None, &raw_signed_targets)
831                    .await?;
832            }
833
834            Ok(true)
835        } else {
836            Ok(false)
837        }
838    }
839
840    /// Fetch a target from the remote repo.
841    ///
842    /// It is **critical** that none of the bytes written to the `write` are used until this future
843    /// returns `Ok`, as the hash of the target is not verified until all bytes are read from the
844    /// repository.
845    pub async fn fetch_target(
846        &mut self,
847        target: &TargetPath,
848    ) -> Result<impl AsyncRead + Send + Unpin + '_> {
849        self.fetch_target_with_start_time(target, &Utc::now()).await
850    }
851
852    /// Fetch a target from the remote repo.
853    ///
854    /// It is **critical** that none of the bytes written to the `write` are used until this future
855    /// returns `Ok`, as the hash of the target is not verified until all bytes are read from the
856    /// repository.
857    pub async fn fetch_target_with_start_time(
858        &mut self,
859        target: &TargetPath,
860        start_time: &DateTime<Utc>,
861    ) -> Result<impl AsyncRead + Send + Unpin + '_> {
862        let target_description = self
863            .fetch_target_description_with_start_time(target, start_time)
864            .await?;
865
866        // TODO: Check the local repository to see if it already has the target.
867        self.remote
868            .fetch_target(
869                self.tuf.trusted_root().consistent_snapshot(),
870                target,
871                target_description,
872            )
873            .await
874    }
875
876    /// Fetch a target from the remote repo and write it to the local repo.
877    ///
878    /// It is **critical** that none of the bytes written to the `write` are used until this future
879    /// returns `Ok`, as the hash of the target is not verified until all bytes are read from the
880    /// repository.
881    pub async fn fetch_target_to_local(&mut self, target: &TargetPath) -> Result<()> {
882        self.fetch_target_to_local_with_start_time(target, &Utc::now())
883            .await
884    }
885
886    /// Fetch a target from the remote repo and write it to the local repo.
887    ///
888    /// It is **critical** that none of the bytes written to the `write` are used until this future
889    /// returns `Ok`, as the hash of the target is not verified until all bytes are read from the
890    /// repository.
891    pub async fn fetch_target_to_local_with_start_time(
892        &mut self,
893        target: &TargetPath,
894        start_time: &DateTime<Utc>,
895    ) -> Result<()> {
896        let target_description = self
897            .fetch_target_description_with_start_time(target, start_time)
898            .await?;
899
900        // Since the async read we fetch from the remote repository has internal
901        // lifetimes, we need to break up client into sub-objects so that rust
902        // won't complain about trying to borrow `&self` for the fetch, and
903        // `&mut self` for the store.
904        let Client {
905            tuf, local, remote, ..
906        } = self;
907
908        // TODO: Check the local repository to see if it already has the target.
909        let mut read = remote
910            .fetch_target(
911                tuf.trusted_root().consistent_snapshot(),
912                target,
913                target_description,
914            )
915            .await?;
916
917        local.store_target(target, &mut read).await
918    }
919
920    /// Fetch a target description from the remote repo and return it.
921    pub async fn fetch_target_description(
922        &mut self,
923        target: &TargetPath,
924    ) -> Result<TargetDescription> {
925        self.fetch_target_description_with_start_time(target, &Utc::now())
926            .await
927    }
928
929    /// Fetch a target description from the remote repo and return it.
930    pub async fn fetch_target_description_with_start_time(
931        &mut self,
932        target: &TargetPath,
933        start_time: &DateTime<Utc>,
934    ) -> Result<TargetDescription> {
935        let snapshot = self
936            .tuf
937            .trusted_snapshot()
938            .ok_or_else(|| Error::MetadataNotFound {
939                path: MetadataPath::snapshot(),
940                version: MetadataVersion::None,
941            })?
942            .clone();
943
944        /////////////////////////////////////////
945        // https://theupdateframework.github.io/specification/v1.0.30/#update-targets:
946        //
947        //     7. **Perform a pre-order depth-first search for metadata about the
948        //     desired target, beginning with the top-level targets role.** Note: If
949        //     any metadata requested in steps 5.6.7.1 - 5.6.7.2 cannot be downloaded nor
950        //     validated, end the search and report that the target cannot be found.
951
952        let (_, target_description) = self
953            .lookup_target_description(start_time, false, 0, target, &snapshot, None)
954            .await;
955
956        target_description
957    }
958
959    async fn lookup_target_description(
960        &mut self,
961        start_time: &DateTime<Utc>,
962        default_terminate: bool,
963        current_depth: u32,
964        target: &TargetPath,
965        snapshot: &SnapshotMetadata,
966        targets: Option<(&Verified<TargetsMetadata>, MetadataPath)>,
967    ) -> (bool, Result<TargetDescription>) {
968        if current_depth > self.config.max_delegation_depth {
969            warn!(
970                "Walking the delegation graph would have exceeded the configured max depth: {}",
971                self.config.max_delegation_depth
972            );
973            return (
974                default_terminate,
975                Err(Error::TargetNotFound(target.clone())),
976            );
977        }
978
979        // these clones are dumb, but we need immutable values and not references for update
980        // tuf in the loop below
981        let (targets, targets_role) = match targets {
982            Some((t, role)) => (t.clone(), role),
983            None => match self.tuf.trusted_targets() {
984                Some(t) => (t.clone(), MetadataPath::targets()),
985                None => {
986                    return (
987                        default_terminate,
988                        Err(Error::MetadataNotFound {
989                            path: MetadataPath::targets(),
990                            version: MetadataVersion::None,
991                        }),
992                    );
993                }
994            },
995        };
996
997        if let Some(t) = targets.targets().get(target) {
998            return (default_terminate, Ok(t.clone()));
999        }
1000
1001        for delegation in targets.delegations().roles() {
1002            if !delegation.paths().iter().any(|p| target.is_child(p)) {
1003                if delegation.terminating() {
1004                    return (true, Err(Error::TargetNotFound(target.clone())));
1005                } else {
1006                    continue;
1007                }
1008            }
1009
1010            let role_meta = match snapshot.meta().get(delegation.name()) {
1011                Some(m) => m,
1012                None if delegation.terminating() => {
1013                    return (true, Err(Error::TargetNotFound(target.clone())));
1014                }
1015                None => {
1016                    continue;
1017                }
1018            };
1019
1020            /////////////////////////////////////////
1021            // TUF-1.0.9 §5.4:
1022            //
1023            //     Download the top-level targets metadata file, up to either the number of bytes
1024            //     specified in the snapshot metadata file, or some Z number of bytes. The value
1025            //     for Z is set by the authors of the application using TUF. For example, Z may be
1026            //     tens of kilobytes. If consistent snapshots are not used (see Section 7), then
1027            //     the filename used to download the targets metadata file is of the fixed form
1028            //     FILENAME.EXT (e.g., targets.json). Otherwise, the filename is of the form
1029            //     VERSION_NUMBER.FILENAME.EXT (e.g., 42.targets.json), where VERSION_NUMBER is the
1030            //     version number of the targets metadata file listed in the snapshot metadata
1031            //     file.
1032
1033            let version = if self.tuf.trusted_root().consistent_snapshot() {
1034                MetadataVersion::Number(role_meta.version())
1035            } else {
1036                MetadataVersion::None
1037            };
1038
1039            let role_length = role_meta.length().or(self.config.max_targets_length);
1040
1041            // https://theupdateframework.github.io/specification/v1.0.26/#update-targets
1042            //
1043            //     [...] The hashes of the new targets metadata file MUST match the hashes, if
1044            //      any, listed in the trusted snapshot metadata.
1045            let role_hashes = crypto::retain_supported_hashes(role_meta.hashes());
1046
1047            let raw_signed_meta = match self
1048                .remote
1049                .fetch_metadata(delegation.name(), version, role_length, role_hashes)
1050                .await
1051            {
1052                Ok(m) => m,
1053                Err(e) => {
1054                    warn!("Failed to fetch metadata {:?}: {:?}", delegation.name(), e);
1055                    if delegation.terminating() {
1056                        return (true, Err(e));
1057                    } else {
1058                        continue;
1059                    }
1060                }
1061            };
1062
1063            match self.tuf.update_delegated_targets(
1064                start_time,
1065                &targets_role,
1066                delegation.name(),
1067                &raw_signed_meta,
1068            ) {
1069                Ok(_) => {
1070                    /////////////////////////////////////////
1071                    // TUF-1.0.9 §5.4.4:
1072                    //
1073                    //     Persist targets metadata. The client MUST write the file to non-volatile
1074                    //     storage as FILENAME.EXT (e.g. targets.json).
1075
1076                    match self
1077                        .local
1078                        .store_metadata(delegation.name(), MetadataVersion::None, &raw_signed_meta)
1079                        .await
1080                    {
1081                        Ok(_) => (),
1082                        Err(e) => {
1083                            warn!(
1084                                "Error storing metadata {:?} locally: {:?}",
1085                                delegation.name(),
1086                                e
1087                            )
1088                        }
1089                    }
1090
1091                    let meta = self
1092                        .tuf
1093                        .trusted_delegations()
1094                        .get(delegation.name())
1095                        .unwrap()
1096                        .clone();
1097                    let f: Pin<Box<dyn Future<Output = _>>> =
1098                        Box::pin(self.lookup_target_description(
1099                            start_time,
1100                            delegation.terminating(),
1101                            current_depth + 1,
1102                            target,
1103                            snapshot,
1104                            Some((&meta, delegation.name().clone())),
1105                        ));
1106                    let (term, res) = f.await;
1107
1108                    if term && res.is_err() {
1109                        return (true, res);
1110                    }
1111
1112                    // TODO end recursion early
1113                }
1114                Err(_) if !delegation.terminating() => continue,
1115                Err(e) => return (true, Err(e)),
1116            };
1117        }
1118
1119        (
1120            default_terminate,
1121            Err(Error::TargetNotFound(target.clone())),
1122        )
1123    }
1124}
1125
1126/// Deconstructed parts of a [Client].
1127///
1128/// This allows taking apart a [Client] in order to reclaim the [Database],
1129/// local, and remote repositories.
1130#[non_exhaustive]
1131#[derive(Debug)]
1132pub struct Parts<D, L, R>
1133where
1134    D: Pouf,
1135    L: RepositoryProvider<D> + RepositoryStorage<D>,
1136    R: RepositoryProvider<D>,
1137{
1138    /// The client configuration.
1139    pub config: Config,
1140
1141    /// The Tuf database, which is updated by the [Client].
1142    pub database: Database<D>,
1143
1144    /// The local repository, which is used to initialize the database, and
1145    /// is updated by the [Client].
1146    pub local: L,
1147
1148    /// The remote repository, which is used by the client to update the database.
1149    pub remote: R,
1150}
1151
1152/// Helper function that first tries to fetch the metadata from the local store, and if it doesn't
1153/// exist or does and fails to parse, try fetching it from the remote store.
1154async fn fetch_metadata_from_local_or_else_remote<'a, D, L, R, M>(
1155    path: &'a MetadataPath,
1156    version: MetadataVersion,
1157    max_length: Option<usize>,
1158    hashes: Vec<(&'static HashAlgorithm, HashValue)>,
1159    local: &'a Repository<L, D>,
1160    remote: &'a Repository<R, D>,
1161) -> Result<(bool, RawSignedMetadata<D, M>)>
1162where
1163    D: Pouf,
1164    L: RepositoryProvider<D> + RepositoryStorage<D>,
1165    R: RepositoryProvider<D>,
1166    M: Metadata + 'static,
1167{
1168    match local
1169        .fetch_metadata(path, version, max_length, hashes.clone())
1170        .await
1171    {
1172        Ok(raw_meta) => Ok((false, raw_meta)),
1173        Err(Error::MetadataNotFound { .. }) => {
1174            let raw_meta = remote
1175                .fetch_metadata(path, version, max_length, hashes)
1176                .await?;
1177            Ok((true, raw_meta))
1178        }
1179        Err(err) => Err(err),
1180    }
1181}
1182
1183/// Configuration for a TUF `Client`.
1184///
1185/// # Defaults
1186///
1187/// The following values are considered reasonably safe defaults, however these values may change
1188/// as this crate moves out of beta. If you are concered about them changing, you should use the
1189/// `ConfigBuilder` and set your own values.
1190///
1191/// ```
1192/// # use tuf::client::{Config};
1193/// let config = Config::default();
1194/// assert_eq!(config.max_root_length(), &Some(500 * 1024));
1195/// assert_eq!(config.max_timestamp_length(), &Some(16 * 1024));
1196/// assert_eq!(config.max_snapshot_length(), &Some(2000000));
1197/// assert_eq!(config.max_targets_length(), &Some(5000000));
1198/// assert_eq!(config.max_delegation_depth(), 8);
1199/// ```
1200#[derive(Clone, Debug, PartialEq, Eq)]
1201pub struct Config {
1202    max_root_length: Option<usize>,
1203    max_timestamp_length: Option<usize>,
1204    max_snapshot_length: Option<usize>,
1205    max_targets_length: Option<usize>,
1206    max_delegation_depth: u32,
1207}
1208
1209impl Config {
1210    /// Initialize a `ConfigBuilder` with the default values.
1211    pub fn build() -> ConfigBuilder {
1212        ConfigBuilder::default()
1213    }
1214
1215    /// Return the optional maximum root metadata length.
1216    pub fn max_root_length(&self) -> &Option<usize> {
1217        &self.max_root_length
1218    }
1219
1220    /// Return the optional maximum timestamp metadata size.
1221    pub fn max_timestamp_length(&self) -> &Option<usize> {
1222        &self.max_timestamp_length
1223    }
1224
1225    /// Return the optional maximum snapshot metadata size.
1226    pub fn max_snapshot_length(&self) -> &Option<usize> {
1227        &self.max_snapshot_length
1228    }
1229
1230    /// Return the optional maximum targets metadata size.
1231    pub fn max_targets_length(&self) -> &Option<usize> {
1232        &self.max_targets_length
1233    }
1234
1235    /// The maximum number of steps used when walking the delegation graph.
1236    pub fn max_delegation_depth(&self) -> u32 {
1237        self.max_delegation_depth
1238    }
1239}
1240
1241impl Default for Config {
1242    fn default() -> Self {
1243        Config {
1244            max_root_length: Some(500 * 1024),
1245            max_timestamp_length: Some(16 * 1024),
1246            max_snapshot_length: Some(2000000),
1247            max_targets_length: Some(5000000),
1248            max_delegation_depth: 8,
1249        }
1250    }
1251}
1252
1253/// Helper for building and validating a TUF client `Config`.
1254#[derive(Debug, Default, PartialEq, Eq)]
1255pub struct ConfigBuilder {
1256    cfg: Config,
1257}
1258
1259impl ConfigBuilder {
1260    /// Validate this builder return a `Config` if validation succeeds.
1261    pub fn finish(self) -> Result<Config> {
1262        Ok(self.cfg)
1263    }
1264
1265    /// Set the optional maximum download length for root metadata.
1266    pub fn max_root_length(mut self, max: Option<usize>) -> Self {
1267        self.cfg.max_root_length = max;
1268        self
1269    }
1270
1271    /// Set the optional maximum download length for timestamp metadata.
1272    pub fn max_timestamp_length(mut self, max: Option<usize>) -> Self {
1273        self.cfg.max_timestamp_length = max;
1274        self
1275    }
1276
1277    /// Set the optional maximum download length for snapshot metadata.
1278    pub fn max_snapshot_length(mut self, max: Option<usize>) -> Self {
1279        self.cfg.max_snapshot_length = max;
1280        self
1281    }
1282
1283    /// Set the optional maximum download length for targets metadata.
1284    pub fn max_targets_length(mut self, max: Option<usize>) -> Self {
1285        self.cfg.max_targets_length = max;
1286        self
1287    }
1288
1289    /// Set the maximum number of steps used when walking the delegation graph.
1290    pub fn max_delegation_depth(mut self, max: u32) -> Self {
1291        self.cfg.max_delegation_depth = max;
1292        self
1293    }
1294}
1295
1296#[cfg(test)]
1297mod test {
1298    use super::*;
1299    use crate::crypto::{Ed25519PrivateKey, HashAlgorithm, PrivateKey};
1300    use crate::metadata::{
1301        MetadataDescription, MetadataPath, MetadataVersion, RootMetadataBuilder,
1302        SnapshotMetadataBuilder, TargetsMetadataBuilder, TimestampMetadataBuilder,
1303    };
1304    use crate::pouf::Pouf1;
1305    use crate::repo_builder::RepoBuilder;
1306    use crate::repository::{
1307        fetch_metadata_to_string, EphemeralRepository, ErrorRepository, Track, TrackRepository,
1308    };
1309    use assert_matches::assert_matches;
1310    use chrono::prelude::*;
1311    use futures_executor::block_on;
1312    use maplit::hashmap;
1313    use pretty_assertions::assert_eq;
1314    use serde_json::json;
1315    use std::collections::HashMap;
1316    use std::iter::once;
1317    use std::sync::LazyLock;
1318
1319    static KEYS: LazyLock<Vec<Ed25519PrivateKey>> = LazyLock::new(|| {
1320        let keys: &[&[u8]] = &[
1321            include_bytes!("../tests/ed25519/ed25519-1.pk8.der"),
1322            include_bytes!("../tests/ed25519/ed25519-2.pk8.der"),
1323            include_bytes!("../tests/ed25519/ed25519-3.pk8.der"),
1324            include_bytes!("../tests/ed25519/ed25519-4.pk8.der"),
1325            include_bytes!("../tests/ed25519/ed25519-5.pk8.der"),
1326            include_bytes!("../tests/ed25519/ed25519-6.pk8.der"),
1327        ];
1328        keys.iter()
1329            .map(|b| Ed25519PrivateKey::from_pkcs8(b).unwrap())
1330            .collect()
1331    });
1332
1333    #[allow(clippy::enum_variant_names)]
1334    enum ConstructorMode {
1335        WithTrustedLocal,
1336        WithTrustedRoot,
1337        WithTrustedRootKeys,
1338        FromDatabase,
1339    }
1340
1341    #[test]
1342    fn client_constructors_err_with_not_found() {
1343        block_on(async {
1344            let mut local = EphemeralRepository::<Pouf1>::new();
1345            let remote = EphemeralRepository::<Pouf1>::new();
1346
1347            let private_key =
1348                Ed25519PrivateKey::from_pkcs8(&Ed25519PrivateKey::pkcs8().unwrap()).unwrap();
1349            let public_key = private_key.public().clone();
1350
1351            assert_matches!(
1352                Client::with_trusted_local(Config::default(), &mut local, &remote).await,
1353                Err(Error::MetadataNotFound { path, version })
1354                if path == MetadataPath::root() && version == MetadataVersion::Number(1)
1355            );
1356
1357            assert_matches!(
1358                Client::with_trusted_root_keys(
1359                    Config::default(),
1360                    MetadataVersion::Number(1),
1361                    1,
1362                    once(&public_key),
1363                    local,
1364                    &remote,
1365                )
1366                .await,
1367                Err(Error::MetadataNotFound { path, version })
1368                if path == MetadataPath::root() && version == MetadataVersion::Number(1)
1369            );
1370        })
1371    }
1372
1373    #[test]
1374    fn client_constructors_err_with_invalid_keys() {
1375        block_on(async {
1376            let mut remote = EphemeralRepository::<Pouf1>::new();
1377
1378            let good_private_key = &KEYS[0];
1379            let bad_private_key = &KEYS[1];
1380
1381            let _ = RepoBuilder::create(&mut remote)
1382                .trusted_root_keys(&[good_private_key])
1383                .trusted_targets_keys(&[good_private_key])
1384                .trusted_snapshot_keys(&[good_private_key])
1385                .trusted_timestamp_keys(&[good_private_key])
1386                .commit()
1387                .await
1388                .unwrap();
1389
1390            assert_matches!(
1391                Client::with_trusted_root_keys(
1392                    Config::default(),
1393                    MetadataVersion::Number(1),
1394                    1,
1395                    once(bad_private_key.public()),
1396                    EphemeralRepository::new(),
1397                    &remote,
1398                )
1399                .await,
1400                Err(Error::MetadataMissingSignatures { role, number_of_valid_signatures: 0, threshold: 1 })
1401                if role == MetadataPath::root()
1402            );
1403        })
1404    }
1405
1406    #[test]
1407    fn with_trusted_local_loads_metadata_from_local_repo() {
1408        block_on(constructors_load_metadata_from_local_repo(
1409            ConstructorMode::WithTrustedLocal,
1410        ))
1411    }
1412
1413    #[test]
1414    fn with_trusted_root_loads_metadata_from_local_repo() {
1415        block_on(constructors_load_metadata_from_local_repo(
1416            ConstructorMode::WithTrustedRoot,
1417        ))
1418    }
1419
1420    #[test]
1421    fn with_trusted_root_keys_loads_metadata_from_local_repo() {
1422        block_on(constructors_load_metadata_from_local_repo(
1423            ConstructorMode::WithTrustedRootKeys,
1424        ))
1425    }
1426
1427    #[test]
1428    fn from_database_loads_metadata_from_local_repo() {
1429        block_on(constructors_load_metadata_from_local_repo(
1430            ConstructorMode::FromDatabase,
1431        ))
1432    }
1433
1434    async fn constructors_load_metadata_from_local_repo(constructor_mode: ConstructorMode) {
1435        // Store an expired root in the local store.
1436        let mut local = EphemeralRepository::<Pouf1>::new();
1437        let metadata1 = RepoBuilder::create(&mut local)
1438            .current_time(Utc.timestamp_opt(0, 0).unwrap())
1439            .trusted_root_keys(&[&KEYS[0]])
1440            .trusted_targets_keys(&[&KEYS[0]])
1441            .trusted_snapshot_keys(&[&KEYS[0]])
1442            .trusted_timestamp_keys(&[&KEYS[0]])
1443            .stage_root_with_builder(|bld| bld.consistent_snapshot(true))
1444            .unwrap()
1445            .commit()
1446            .await
1447            .unwrap();
1448
1449        // Remote repo has unexpired metadata.
1450        let mut remote = EphemeralRepository::<Pouf1>::new();
1451        let metadata2 = RepoBuilder::create(&mut remote)
1452            .trusted_root_keys(&[&KEYS[0]])
1453            .trusted_targets_keys(&[&KEYS[0]])
1454            .trusted_snapshot_keys(&[&KEYS[0]])
1455            .trusted_timestamp_keys(&[&KEYS[0]])
1456            .stage_root_with_builder(|bld| bld.version(2).consistent_snapshot(true))
1457            .unwrap()
1458            .stage_targets_with_builder(|bld| bld.version(2))
1459            .unwrap()
1460            .stage_snapshot_with_builder(|bld| bld.version(2))
1461            .unwrap()
1462            .stage_timestamp_with_builder(|bld| bld.version(2))
1463            .unwrap()
1464            .commit()
1465            .await
1466            .unwrap();
1467
1468        // Now, make sure that the local metadata got version 1.
1469        let track_local = TrackRepository::new(local);
1470        let track_remote = TrackRepository::new(remote);
1471
1472        // Make sure the client initialized metadata in the right order. Each has a slightly
1473        // different usage of the local repository.
1474        let mut client = match constructor_mode {
1475            ConstructorMode::WithTrustedLocal => {
1476                Client::with_trusted_local(Config::default(), track_local, track_remote)
1477                    .await
1478                    .unwrap()
1479            }
1480            ConstructorMode::WithTrustedRoot => Client::with_trusted_root(
1481                Config::default(),
1482                metadata1.root().unwrap(),
1483                track_local,
1484                track_remote,
1485            )
1486            .await
1487            .unwrap(),
1488            ConstructorMode::WithTrustedRootKeys => Client::with_trusted_root_keys(
1489                Config::default(),
1490                MetadataVersion::Number(1),
1491                1,
1492                once(&KEYS[0].public().clone()),
1493                track_local,
1494                track_remote,
1495            )
1496            .await
1497            .unwrap(),
1498            ConstructorMode::FromDatabase => Client::from_database(
1499                Config::default(),
1500                Database::from_trusted_root(metadata1.root().unwrap()).unwrap(),
1501                track_local,
1502                track_remote,
1503            ),
1504        };
1505
1506        assert_eq!(client.tuf.trusted_root().version(), 1);
1507        assert_eq!(client.remote_repo().take_tracks(), vec![]);
1508
1509        // According to [1], "Check for freeze attack", only the root should be
1510        // fetched since it has expired.
1511        //
1512        // [1]: https://theupdateframework.github.io/specification/latest/#update-root
1513        match constructor_mode {
1514            ConstructorMode::WithTrustedLocal => {
1515                assert_eq!(
1516                    client.local_repo().take_tracks(),
1517                    vec![
1518                        Track::fetch_meta_found(
1519                            MetadataVersion::Number(1),
1520                            metadata1.root().unwrap()
1521                        ),
1522                        Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(2)),
1523                    ],
1524                );
1525            }
1526            ConstructorMode::WithTrustedRoot => {
1527                assert_eq!(
1528                    client.local_repo().take_tracks(),
1529                    vec![Track::FetchErr(
1530                        MetadataPath::root(),
1531                        MetadataVersion::Number(2)
1532                    )],
1533                );
1534            }
1535            ConstructorMode::WithTrustedRootKeys => {
1536                assert_eq!(
1537                    client.local_repo().take_tracks(),
1538                    vec![
1539                        Track::fetch_meta_found(
1540                            MetadataVersion::Number(1),
1541                            metadata1.root().unwrap()
1542                        ),
1543                        Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(2)),
1544                    ],
1545                );
1546            }
1547            ConstructorMode::FromDatabase => {
1548                assert_eq!(client.local_repo().take_tracks(), vec![],);
1549            }
1550        };
1551
1552        assert_matches!(client.update().await, Ok(true));
1553        assert_eq!(client.tuf.trusted_root().version(), 2);
1554
1555        // We should only fetch metadata from the remote repository and write it to the local
1556        // repository.
1557        assert_eq!(
1558            client.remote_repo().take_tracks(),
1559            vec![
1560                Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.root().unwrap()),
1561                Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(3)),
1562                Track::fetch_meta_found(MetadataVersion::None, metadata2.timestamp().unwrap()),
1563                Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.snapshot().unwrap()),
1564                Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.targets().unwrap()),
1565            ],
1566        );
1567        assert_eq!(
1568            client.local_repo().take_tracks(),
1569            vec![
1570                Track::store_meta(MetadataVersion::None, metadata2.root().unwrap()),
1571                Track::store_meta(MetadataVersion::Number(2), metadata2.root().unwrap()),
1572                Track::store_meta(MetadataVersion::None, metadata2.timestamp().unwrap()),
1573                Track::store_meta(MetadataVersion::None, metadata2.snapshot().unwrap()),
1574                Track::store_meta(MetadataVersion::None, metadata2.targets().unwrap()),
1575            ],
1576        );
1577
1578        // Another update should not fetch anything.
1579        assert_matches!(client.update().await, Ok(false));
1580        assert_eq!(client.tuf.trusted_root().version(), 2);
1581
1582        // Make sure we only fetched the next root and timestamp, and didn't store anything.
1583        assert_eq!(
1584            client.remote_repo().take_tracks(),
1585            vec![
1586                Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(3)),
1587                Track::fetch_meta_found(MetadataVersion::None, metadata2.timestamp().unwrap()),
1588            ]
1589        );
1590        assert_eq!(client.local_repo().take_tracks(), vec![]);
1591    }
1592
1593    #[test]
1594    fn constructor_succeeds_with_missing_metadata() {
1595        block_on(async {
1596            let mut local = EphemeralRepository::<Pouf1>::new();
1597            let remote = EphemeralRepository::<Pouf1>::new();
1598
1599            // Store only a root in the local store.
1600            let metadata1 = RepoBuilder::create(&mut local)
1601                .trusted_root_keys(&[&KEYS[0]])
1602                .trusted_targets_keys(&[&KEYS[0]])
1603                .trusted_snapshot_keys(&[&KEYS[0]])
1604                .trusted_timestamp_keys(&[&KEYS[0]])
1605                .stage_root_with_builder(|bld| bld.consistent_snapshot(true))
1606                .unwrap()
1607                .skip_targets()
1608                .skip_snapshot()
1609                .skip_timestamp()
1610                .commit()
1611                .await
1612                .unwrap();
1613
1614            let track_local = TrackRepository::new(local);
1615            let track_remote = TrackRepository::new(remote);
1616
1617            // Create a client, which should try to fetch metadata from the local store.
1618            let client = Client::with_trusted_root(
1619                Config::default(),
1620                metadata1.root().unwrap(),
1621                track_local,
1622                track_remote,
1623            )
1624            .await
1625            .unwrap();
1626
1627            assert_eq!(client.tuf.trusted_root().version(), 1);
1628
1629            // We shouldn't fetch metadata.
1630            assert_eq!(client.remote_repo().take_tracks(), vec![]);
1631
1632            // We should have tried fetching a new timestamp, but it shouldn't exist in the
1633            // repository.
1634            assert_eq!(
1635                client.local_repo().take_tracks(),
1636                vec![
1637                    Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(2)),
1638                    Track::FetchErr(MetadataPath::timestamp(), MetadataVersion::None)
1639                ],
1640            );
1641
1642            // An update should succeed.
1643            let mut parts = client.into_parts();
1644            let metadata2 = RepoBuilder::create(parts.remote.as_inner_mut())
1645                .trusted_root_keys(&[&KEYS[0]])
1646                .trusted_targets_keys(&[&KEYS[0]])
1647                .trusted_snapshot_keys(&[&KEYS[0]])
1648                .trusted_timestamp_keys(&[&KEYS[0]])
1649                .stage_root_with_builder(|bld| bld.version(2).consistent_snapshot(true))
1650                .unwrap()
1651                .stage_targets_with_builder(|bld| bld.version(2))
1652                .unwrap()
1653                .stage_snapshot_with_builder(|bld| bld.version(2))
1654                .unwrap()
1655                .stage_timestamp_with_builder(|bld| bld.version(2))
1656                .unwrap()
1657                .commit()
1658                .await
1659                .unwrap();
1660
1661            let mut client = Client::from_parts(parts);
1662            assert_matches!(client.update().await, Ok(true));
1663            assert_eq!(client.tuf.trusted_root().version(), 2);
1664
1665            // We should have fetched the metadata, and written it to the local database.
1666            assert_eq!(
1667                client.remote_repo().take_tracks(),
1668                vec![
1669                    Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.root().unwrap()),
1670                    Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(3)),
1671                    Track::fetch_meta_found(MetadataVersion::None, metadata2.timestamp().unwrap()),
1672                    Track::fetch_meta_found(
1673                        MetadataVersion::Number(2),
1674                        metadata2.snapshot().unwrap()
1675                    ),
1676                    Track::fetch_meta_found(
1677                        MetadataVersion::Number(2),
1678                        metadata2.targets().unwrap()
1679                    ),
1680                ],
1681            );
1682            assert_eq!(
1683                client.local_repo().take_tracks(),
1684                vec![
1685                    Track::store_meta(MetadataVersion::None, metadata2.root().unwrap()),
1686                    Track::store_meta(MetadataVersion::Number(2), metadata2.root().unwrap()),
1687                    Track::store_meta(MetadataVersion::None, metadata2.timestamp().unwrap()),
1688                    Track::store_meta(MetadataVersion::None, metadata2.snapshot().unwrap()),
1689                    Track::store_meta(MetadataVersion::None, metadata2.targets().unwrap()),
1690                ],
1691            );
1692        })
1693    }
1694
1695    #[test]
1696    fn constructor_succeeds_with_expired_metadata() {
1697        block_on(async {
1698            let mut local = EphemeralRepository::<Pouf1>::new();
1699            let remote = EphemeralRepository::<Pouf1>::new();
1700
1701            // Store an expired root in the local store.
1702            let metadata1 = RepoBuilder::create(&mut local)
1703                .current_time(Utc.timestamp_opt(0, 0).unwrap())
1704                .trusted_root_keys(&[&KEYS[0]])
1705                .trusted_targets_keys(&[&KEYS[0]])
1706                .trusted_snapshot_keys(&[&KEYS[0]])
1707                .trusted_timestamp_keys(&[&KEYS[0]])
1708                .stage_root_with_builder(|bld| bld.version(1).consistent_snapshot(true))
1709                .unwrap()
1710                .commit()
1711                .await
1712                .unwrap();
1713
1714            let metadata2 = RepoBuilder::create(&mut local)
1715                .current_time(Utc.timestamp_opt(0, 0).unwrap())
1716                .trusted_root_keys(&[&KEYS[0]])
1717                .trusted_targets_keys(&[&KEYS[0]])
1718                .trusted_snapshot_keys(&[&KEYS[0]])
1719                .trusted_timestamp_keys(&[&KEYS[0]])
1720                .stage_root_with_builder(|bld| bld.version(2).consistent_snapshot(true))
1721                .unwrap()
1722                .stage_targets_with_builder(|bld| bld.version(2))
1723                .unwrap()
1724                .stage_snapshot_with_builder(|bld| bld.version(2))
1725                .unwrap()
1726                .stage_timestamp_with_builder(|bld| bld.version(2))
1727                .unwrap()
1728                .commit()
1729                .await
1730                .unwrap();
1731
1732            // Now, make sure that the local metadata got version 1.
1733            let track_local = TrackRepository::new(local);
1734            let track_remote = TrackRepository::new(remote);
1735
1736            let client = Client::with_trusted_root(
1737                Config::default(),
1738                metadata1.root().unwrap(),
1739                track_local,
1740                track_remote,
1741            )
1742            .await
1743            .unwrap();
1744
1745            assert_eq!(client.tuf.trusted_root().version(), 2);
1746
1747            // We shouldn't fetch metadata.
1748            assert_eq!(client.remote_repo().take_tracks(), vec![]);
1749
1750            // We should only load the root metadata, but because it's expired we don't try
1751            // fetching the other local metadata.
1752            assert_eq!(
1753                client.local_repo().take_tracks(),
1754                vec![
1755                    Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.root().unwrap()),
1756                    Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(3))
1757                ],
1758            );
1759
1760            // An update should succeed.
1761            let mut parts = client.into_parts();
1762            let _metadata3 = RepoBuilder::create(&mut parts.remote)
1763                .trusted_root_keys(&[&KEYS[0]])
1764                .trusted_targets_keys(&[&KEYS[0]])
1765                .trusted_snapshot_keys(&[&KEYS[0]])
1766                .trusted_timestamp_keys(&[&KEYS[0]])
1767                .stage_root_with_builder(|bld| {
1768                    bld.version(3)
1769                        .consistent_snapshot(true)
1770                        .expires(Utc.with_ymd_and_hms(2038, 1, 1, 0, 0, 0).unwrap())
1771                })
1772                .unwrap()
1773                .stage_targets_with_builder(|bld| bld.version(2))
1774                .unwrap()
1775                .stage_snapshot_with_builder(|bld| bld.version(2))
1776                .unwrap()
1777                .stage_timestamp_with_builder(|bld| bld.version(2))
1778                .unwrap()
1779                .commit()
1780                .await
1781                .unwrap();
1782
1783            let mut client = Client::from_parts(parts);
1784            assert_matches!(client.update().await, Ok(true));
1785            assert_eq!(client.tuf.trusted_root().version(), 3);
1786        })
1787    }
1788
1789    #[test]
1790    fn constructor_succeeds_with_malformed_metadata() {
1791        block_on(async {
1792            // Store a malformed timestamp in the local repository.
1793            let local = EphemeralRepository::<Pouf1>::new();
1794            let junk_timestamp = "junk timestamp";
1795
1796            local
1797                .store_metadata(
1798                    &MetadataPath::timestamp(),
1799                    MetadataVersion::None,
1800                    &mut junk_timestamp.as_bytes(),
1801                )
1802                .await
1803                .unwrap();
1804
1805            // Create a normal repository on the remote server.
1806            let mut remote = EphemeralRepository::<Pouf1>::new();
1807            let metadata1 = RepoBuilder::create(&mut remote)
1808                .trusted_root_keys(&[&KEYS[0]])
1809                .trusted_targets_keys(&[&KEYS[0]])
1810                .trusted_snapshot_keys(&[&KEYS[0]])
1811                .trusted_timestamp_keys(&[&KEYS[0]])
1812                .commit()
1813                .await
1814                .unwrap();
1815
1816            // Create the client. It should ignore the malformed timestamp.
1817            let track_local = TrackRepository::new(local);
1818            let track_remote = TrackRepository::new(remote);
1819
1820            let mut client = Client::with_trusted_root(
1821                Config::default(),
1822                metadata1.root().unwrap(),
1823                track_local,
1824                track_remote,
1825            )
1826            .await
1827            .unwrap();
1828
1829            assert_eq!(client.tuf.trusted_root().version(), 1);
1830
1831            // We shouldn't fetch metadata.
1832            assert_eq!(client.remote_repo().take_tracks(), vec![]);
1833
1834            // We should only load the root metadata, but because it's expired we don't try
1835            // fetching the other local metadata.
1836            assert_eq!(
1837                client.local_repo().take_tracks(),
1838                vec![
1839                    Track::FetchErr(MetadataPath::root(), MetadataVersion::Number(2)),
1840                    Track::FetchFound {
1841                        path: MetadataPath::timestamp(),
1842                        version: MetadataVersion::None,
1843                        metadata: junk_timestamp.into(),
1844                    },
1845                ],
1846            );
1847
1848            // An update should work.
1849            assert_matches!(client.update().await, Ok(true));
1850        })
1851    }
1852
1853    #[test]
1854    fn root_chain_update_consistent_snapshot_false() {
1855        block_on(root_chain_update(false))
1856    }
1857
1858    #[test]
1859    fn root_chain_update_consistent_snapshot_true() {
1860        block_on(root_chain_update(true))
1861    }
1862
1863    async fn root_chain_update(consistent_snapshot: bool) {
1864        let mut repo = EphemeralRepository::<Pouf1>::new();
1865
1866        // First, create the initial metadata. We want to use the same non-root
1867        // metadata, so sign it with all the keys.
1868        let metadata1 = RepoBuilder::create(&mut repo)
1869            .trusted_root_keys(&[&KEYS[0]])
1870            .signing_targets_keys(&[&KEYS[1], &KEYS[2]])
1871            .trusted_targets_keys(&[&KEYS[0]])
1872            .signing_snapshot_keys(&[&KEYS[1], &KEYS[2]])
1873            .trusted_snapshot_keys(&[&KEYS[0]])
1874            .signing_timestamp_keys(&[&KEYS[1], &KEYS[2]])
1875            .trusted_timestamp_keys(&[&KEYS[0]])
1876            .stage_root_with_builder(|bld| bld.consistent_snapshot(consistent_snapshot))
1877            .unwrap()
1878            .commit()
1879            .await
1880            .unwrap();
1881
1882        let root_path = MetadataPath::root();
1883        let timestamp_path = MetadataPath::timestamp();
1884
1885        let targets_version;
1886        let snapshot_version;
1887        if consistent_snapshot {
1888            targets_version = MetadataVersion::Number(1);
1889            snapshot_version = MetadataVersion::Number(1);
1890        } else {
1891            targets_version = MetadataVersion::None;
1892            snapshot_version = MetadataVersion::None;
1893        };
1894
1895        // Now, make sure that the local metadata got version 1.
1896        let track_local = TrackRepository::new(EphemeralRepository::new());
1897        let track_remote = TrackRepository::new(repo);
1898
1899        let mut client = Client::with_trusted_root_keys(
1900            Config::default(),
1901            MetadataVersion::Number(1),
1902            1,
1903            once(&KEYS[0].public().clone()),
1904            track_local,
1905            track_remote,
1906        )
1907        .await
1908        .unwrap();
1909
1910        // Check that we tried to load metadata from the local repository.
1911        assert_eq!(
1912            client.remote_repo().take_tracks(),
1913            vec![Track::fetch_found(
1914                &root_path,
1915                MetadataVersion::Number(1),
1916                metadata1.root().unwrap().as_bytes()
1917            ),]
1918        );
1919        assert_eq!(
1920            client.local_repo().take_tracks(),
1921            vec![
1922                Track::FetchErr(root_path.clone(), MetadataVersion::Number(1)),
1923                Track::store_meta(MetadataVersion::Number(1), metadata1.root().unwrap()),
1924                Track::FetchErr(root_path.clone(), MetadataVersion::Number(2)),
1925                Track::FetchErr(timestamp_path.clone(), MetadataVersion::None),
1926            ]
1927        );
1928
1929        assert_matches!(client.update().await, Ok(true));
1930        assert_eq!(client.tuf.trusted_root().version(), 1);
1931
1932        // Make sure we fetched the metadata in the right order.
1933        assert_eq!(
1934            client.remote_repo().take_tracks(),
1935            vec![
1936                Track::FetchErr(root_path.clone(), MetadataVersion::Number(2)),
1937                Track::fetch_meta_found(MetadataVersion::None, metadata1.timestamp().unwrap()),
1938                Track::fetch_meta_found(snapshot_version, metadata1.snapshot().unwrap()),
1939                Track::fetch_meta_found(targets_version, metadata1.targets().unwrap()),
1940            ]
1941        );
1942        assert_eq!(
1943            client.local_repo().take_tracks(),
1944            vec![
1945                Track::store_meta(MetadataVersion::None, metadata1.timestamp().unwrap()),
1946                Track::store_meta(MetadataVersion::None, metadata1.snapshot().unwrap()),
1947                Track::store_meta(MetadataVersion::None, metadata1.targets().unwrap()),
1948            ],
1949        );
1950
1951        // Another update should not fetch anything.
1952        assert_matches!(client.update().await, Ok(false));
1953        assert_eq!(client.tuf.trusted_root().version(), 1);
1954
1955        // Make sure we only fetched the next root and timestamp, and didn't store anything.
1956        assert_eq!(
1957            client.remote_repo().take_tracks(),
1958            vec![
1959                Track::FetchErr(root_path.clone(), MetadataVersion::Number(2)),
1960                Track::fetch_meta_found(MetadataVersion::None, metadata1.timestamp().unwrap()),
1961            ]
1962        );
1963        assert_eq!(client.local_repo().take_tracks(), vec![]);
1964
1965        ////
1966        // Now bump the root to version 3
1967
1968        // Make sure the version 2 is also signed by version 1's keys.
1969        //
1970        // Note that we write to the underlying store so TrackRepo doesn't track
1971        // this new metadata.
1972        let mut parts = client.into_parts();
1973        let metadata2 = RepoBuilder::create(parts.remote.as_inner_mut())
1974            .signing_root_keys(&[&KEYS[0]])
1975            .trusted_root_keys(&[&KEYS[1]])
1976            .trusted_targets_keys(&[&KEYS[1]])
1977            .trusted_snapshot_keys(&[&KEYS[1]])
1978            .trusted_timestamp_keys(&[&KEYS[1]])
1979            .stage_root_with_builder(|bld| bld.version(2).consistent_snapshot(consistent_snapshot))
1980            .unwrap()
1981            .skip_targets()
1982            .skip_snapshot()
1983            .skip_timestamp()
1984            .commit()
1985            .await
1986            .unwrap();
1987
1988        // Make sure the version 3 is also signed by version 2's keys.
1989        let metadata3 = RepoBuilder::create(parts.remote.as_inner_mut())
1990            .signing_root_keys(&[&KEYS[1]])
1991            .trusted_root_keys(&[&KEYS[2]])
1992            .trusted_targets_keys(&[&KEYS[2]])
1993            .trusted_snapshot_keys(&[&KEYS[2]])
1994            .trusted_timestamp_keys(&[&KEYS[2]])
1995            .stage_root_with_builder(|bld| bld.version(3).consistent_snapshot(consistent_snapshot))
1996            .unwrap()
1997            .skip_targets()
1998            .skip_snapshot()
1999            .skip_timestamp()
2000            .commit()
2001            .await
2002            .unwrap();
2003
2004        ////
2005        // Finally, check that the update brings us to version 3.
2006        let mut client = Client::from_parts(parts);
2007        assert_matches!(client.update().await, Ok(true));
2008        assert_eq!(client.tuf.trusted_root().version(), 3);
2009
2010        // Make sure we fetched and stored the metadata in the expected order. Note that we
2011        // re-fetch snapshot and targets because we rotated keys, which caused `tuf::Database` to delete
2012        // the metadata.
2013        assert_eq!(
2014            client.remote_repo().take_tracks(),
2015            vec![
2016                Track::fetch_meta_found(MetadataVersion::Number(2), metadata2.root().unwrap()),
2017                Track::fetch_meta_found(MetadataVersion::Number(3), metadata3.root().unwrap()),
2018                Track::FetchErr(root_path.clone(), MetadataVersion::Number(4)),
2019                Track::fetch_meta_found(MetadataVersion::None, metadata1.timestamp().unwrap()),
2020                Track::fetch_meta_found(snapshot_version, metadata1.snapshot().unwrap()),
2021                Track::fetch_meta_found(targets_version, metadata1.targets().unwrap()),
2022            ]
2023        );
2024        assert_eq!(
2025            client.local_repo().take_tracks(),
2026            vec![
2027                Track::store_meta(MetadataVersion::None, metadata2.root().unwrap()),
2028                Track::store_meta(MetadataVersion::Number(2), metadata2.root().unwrap()),
2029                Track::store_meta(MetadataVersion::None, metadata3.root().unwrap()),
2030                Track::store_meta(MetadataVersion::Number(3), metadata3.root().unwrap()),
2031                Track::store_meta(MetadataVersion::None, metadata1.timestamp().unwrap()),
2032                Track::store_meta(MetadataVersion::None, metadata1.snapshot().unwrap()),
2033                Track::store_meta(MetadataVersion::None, metadata1.targets().unwrap()),
2034            ],
2035        );
2036    }
2037
2038    #[test]
2039    fn test_fetch_target_description_standard() {
2040        block_on(test_fetch_target_description(
2041            "standard/metadata".to_string(),
2042            TargetDescription::from_slice(
2043                "target with no custom metadata".as_bytes(),
2044                &[HashAlgorithm::Sha256],
2045            )
2046            .unwrap(),
2047        ));
2048    }
2049
2050    #[test]
2051    fn test_fetch_target_description_custom_empty() {
2052        block_on(test_fetch_target_description(
2053            "custom-empty".to_string(),
2054            TargetDescription::from_slice_with_custom(
2055                "target with empty custom metadata".as_bytes(),
2056                &[HashAlgorithm::Sha256],
2057                hashmap!(),
2058            )
2059            .unwrap(),
2060        ));
2061    }
2062
2063    #[test]
2064    fn test_fetch_target_description_custom() {
2065        block_on(test_fetch_target_description(
2066            "custom/metadata".to_string(),
2067            TargetDescription::from_slice_with_custom(
2068                "target with lots of custom metadata".as_bytes(),
2069                &[HashAlgorithm::Sha256],
2070                hashmap!(
2071                    "string".to_string() => json!("string"),
2072                    "bool".to_string() => json!(true),
2073                    "int".to_string() => json!(42),
2074                    "object".to_string() => json!({
2075                        "string": json!("string"),
2076                        "bool": json!(true),
2077                        "int": json!(42),
2078                    }),
2079                    "array".to_string() => json!([1, 2, 3]),
2080                ),
2081            )
2082            .unwrap(),
2083        ));
2084    }
2085
2086    async fn test_fetch_target_description(path: String, expected_description: TargetDescription) {
2087        // Generate an ephemeral repository with a single target.
2088        let mut remote = EphemeralRepository::<Pouf1>::new();
2089
2090        let metadata = RepoBuilder::create(&mut remote)
2091            .trusted_root_keys(&[&KEYS[0]])
2092            .trusted_targets_keys(&[&KEYS[0]])
2093            .trusted_snapshot_keys(&[&KEYS[0]])
2094            .trusted_timestamp_keys(&[&KEYS[0]])
2095            .stage_root()
2096            .unwrap()
2097            .stage_targets_with_builder(|bld| {
2098                bld.insert_target_description(
2099                    TargetPath::new(path.clone()).unwrap(),
2100                    expected_description.clone(),
2101                )
2102            })
2103            .unwrap()
2104            .commit()
2105            .await
2106            .unwrap();
2107
2108        // Initialize and update client.
2109        let mut client = Client::with_trusted_root(
2110            Config::default(),
2111            metadata.root().unwrap(),
2112            EphemeralRepository::new(),
2113            remote,
2114        )
2115        .await
2116        .unwrap();
2117
2118        assert_matches!(client.update().await, Ok(true));
2119
2120        // Verify fetch_target_description returns expected target metadata
2121        let description = client
2122            .fetch_target_description(&TargetPath::new(path).unwrap())
2123            .await
2124            .unwrap();
2125
2126        assert_eq!(description, expected_description);
2127    }
2128
2129    #[test]
2130    fn update_eventually_succeeds_if_cannot_write_to_repo() {
2131        block_on(async {
2132            let mut remote = EphemeralRepository::<Pouf1>::new();
2133
2134            // First, create the metadata.
2135            let _ = RepoBuilder::create(&mut remote)
2136                .trusted_root_keys(&[&KEYS[0]])
2137                .trusted_targets_keys(&[&KEYS[0]])
2138                .trusted_snapshot_keys(&[&KEYS[0]])
2139                .trusted_timestamp_keys(&[&KEYS[0]])
2140                .commit()
2141                .await
2142                .unwrap();
2143
2144            // Now, make sure that the local metadata got version 1.
2145            let local = ErrorRepository::new(EphemeralRepository::new());
2146            let mut client = Client::with_trusted_root_keys(
2147                Config::default(),
2148                MetadataVersion::Number(1),
2149                1,
2150                once(&KEYS[0].public().clone()),
2151                local,
2152                remote,
2153            )
2154            .await
2155            .unwrap();
2156
2157            // The first update should succeed.
2158            assert_matches!(client.update().await, Ok(true));
2159
2160            // Make sure the database is correct.
2161            let mut parts = client.into_parts();
2162            assert_eq!(parts.database.trusted_root().version(), 1);
2163            assert_eq!(parts.database.trusted_timestamp().unwrap().version(), 1);
2164            assert_eq!(parts.database.trusted_snapshot().unwrap().version(), 1);
2165            assert_eq!(parts.database.trusted_targets().unwrap().version(), 1);
2166
2167            // Publish new metadata.
2168            let _ = RepoBuilder::create(&mut parts.remote)
2169                .trusted_root_keys(&[&KEYS[0]])
2170                .trusted_targets_keys(&[&KEYS[0]])
2171                .trusted_snapshot_keys(&[&KEYS[0]])
2172                .trusted_timestamp_keys(&[&KEYS[0]])
2173                .stage_root_with_builder(|bld| bld.version(2))
2174                .unwrap()
2175                .stage_targets_with_builder(|bld| bld.version(2))
2176                .unwrap()
2177                .stage_snapshot_with_builder(|bld| bld.version(2))
2178                .unwrap()
2179                .stage_timestamp_with_builder(|bld| bld.version(2))
2180                .unwrap()
2181                .commit()
2182                .await
2183                .unwrap();
2184
2185            // Make sure we fail to write metadata to the local store.
2186            parts.local.fail_metadata_stores(true);
2187
2188            // The second update should fail.
2189            let mut client = Client::from_parts(parts);
2190            assert_matches!(client.update().await, Err(Error::Encoding(_)));
2191
2192            // FIXME(#297): rust-tuf diverges from the spec by throwing away the
2193            // metadata if the root is updated.
2194            assert_eq!(client.database().trusted_root().version(), 2);
2195            assert_eq!(client.database().trusted_timestamp(), None);
2196            assert_eq!(client.database().trusted_snapshot(), None);
2197            assert_eq!(client.database().trusted_targets(), None);
2198
2199            // However, due to https://github.com/theupdateframework/specification/issues/131, if
2200            // the update is retried a few times it will still succeed.
2201            assert_matches!(client.update().await, Err(Error::Encoding(_)));
2202            assert_eq!(client.database().trusted_root().version(), 2);
2203            assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2204            assert_eq!(client.database().trusted_snapshot(), None);
2205            assert_eq!(client.database().trusted_targets(), None);
2206
2207            assert_matches!(client.update().await, Err(Error::Encoding(_)));
2208            assert_eq!(client.database().trusted_root().version(), 2);
2209            assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2210            assert_eq!(client.database().trusted_snapshot().unwrap().version(), 2);
2211            assert_eq!(client.database().trusted_targets(), None);
2212
2213            assert_matches!(client.update().await, Err(Error::Encoding(_)));
2214            assert_eq!(client.database().trusted_root().version(), 2);
2215            assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2216            assert_eq!(client.database().trusted_snapshot().unwrap().version(), 2);
2217            assert_eq!(client.database().trusted_targets().unwrap().version(), 2);
2218
2219            assert_matches!(client.update().await, Ok(false));
2220            assert_eq!(client.database().trusted_root().version(), 2);
2221            assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2222            assert_eq!(client.database().trusted_snapshot().unwrap().version(), 2);
2223            assert_eq!(client.database().trusted_targets().unwrap().version(), 2);
2224        });
2225    }
2226
2227    #[test]
2228    fn test_local_and_remote_repo_methods() {
2229        block_on(async {
2230            let local = EphemeralRepository::<Pouf1>::new();
2231            let mut remote = EphemeralRepository::<Pouf1>::new();
2232
2233            let metadata1 = RepoBuilder::create(&mut remote)
2234                .trusted_root_keys(&[&KEYS[0]])
2235                .trusted_targets_keys(&[&KEYS[0]])
2236                .trusted_snapshot_keys(&[&KEYS[0]])
2237                .trusted_timestamp_keys(&[&KEYS[0]])
2238                .stage_root()
2239                .unwrap()
2240                .stage_targets()
2241                .unwrap()
2242                .stage_snapshot()
2243                .unwrap()
2244                .stage_timestamp_with_builder(|bld| bld.version(1))
2245                .unwrap()
2246                .commit()
2247                .await
2248                .unwrap();
2249
2250            let mut client = Client::with_trusted_root(
2251                Config::default(),
2252                metadata1.root().unwrap(),
2253                local,
2254                remote,
2255            )
2256            .await
2257            .unwrap();
2258
2259            client.update().await.unwrap();
2260
2261            // Generate some new metadata.
2262            let metadata2 = RepoBuilder::from_database(
2263                &mut EphemeralRepository::<Pouf1>::new(),
2264                client.database(),
2265            )
2266            .trusted_root_keys(&[&KEYS[0]])
2267            .trusted_targets_keys(&[&KEYS[0]])
2268            .trusted_snapshot_keys(&[&KEYS[0]])
2269            .trusted_timestamp_keys(&[&KEYS[0]])
2270            .skip_root()
2271            .skip_targets()
2272            .skip_snapshot()
2273            .stage_timestamp_with_builder(|bld| bld.version(2))
2274            .unwrap()
2275            .commit()
2276            .await
2277            .unwrap();
2278
2279            // Make sure we can update the local and remote store through the client.
2280            client
2281                .local_repo_mut()
2282                .store_metadata(
2283                    &MetadataPath::timestamp(),
2284                    MetadataVersion::None,
2285                    &mut metadata2.timestamp().unwrap().as_bytes(),
2286                )
2287                .await
2288                .unwrap();
2289
2290            client
2291                .remote_repo_mut()
2292                .store_metadata(
2293                    &MetadataPath::timestamp(),
2294                    MetadataVersion::None,
2295                    &mut metadata2.timestamp().unwrap().as_bytes(),
2296                )
2297                .await
2298                .unwrap();
2299
2300            // Make sure we can read it back.
2301            let timestamp2 =
2302                String::from_utf8(metadata2.timestamp().unwrap().as_bytes().to_vec()).unwrap();
2303
2304            assert_eq!(
2305                &timestamp2,
2306                &fetch_metadata_to_string(
2307                    client.local_repo(),
2308                    &MetadataPath::timestamp(),
2309                    MetadataVersion::None,
2310                )
2311                .await
2312                .unwrap(),
2313            );
2314
2315            assert_eq!(
2316                &timestamp2,
2317                &fetch_metadata_to_string(
2318                    client.remote_repo(),
2319                    &MetadataPath::timestamp(),
2320                    MetadataVersion::None,
2321                )
2322                .await
2323                .unwrap(),
2324            );
2325
2326            // Finally, make sure we can update the database through the client as well.
2327            client.database_mut().update_metadata(&metadata2).unwrap();
2328            assert_eq!(client.database().trusted_timestamp().unwrap().version(), 2);
2329        })
2330    }
2331
2332    #[test]
2333    fn client_can_update_with_unknown_len_and_hashes() {
2334        block_on(async {
2335            let repo = EphemeralRepository::<Pouf1>::new();
2336
2337            let root = RootMetadataBuilder::new()
2338                .consistent_snapshot(true)
2339                .root_key(KEYS[0].public().clone())
2340                .targets_key(KEYS[1].public().clone())
2341                .snapshot_key(KEYS[2].public().clone())
2342                .timestamp_key(KEYS[3].public().clone())
2343                .signed::<Pouf1>(&KEYS[0])
2344                .unwrap()
2345                .to_raw()
2346                .unwrap();
2347
2348            repo.store_metadata(
2349                &MetadataPath::root(),
2350                MetadataVersion::Number(1),
2351                &mut root.as_bytes(),
2352            )
2353            .await
2354            .unwrap();
2355
2356            let targets = TargetsMetadataBuilder::new()
2357                .signed::<Pouf1>(&KEYS[1])
2358                .unwrap()
2359                .to_raw()
2360                .unwrap();
2361
2362            repo.store_metadata(
2363                &MetadataPath::targets(),
2364                MetadataVersion::Number(1),
2365                &mut targets.as_bytes(),
2366            )
2367            .await
2368            .unwrap();
2369
2370            // Create a targets metadata description, and deliberately don't set the metadata length
2371            // or hashes.
2372            let targets_description = MetadataDescription::new(1, None, HashMap::new()).unwrap();
2373
2374            let snapshot = SnapshotMetadataBuilder::new()
2375                .insert_metadata_description(MetadataPath::targets(), targets_description)
2376                .signed::<Pouf1>(&KEYS[2])
2377                .unwrap()
2378                .to_raw()
2379                .unwrap();
2380
2381            repo.store_metadata(
2382                &MetadataPath::snapshot(),
2383                MetadataVersion::Number(1),
2384                &mut snapshot.as_bytes(),
2385            )
2386            .await
2387            .unwrap();
2388
2389            // Create a snapshot metadata description, and deliberately don't set the metadata length
2390            // or hashes.
2391            let snapshot_description = MetadataDescription::new(1, None, HashMap::new()).unwrap();
2392
2393            let timestamp =
2394                TimestampMetadataBuilder::from_metadata_description(snapshot_description)
2395                    .signed::<Pouf1>(&KEYS[3])
2396                    .unwrap()
2397                    .to_raw()
2398                    .unwrap();
2399
2400            repo.store_metadata(
2401                &MetadataPath::timestamp(),
2402                MetadataVersion::None,
2403                &mut timestamp.as_bytes(),
2404            )
2405            .await
2406            .unwrap();
2407
2408            let mut client = Client::with_trusted_root_keys(
2409                Config::default(),
2410                MetadataVersion::Number(1),
2411                1,
2412                once(&KEYS[0].public().clone()),
2413                EphemeralRepository::new(),
2414                repo,
2415            )
2416            .await
2417            .unwrap();
2418
2419            assert_matches!(client.update().await, Ok(true));
2420        })
2421    }
2422}