Skip to main content

tough/schema/
mod.rs

1#![allow(clippy::used_underscore_binding, clippy::pub_underscore_fields)] // #20
2
3//! Provides the schema objects as defined by the TUF spec.
4
5mod de;
6pub mod decoded;
7mod error;
8mod iter;
9pub mod key;
10mod ser;
11mod spki;
12mod verify;
13
14use crate::schema::decoded::{Decoded, Hex};
15pub use crate::schema::error::{Error, Result};
16use crate::schema::iter::KeysIter;
17use crate::schema::key::Key;
18use crate::sign::Sign;
19pub use crate::transport::{FilesystemTransport, Transport};
20use crate::{encode_filename, TargetName};
21use aws_lc_rs::digest::{digest, Context, SHA256};
22use globset::{Glob, GlobMatcher};
23use hex::ToHex;
24use olpc_cjson::CanonicalFormatter;
25use serde::de::Error as SerdeDeError;
26use serde::{Deserialize, Deserializer, Serialize, Serializer};
27use serde_json::Value;
28use serde_plain::{derive_display_from_serialize, derive_fromstr_from_deserialize};
29use snafu::ResultExt;
30use std::collections::{BTreeSet, HashMap};
31use std::num::NonZeroU64;
32use std::ops::{Deref, DerefMut};
33use std::path::Path;
34use std::str::FromStr;
35use tokio::fs::File;
36use tokio::io::AsyncReadExt;
37
38/// The type of metadata role.
39#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
40#[serde(rename_all = "kebab-case")]
41pub enum RoleType {
42    /// The root role delegates trust to specific keys trusted for all other top-level roles used in
43    /// the system.
44    Root,
45    /// The snapshot role signs a metadata file that provides information about the latest version
46    /// of all targets metadata on the repository (the top-level targets role and all delegated
47    /// roles).
48    Snapshot,
49    /// The targets role's signature indicates which target files are trusted by clients.
50    Targets,
51    /// The timestamp role is used to prevent an adversary from replaying an out-of-date signed
52    /// metadata file whose signature has not yet expired.
53    Timestamp,
54    /// A delegated targets role
55    DelegatedTargets,
56}
57
58derive_display_from_serialize!(RoleType);
59derive_fromstr_from_deserialize!(RoleType);
60
61/// A role identifier
62#[derive(Debug, Clone)]
63pub enum RoleId {
64    /// Top level roles are identified by a `RoleType`
65    StandardRole(RoleType),
66    /// A delegated role is identified by a String
67    DelegatedRole(String),
68}
69
70/// Common trait implemented by all roles.
71pub trait Role: Serialize {
72    /// The type of role this object represents.
73    const TYPE: RoleType;
74
75    /// Determines when metadata should be considered expired and no longer trusted by clients.
76    fn expires(&self) -> jiff::Timestamp;
77
78    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
79    /// number less than the one currently trusted.
80    fn version(&self) -> NonZeroU64;
81
82    /// The filename that the role metadata should be written to
83    fn filename(&self, consistent_snapshot: bool) -> String;
84
85    /// The `RoleId` corresponding to the role
86    fn role_id(&self) -> RoleId {
87        RoleId::StandardRole(Self::TYPE)
88    }
89
90    /// A deterministic JSON serialization used when calculating the digest of a metadata object.
91    /// [More info on canonical JSON](http://wiki.laptop.org/go/Canonical_JSON)
92    fn canonical_form(&self) -> Result<Vec<u8>> {
93        let mut data = Vec::new();
94        let mut ser = serde_json::Serializer::with_formatter(&mut data, CanonicalFormatter::new());
95        self.serialize(&mut ser)
96            .context(error::JsonSerializationSnafu { what: "role" })?;
97        Ok(data)
98    }
99}
100
101/// A signed metadata object.
102#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
103pub struct Signed<T> {
104    /// The role that is signed.
105    pub signed: T,
106    /// A list of signatures and their key IDs.
107    pub signatures: Vec<Signature>,
108}
109
110/// A signature and the key ID that made it.
111#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
112pub struct Signature {
113    /// The key ID (listed in root.json) that made this signature.
114    pub keyid: Decoded<Hex>,
115    /// A hex-encoded signature of the canonical JSON form of a role.
116    pub sig: Decoded<Hex>,
117}
118
119/// A `KeyHolder` is metadata that is responsible for verifying the signatures of a role.
120/// `KeyHolder` contains either a `Delegations` of a `Targets` or a `Root`
121#[derive(Debug, Clone)]
122pub enum KeyHolder {
123    /// Delegations verify delegated targets
124    Delegations(Delegations),
125    /// Root verifies the top level targets, snapshot, timestamp, and root
126    Root(Root),
127}
128
129// =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=
130
131/// TUF 4.3: The root.json file is signed by the root role's keys. It indicates which keys are
132/// authorized for all top-level roles, including the root role itself. Revocation and replacement
133/// of top-level role keys, including for the root role, is done by changing the keys listed for the
134/// roles in this file.
135#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
136#[serde(tag = "_type")]
137#[serde(rename = "root")]
138pub struct Root {
139    /// A string that contains the version number of the TUF specification. Its format follows the
140    /// Semantic Versioning 2.0.0 (semver) specification.
141    pub spec_version: String,
142
143    /// A boolean indicating whether the repository supports consistent snapshots. When consistent
144    /// snapshots is `true`, targets and certain metadata filenames are prefixed with either a
145    /// a version number or digest.
146    pub consistent_snapshot: bool,
147
148    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
149    /// number less than the one currently trusted.
150    pub version: NonZeroU64,
151
152    /// Determines when metadata should be considered expired and no longer trusted by clients.
153    #[serde(serialize_with = "ser::serialize_timestamp")]
154    pub expires: jiff::Timestamp,
155
156    /// The KEYID must be correct for the specified KEY. Clients MUST calculate each KEYID to verify
157    /// this is correct for the associated key. Clients MUST ensure that for any KEYID represented
158    /// in this key list and in other files, only one unique key has that KEYID.
159    #[serde(deserialize_with = "de::deserialize_keys")]
160    pub keys: HashMap<Decoded<Hex>, Key>,
161
162    /// A list of roles, the keys associated with each role, and the threshold of signatures used
163    /// for each role.
164    pub roles: HashMap<RoleType, RoleKeys>,
165
166    /// Extra arguments found during deserialization.
167    ///
168    /// We must store these to correctly verify signatures for this object.
169    ///
170    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
171    #[serde(flatten)]
172    #[serde(deserialize_with = "de::extra_skip_type")]
173    pub _extra: HashMap<String, Value>,
174}
175
176/// Represents the key IDs used for a role and the threshold of signatures required to validate it.
177/// TUF 4.3: A ROLE is one of "root", "snapshot", "targets", "timestamp", or "mirrors". A role for
178/// each of "root", "snapshot", "timestamp", and "targets" MUST be specified in the key list.
179/// The role of "mirror" is optional. If not specified, the mirror list will not need to be signed
180/// if mirror lists are being used. The THRESHOLD for a role is an integer of the number of keys of
181/// that role whose signatures are required in order to consider a file as being properly signed by
182/// that role.
183#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
184pub struct RoleKeys {
185    /// The key IDs used for the role.
186    pub keyids: Vec<Decoded<Hex>>,
187
188    /// The threshold of signatures required to validate the role.
189    pub threshold: NonZeroU64,
190
191    /// Extra arguments found during deserialization.
192    ///
193    /// We must store these to correctly verify signatures for this object.
194    ///
195    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
196    #[serde(flatten)]
197    pub _extra: HashMap<String, Value>,
198}
199
200impl Root {
201    /// An iterator over the keys for a given role.
202    pub fn keys(&self, role: RoleType) -> impl Iterator<Item = &Key> {
203        KeysIter {
204            keyids_iter: match self.roles.get(&role) {
205                Some(role_keys) => role_keys.keyids.iter(),
206                None => [].iter(),
207            },
208            keys: &self.keys,
209        }
210    }
211
212    /// Given an object/key that impls Sign, return the corresponding
213    /// key ID from Root
214    pub fn key_id(&self, key_pair: &dyn Sign) -> Option<Decoded<Hex>> {
215        for (key_id, key) in &self.keys {
216            if key_pair.tuf_key() == *key {
217                return Some(key_id.clone());
218            }
219        }
220        None
221    }
222}
223
224impl Role for Root {
225    const TYPE: RoleType = RoleType::Root;
226
227    fn expires(&self) -> jiff::Timestamp {
228        self.expires
229    }
230
231    fn version(&self) -> NonZeroU64 {
232        self.version
233    }
234
235    fn filename(&self, _consistent_snapshot: bool) -> String {
236        format!("{}.root.json", self.version())
237    }
238}
239
240// =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=
241
242/// TUF 4.4 The snapshot.json file is signed by the snapshot role. It MUST list the version numbers
243/// of the top-level targets metadata and all delegated targets metadata. It MAY also list their
244/// lengths and file hashes.
245#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
246#[serde(tag = "_type")]
247#[serde(rename = "snapshot")]
248pub struct Snapshot {
249    /// A string that contains the version number of the TUF specification. Its format follows the
250    /// Semantic Versioning 2.0.0 (semver) specification.
251    pub spec_version: String,
252
253    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
254    /// number less than the one currently trusted.
255    pub version: NonZeroU64,
256
257    /// Determines when metadata should be considered expired and no longer trusted by clients.
258    #[serde(serialize_with = "ser::serialize_timestamp")]
259    pub expires: jiff::Timestamp,
260
261    /// A list of what the TUF spec calls 'METAFILES' (`Metafiles` objects). The TUF spec
262    /// describes the hash key in 4.4: METAPATH is the file path of the metadata on the repository
263    /// relative to the metadata base URL. For snapshot.json, these are top-level targets metadata
264    /// and delegated targets metadata.
265    pub meta: HashMap<String, Metafile>,
266
267    /// Extra arguments found during deserialization.
268    ///
269    /// We must store these to correctly verify signatures for this object.
270    ///
271    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
272    #[serde(flatten)]
273    #[serde(deserialize_with = "de::extra_skip_type")]
274    pub _extra: HashMap<String, Value>,
275}
276
277/// Represents a metadata file in a `snapshot.json` and in a `timestamp.json` file.
278/// TUF 4.4: METAFILES is an object whose format is the following:
279/// ```text
280///  { METAPATH : {
281///        "version" : VERSION,
282///        ("length" : LENGTH, |
283///         "hashes" : HASHES) }
284///    , ...
285///  }
286/// ```
287/// e.g.
288/// ```json
289///    "project1.json": {
290///     "version": 1,
291///     "hashes": {
292///      "sha256": "f592d072e1193688a686267e8e10d7257b4ebfcf28133350dae88362d82a0c8a"
293///     }
294///    },
295/// ```
296#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
297pub struct Metafile {
298    /// LENGTH is the integer length in bytes of the metadata file at METAPATH. It is OPTIONAL and
299    /// can be omitted to reduce the snapshot metadata file size. In that case the client MUST use a
300    /// custom download limit for the listed metadata.
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub length: Option<u64>,
303
304    /// HASHES is a dictionary that specifies one or more hashes of the metadata file at METAPATH,
305    /// including their cryptographic hash function. For example: `{ "sha256": HASH, ... }`. HASHES
306    /// is OPTIONAL and can be omitted to reduce the snapshot metadata file size. In that case the
307    /// repository MUST guarantee that VERSION alone unambiguously identifies the metadata at
308    /// METAPATH.
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub hashes: Option<Hashes>,
311
312    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
313    /// number less than the one currently trusted.
314    pub version: NonZeroU64,
315
316    /// Extra arguments found during deserialization.
317    ///
318    /// We must store these to correctly verify signatures for this object.
319    ///
320    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
321    #[serde(flatten)]
322    pub _extra: HashMap<String, Value>,
323}
324
325/// Represents the hash dictionary in a `snapshot.json` file.
326#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
327pub struct Hashes {
328    /// The SHA 256 digest of a metadata file.
329    pub sha256: Decoded<Hex>,
330
331    /// Extra arguments found during deserialization.
332    ///
333    /// We must store these to correctly verify signatures for this object.
334    ///
335    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
336    #[serde(flatten)]
337    pub _extra: HashMap<String, Value>,
338}
339
340impl Snapshot {
341    /// Create a new `Snapshot` object.
342    pub fn new(spec_version: String, version: NonZeroU64, expires: jiff::Timestamp) -> Self {
343        Snapshot {
344            spec_version,
345            version,
346            expires,
347            meta: HashMap::new(),
348            _extra: HashMap::new(),
349        }
350    }
351}
352impl Role for Snapshot {
353    const TYPE: RoleType = RoleType::Snapshot;
354
355    fn expires(&self) -> jiff::Timestamp {
356        self.expires
357    }
358
359    fn version(&self) -> NonZeroU64 {
360        self.version
361    }
362
363    fn filename(&self, consistent_snapshot: bool) -> String {
364        if consistent_snapshot {
365            format!("{}.snapshot.json", self.version())
366        } else {
367            "snapshot.json".to_string()
368        }
369    }
370}
371
372// =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=
373
374/// Represents a `targets.json` file.
375/// TUF 4.5:
376/// The "signed" portion of targets.json is as follows:
377/// ```text
378/// { "_type" : "targets",
379///   "spec_version" : SPEC_VERSION,
380///   "version" : VERSION,
381///   "expires" : EXPIRES,
382///   "targets" : TARGETS,
383///   ("delegations" : DELEGATIONS)
384/// }
385/// ```
386///
387#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
388#[serde(tag = "_type")]
389#[serde(rename = "targets")]
390pub struct Targets {
391    /// A string that contains the version number of the TUF specification. Its format follows the
392    /// Semantic Versioning 2.0.0 (semver) specification.
393    pub spec_version: String,
394
395    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
396    /// number less than the one currently trusted.
397    pub version: NonZeroU64,
398
399    /// Determines when metadata should be considered expired and no longer trusted by clients.
400    #[serde(serialize_with = "ser::serialize_timestamp")]
401    pub expires: jiff::Timestamp,
402
403    /// Each key of the TARGETS object is a TARGETPATH. A TARGETPATH is a path to a file that is
404    /// relative to a mirror's base URL of targets.
405    pub targets: HashMap<TargetName, Target>,
406
407    /// Delegations describes subsets of the targets for which responsibility is delegated to
408    /// another role.
409    #[serde(skip_serializing_if = "Option::is_none")]
410    pub delegations: Option<Delegations>,
411
412    /// Extra arguments found during deserialization.
413    ///
414    /// We must store these to correctly verify signatures for this object.
415    ///
416    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
417    #[serde(flatten)]
418    #[serde(deserialize_with = "de::extra_skip_type")]
419    pub _extra: HashMap<String, Value>,
420}
421
422/// TUF 4.5: TARGETS is an object whose format is the following:
423/// ```text
424/// { TARGETPATH : {
425///       "length" : LENGTH,
426///       "hashes" : HASHES,
427///       ("custom" : { ... }) }
428///   , ...
429/// }
430/// ```
431#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
432pub struct Target {
433    /// LENGTH is the integer length in bytes of the target file at TARGETPATH.
434    pub length: u64,
435
436    /// HASHES is a dictionary that specifies one or more hashes, including the cryptographic hash
437    /// function. For example: `{ "sha256": HASH, ... }`. HASH is the hexdigest of the cryptographic
438    /// function computed on the target file.
439    pub hashes: Hashes,
440
441    /// If defined, the elements and values of "custom" will be made available to the client
442    /// application. The information in "custom" is opaque to the framework and can include version
443    /// numbers, dependencies, requirements, and any other data that the application wants to
444    /// include to describe the file at TARGETPATH. The application may use this information to
445    /// guide download decisions.
446    #[serde(default)]
447    #[serde(skip_serializing_if = "HashMap::is_empty")]
448    pub custom: HashMap<String, Value>,
449
450    /// Extra arguments found during deserialization.
451    ///
452    /// We must store these to correctly verify signatures for this object.
453    ///
454    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
455    #[serde(flatten)]
456    pub _extra: HashMap<String, Value>,
457}
458
459impl Target {
460    /// Given a path, returns a Target struct
461    pub async fn from_path<P>(path: P) -> Result<Target>
462    where
463        P: AsRef<Path>,
464    {
465        // Ensure the given path is a file
466        let path = path.as_ref();
467        if !path.is_file() {
468            return error::TargetNotAFileSnafu { path }.fail();
469        }
470
471        // Get the sha256 and length of the target
472        let mut file = File::open(path)
473            .await
474            .context(error::FileOpenSnafu { path })?;
475        let mut digest = Context::new(&SHA256);
476        let mut buf = [0; 8 * 1024];
477        let mut length = 0;
478        loop {
479            match file
480                .read(&mut buf)
481                .await
482                .context(error::FileReadSnafu { path })?
483            {
484                0 => break,
485                n => {
486                    digest.update(&buf[..n]);
487                    length += n as u64;
488                }
489            }
490        }
491
492        Ok(Target {
493            length,
494            hashes: Hashes {
495                sha256: Decoded::from(digest.finish().as_ref().to_vec()),
496                _extra: HashMap::new(),
497            },
498            custom: HashMap::new(),
499            _extra: HashMap::new(),
500        })
501    }
502}
503
504impl Targets {
505    /// Create a new `Targets` object.
506    pub fn new(spec_version: String, version: NonZeroU64, expires: jiff::Timestamp) -> Self {
507        Targets {
508            spec_version,
509            version,
510            expires,
511            targets: HashMap::new(),
512            _extra: HashMap::new(),
513            delegations: Some(Delegations::new()),
514        }
515    }
516
517    /// Given a target url, returns a reference to the Target struct or error if the target is
518    /// unreachable.
519    ///
520    /// **Caution**: does not imply that delegations in this struct or any child are valid.
521    ///
522    pub fn find_target(&self, target_name: &TargetName, permissive: bool) -> Result<&Target> {
523        // visited roles: specification 5.6.7.1 only visit a role once while searching
524        // This breaks any cyclic delegation, and speeds up searches with redundant
525        // delegation.
526        let mut visited: BTreeSet<String> = BTreeSet::new();
527        // terminated: set true when a terminating delegation is selected. No
528        // subsequent delegations should be consulted.
529        let mut terminated = false;
530        self.find_target_from_role(target_name, &mut visited, &mut terminated, permissive)
531    }
532
533    fn find_target_from_role(
534        &self,
535        target_name: &TargetName,
536        visited: &mut BTreeSet<String>,
537        terminated: &mut bool,
538        permissive: bool,
539    ) -> Result<&Target> {
540        if let Some(target) = self.targets.get(target_name) {
541            return Ok(target);
542        }
543        if let Some(delegations) = &self.delegations {
544            for role in &delegations.roles {
545                // If the target cannot match this DelegatedRole, then we do not want to recurse and
546                // check any of its child roles either. If we have already visited this role, we
547                // do not need to visit it again.
548                if !role.paths.matches_target_name(target_name) || visited.contains(&role.name) {
549                    continue;
550                }
551                visited.insert(role.name.clone());
552                if let Some(targets) = &role.targets {
553                    if let Ok(target) = targets.signed.find_target_from_role(
554                        target_name,
555                        visited,
556                        terminated,
557                        permissive,
558                    ) {
559                        return Ok(target);
560                    }
561                    if !permissive && *terminated {
562                        // we encountered a terminating delegation, so we stop iterating immediately
563                        break;
564                    }
565                }
566                if role.terminating && !permissive {
567                    // this role was terminating, so set terminated. This will cause all ancestors
568                    // to stop iterating and return not-found.
569                    *terminated = true;
570                    break;
571                }
572            }
573        }
574        error::TargetNotFoundSnafu {
575            name: target_name.clone(),
576        }
577        .fail()
578    }
579
580    /// Returns a hashmap of all targets and all delegated targets recursively
581    pub fn targets_map(&self) -> HashMap<TargetName, &Target> {
582        self.targets_iter()
583            .map(|(target_name, target)| (target_name.clone(), target))
584            .collect()
585    }
586
587    /// Returns an iterator of all targets and all delegated targets recursively
588    pub fn targets_iter(&self) -> impl Iterator<Item = (&TargetName, &Target)> + '_ {
589        let mut iter: Box<dyn Iterator<Item = (&TargetName, &Target)>> =
590            Box::new(self.targets.iter());
591        if let Some(delegations) = &self.delegations {
592            for role in &delegations.roles {
593                if let Some(targets) = &role.targets {
594                    iter = Box::new(iter.chain(targets.signed.targets_iter()));
595                }
596            }
597        }
598        iter
599    }
600
601    /// Recursively clears all targets
602    pub fn clear_targets(&mut self) {
603        self.targets = HashMap::new();
604        if let Some(delegations) = &mut self.delegations {
605            for delegated_role in &mut delegations.roles {
606                if let Some(targets) = &mut delegated_role.targets {
607                    targets.signed.clear_targets();
608                }
609            }
610        }
611    }
612
613    /// Add a target to targets
614    pub fn add_target(&mut self, name: TargetName, target: Target) {
615        self.targets.insert(name, target);
616    }
617
618    /// Remove a target from targets
619    pub fn remove_target(&mut self, name: &TargetName) -> Option<Target> {
620        self.targets.remove(name)
621    }
622
623    /// Returns the `&Signed<Targets>` for `name`
624    pub fn delegated_targets(&self, name: &str) -> Result<&Signed<Targets>> {
625        self.delegated_role(name)?
626            .targets
627            .as_ref()
628            .ok_or(error::Error::NoTargets)
629    }
630
631    /// Returns a mutable `Signed<Targets>` for `name`
632    pub fn delegated_targets_mut(&mut self, name: &str) -> Result<&mut Signed<Targets>> {
633        self.delegated_role_mut(name)?
634            .targets
635            .as_mut()
636            .ok_or(error::Error::NoTargets)
637    }
638
639    /// Returns the `&DelegatedRole` for `name`
640    pub fn delegated_role(&self, name: &str) -> Result<&DelegatedRole> {
641        for role in &self
642            .delegations
643            .as_ref()
644            .ok_or(error::Error::NoDelegations)?
645            .roles
646        {
647            if role.name == name {
648                return Ok(role);
649            } else if let Ok(role) = role
650                .targets
651                .as_ref()
652                .ok_or(error::Error::NoTargets)?
653                .signed
654                .delegated_role(name)
655            {
656                return Ok(role);
657            }
658        }
659        Err(error::Error::RoleNotFound {
660            name: name.to_string(),
661        })
662    }
663
664    /// Returns a mutable `DelegatedRole` for `name`
665    pub fn delegated_role_mut(&mut self, name: &str) -> Result<&mut DelegatedRole> {
666        for role in &mut self
667            .delegations
668            .as_mut()
669            .ok_or(error::Error::NoDelegations)?
670            .roles
671        {
672            if role.name == name {
673                return Ok(role);
674            } else if let Ok(role) = role
675                .targets
676                .as_mut()
677                .ok_or(error::Error::NoTargets)?
678                .signed
679                .delegated_role_mut(name)
680            {
681                return Ok(role);
682            }
683        }
684        Err(error::Error::RoleNotFound {
685            name: name.to_string(),
686        })
687    }
688
689    ///Returns a vec of all rolenames
690    pub fn role_names(&self) -> Vec<&String> {
691        let mut roles = Vec::new();
692        if let Some(delelegations) = &self.delegations {
693            for role in &delelegations.roles {
694                roles.push(&role.name);
695                if let Some(targets) = &role.targets {
696                    roles.append(&mut targets.signed.role_names());
697                }
698            }
699        }
700
701        roles
702    }
703
704    /// Returns a reference to the parent delegation of `name`
705    pub fn parent_of(&self, name: &str) -> Result<&Delegations> {
706        if let Some(delegations) = &self.delegations {
707            for role in &delegations.roles {
708                if role.name == name {
709                    return Ok(delegations);
710                }
711                if let Some(targets) = &role.targets {
712                    if let Ok(delegation) = targets.signed.parent_of(name) {
713                        return Ok(delegation);
714                    }
715                }
716            }
717        }
718        Err(error::Error::RoleNotFound {
719            name: name.to_string(),
720        })
721    }
722
723    /// Returns a vec of all targets roles delegated by this role
724    pub fn signed_delegated_targets(&self) -> Vec<Signed<DelegatedTargets>> {
725        let mut delegated_targets = Vec::new();
726        if let Some(delegations) = &self.delegations {
727            for role in &delegations.roles {
728                if let Some(targets) = &role.targets {
729                    delegated_targets.push(targets.clone().delegated_targets(&role.name));
730                    delegated_targets.extend(targets.signed.signed_delegated_targets());
731                }
732            }
733        }
734        delegated_targets
735    }
736
737    /// Link all current targets to `new_targets` metadata, returns a list of new `Targets` not included in the original `Targets`' delegated roles
738    /// This is used to insert a set of updated `Targets` metadata without reloading the rest of the chain.
739    pub fn update_targets(&self, new_targets: &mut Signed<Targets>) -> Vec<String> {
740        let mut needed_roles = Vec::new();
741        // Copy existing targets into proper places of new_targets
742        if let Some(delegations) = &mut new_targets.signed.delegations {
743            for role in &mut delegations.roles {
744                // Check to see if `role.name` has already been loaded
745                if let Ok(targets) = self.delegated_targets(&role.name) {
746                    // If it has been loaded, use it as the targets for the role
747                    role.targets = Some(targets.clone());
748                } else {
749                    // If not make sure we keep track that it needs to be loaded
750                    needed_roles.push(role.name.clone());
751                }
752            }
753        }
754
755        needed_roles
756    }
757
758    /// Calls `find_target` on each target (recursively provided by `targets_iter`). This
759    /// proves that the target is either owned by us, or correctly matches through some hierarchy of
760    /// [`PathSets`] below us. When called on the top level [`Targets`] of a repository, this proves
761    /// that the ownership of each target is valid.
762    pub(crate) fn validate(&self) -> Result<()> {
763        for (target_name, _) in self.targets_iter() {
764            self.find_target(target_name, true)?;
765        }
766        Ok(())
767    }
768}
769
770impl Role for Targets {
771    const TYPE: RoleType = RoleType::Targets;
772
773    fn expires(&self) -> jiff::Timestamp {
774        self.expires
775    }
776
777    fn version(&self) -> NonZeroU64 {
778        self.version
779    }
780
781    fn filename(&self, consistent_snapshot: bool) -> String {
782        if consistent_snapshot {
783            format!("{}.targets.json", self.version())
784        } else {
785            "targets.json".to_string()
786        }
787    }
788}
789
790/// Wrapper for `Targets` so that a `Targets` role can be given a name
791#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
792pub struct DelegatedTargets {
793    /// The name of the role
794    #[serde(skip)]
795    pub name: String,
796    /// The targets representing the role metadata
797    #[serde(flatten)]
798    pub targets: Targets,
799}
800
801impl Deref for DelegatedTargets {
802    type Target = Targets;
803
804    fn deref(&self) -> &Targets {
805        &self.targets
806    }
807}
808
809impl DerefMut for DelegatedTargets {
810    fn deref_mut(&mut self) -> &mut Targets {
811        &mut self.targets
812    }
813}
814
815impl Role for DelegatedTargets {
816    const TYPE: RoleType = RoleType::DelegatedTargets;
817
818    fn expires(&self) -> jiff::Timestamp {
819        self.targets.expires
820    }
821
822    fn version(&self) -> NonZeroU64 {
823        self.targets.version
824    }
825
826    fn filename(&self, consistent_snapshot: bool) -> String {
827        if consistent_snapshot {
828            format!("{}.{}.json", self.version(), encode_filename(&self.name))
829        } else {
830            format!("{}.json", encode_filename(&self.name))
831        }
832    }
833
834    fn role_id(&self) -> RoleId {
835        if self.name == "targets" {
836            RoleId::StandardRole(RoleType::Targets)
837        } else {
838            RoleId::DelegatedRole(self.name.clone())
839        }
840    }
841}
842
843impl Signed<DelegatedTargets> {
844    /// Convert a `Signed<DelegatedTargets>` to the string representing the role and its `Signed<Targets>`
845    pub fn targets(self) -> (String, Signed<Targets>) {
846        (
847            self.signed.name,
848            Signed {
849                signed: self.signed.targets,
850                signatures: self.signatures,
851            },
852        )
853    }
854}
855
856impl Signed<Targets> {
857    /// Use a string and a `Signed<Targets>` to create a `Signed<DelegatedTargets>`
858    pub fn delegated_targets(self, name: &str) -> Signed<DelegatedTargets> {
859        Signed {
860            signed: DelegatedTargets {
861                name: name.to_string(),
862                targets: self.signed,
863            },
864            signatures: self.signatures,
865        }
866    }
867}
868
869/// Delegations are found in a `targets.json` file.
870/// TUF 4.5: DELEGATIONS is an object whose format is the following:
871/// ```text
872/// { "keys" : {
873///       KEYID : KEY,
874///       ... },
875///   "roles" : [{
876///       "name": ROLENAME,
877///       "keyids" : [ KEYID, ... ] ,
878///       "threshold" : THRESHOLD,
879///       ("path_hash_prefixes" : [ HEX_DIGEST, ... ] |
880///        "paths" : [ PATHPATTERN, ... ]),
881///       "terminating": TERMINATING,
882///   }, ... ]
883/// }
884/// ```
885#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
886pub struct Delegations {
887    /// Lists the public keys to verify signatures of delegated targets roles. Revocation and
888    /// replacement of delegated targets roles keys is done by changing the keys in this field in
889    /// the delegating role's metadata.
890    #[serde(deserialize_with = "de::deserialize_keys")]
891    pub keys: HashMap<Decoded<Hex>, Key>,
892
893    /// The list of delegated roles.
894    pub roles: Vec<DelegatedRole>,
895}
896
897/// Each role delegated in a targets file is considered a delegated role
898#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
899pub struct DelegatedRole {
900    /// The name of the delegated role. For example, "projects".
901    pub name: String,
902
903    /// The key IDs used by this role.
904    pub keyids: Vec<Decoded<Hex>>,
905
906    /// The threshold of signatures required to validate the role.
907    pub threshold: NonZeroU64,
908
909    /// The paths governed by this role.
910    #[serde(flatten)]
911    pub paths: PathSet,
912
913    /// Indicates whether subsequent delegations should be considered.
914    pub terminating: bool,
915
916    /// The targets that are signed by this role.
917    #[serde(skip)]
918    pub targets: Option<Signed<Targets>>,
919}
920
921/// Specifies the target paths that a delegated role controls.
922#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
923pub enum PathSet {
924    /// The "paths" list describes paths that the role is trusted to provide. Clients MUST check
925    /// that a target is in one of the trusted paths of all roles in a delegation chain, not just in
926    /// a trusted path of the role that describes the target file. PATHPATTERN can include shell-
927    /// style wildcards and supports the Unix filename pattern matching convention. Its format may
928    /// either indicate a path to a single file, or to multiple paths with the use of shell-style
929    /// wildcards. For example, the path pattern "targets/*.tgz" would match file paths
930    /// "targets/foo.tgz" and "targets/bar.tgz", but not "targets/foo.txt". Likewise, path pattern
931    /// "foo-version-?.tgz" matches "foo-version-2.tgz" and "foo-version-a.tgz", but not
932    /// "foo-version-alpha.tgz". To avoid surprising behavior when matching targets with
933    /// PATHPATTERN, it is RECOMMENDED that PATHPATTERN uses the forward slash (/) as directory
934    /// separator and does not start with a directory separator, akin to TARGETSPATH.
935    #[serde(rename = "paths")]
936    Paths(Vec<PathPattern>),
937
938    /// The `path_hash_prefixes` list is used to succinctly describe a set of target paths.
939    /// Specifically, each `HEX_DIGEST` in `path_hash_prefixes` describes a set of target paths;
940    /// therefore, `path_hash_prefixes` is the union over each prefix of its set of target paths.
941    /// The target paths must meet this condition: each target path, when hashed with the SHA-256
942    /// hash function to produce a 64-byte hexadecimal digest (`HEX_DIGEST`), must share the same
943    /// prefix as one of the prefixes in `path_hash_prefixes`. This is useful to split a large
944    /// number of targets into separate bins identified by consistent hashing.
945    #[serde(rename = "path_hash_prefixes")]
946    PathHashPrefixes(Vec<PathHashPrefix>),
947}
948
949/// A glob-like path pattern for matching delegated targets, e.g. `foo/bar/*`.
950///
951/// `PATHPATTERN` supports the Unix shell pattern matching convention for paths
952/// ([glob](https://man7.org/linux/man-pages/man7/glob.7.html)bing pathnames). Its format may either
953/// indicate a path to a single file, or to multiple files with the use of shell-style wildcards
954/// (`*` or `?`). To avoid surprising behavior when matching targets with `PATHPATTERN` it is
955/// RECOMMENDED that `PATHPATTERN` uses the forward slash (`/`) as directory separator and does
956/// not start with a directory separator, as is also recommended for `TARGETPATH`. A path
957/// separator in a path SHOULD NOT be matched by a wildcard in the `PATHPATTERN`.
958///
959/// Some example `PATHPATTERN`s and expected matches:
960/// * a `PATHPATTERN` of `"targets/*.tgz"` would match file paths `"targets/foo.tgz"` and
961///   `"targets/bar.tgz"`, but not `"targets/foo.txt"`.
962/// * a `PATHPATTERN` of `"foo-version-?.tgz"` matches `"foo-version-2.tgz"` and
963///   `"foo-version-a.tgz"`, but not `"foo-version-alpha.tgz"`.
964/// * a `PATHPATTERN` of `"*.tgz"` would match `"foo.tgz"` and `"bar.tgz"`,
965///   but not `"targets/foo.tgz"`
966/// * a `PATHPATTERN` of `"foo.tgz"` would match only `"foo.tgz"`
967#[derive(Clone, Debug)]
968pub struct PathPattern {
969    value: String,
970    glob: GlobMatcher,
971}
972
973impl PathPattern {
974    /// Create a new, valid `PathPattern`. This will fail if we cannot parse the value as a glob. It is important that
975    /// our implementation stop if it encounters a glob it cannot parse so that we do not load repositories where we
976    /// cannot enforce delegate ownership.
977    pub fn new<S: Into<String>>(value: S) -> Result<Self> {
978        let value = value.into();
979        let glob = Glob::new(&value)
980            .context(error::GlobSnafu { pattern: &value })?
981            .compile_matcher();
982        Ok(Self { value, glob })
983    }
984
985    /// Get the inner value of this `PathPattern` as a string.
986    pub fn value(&self) -> &str {
987        &self.value
988    }
989
990    fn matches_target_name(&self, target_name: &TargetName) -> bool {
991        self.glob.is_match(target_name.resolved())
992    }
993}
994
995impl FromStr for PathPattern {
996    type Err = Error;
997
998    fn from_str(s: &str) -> Result<Self> {
999        PathPattern::new(s)
1000    }
1001}
1002
1003impl PartialEq for PathPattern {
1004    fn eq(&self, other: &Self) -> bool {
1005        PartialEq::eq(&self.value, &other.value)
1006    }
1007}
1008
1009impl Serialize for PathPattern {
1010    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1011    where
1012        S: Serializer,
1013    {
1014        serializer.serialize_str(self.value().as_ref())
1015    }
1016}
1017
1018impl<'de> Deserialize<'de> for PathPattern {
1019    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1020    where
1021        D: Deserializer<'de>,
1022    {
1023        let s = <String>::deserialize(deserializer)?;
1024        PathPattern::new(s).map_err(|e| D::Error::custom(format!("{e}")))
1025    }
1026}
1027
1028/// The first characters found in the string representation of a sha256 digest. This can be used for
1029/// randomly sharding a repository. See [`PathSet::PathHashDigest`] for the description of how this
1030/// is used.
1031#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
1032pub struct PathHashPrefix(String);
1033
1034impl PathHashPrefix {
1035    /// Create a new, valid `PathPattern`.
1036    pub fn new<S: Into<String>>(value: S) -> Result<Self> {
1037        // In case we choose to reject some of these in the future, we return a result. For now this
1038        // will always succeed.
1039        Ok(PathHashPrefix(value.into()))
1040    }
1041
1042    /// Get the inner value of this `PathPattern` as a string.
1043    pub fn value(&self) -> &str {
1044        &self.0
1045    }
1046
1047    fn matches_target_name(&self, target_name: &TargetName) -> bool {
1048        let target_name_digest =
1049            digest(&SHA256, target_name.resolved().as_bytes()).encode_hex::<String>();
1050        target_name_digest.starts_with(self.value())
1051    }
1052}
1053
1054impl FromStr for PathHashPrefix {
1055    type Err = Error;
1056
1057    fn from_str(s: &str) -> Result<Self> {
1058        PathHashPrefix::new(s)
1059    }
1060}
1061
1062impl PathSet {
1063    /// Given a `target_name`, returns whether or not this `PathSet` contains a pattern or hash
1064    /// prefix that matches.
1065    fn matches_target_name(&self, target_name: &TargetName) -> bool {
1066        match self {
1067            Self::Paths(paths) => {
1068                for path in paths {
1069                    if path.matches_target_name(target_name) {
1070                        return true;
1071                    }
1072                }
1073            }
1074
1075            Self::PathHashPrefixes(path_prefixes) => {
1076                for prefix in path_prefixes {
1077                    if prefix.matches_target_name(target_name) {
1078                        return true;
1079                    }
1080                }
1081            }
1082        }
1083        false
1084    }
1085}
1086
1087impl Delegations {
1088    /// Creates a new Delegations with no keys or roles
1089    pub fn new() -> Self {
1090        Delegations {
1091            keys: HashMap::new(),
1092            roles: Vec::new(),
1093        }
1094    }
1095
1096    /// Determines if target passes pathset specific matching
1097    pub fn target_is_delegated(&self, target: &TargetName) -> bool {
1098        for role in &self.roles {
1099            if role.paths.matches_target_name(target) {
1100                return true;
1101            }
1102        }
1103        false
1104    }
1105
1106    /// Given an object/key that impls Sign, return the corresponding
1107    /// key ID from Delegation
1108    pub fn key_id(&self, key_pair: &dyn Sign) -> Option<Decoded<Hex>> {
1109        for (key_id, key) in &self.keys {
1110            if key_pair.tuf_key() == *key {
1111                return Some(key_id.clone());
1112            }
1113        }
1114        None
1115    }
1116}
1117
1118impl DelegatedRole {
1119    /// Returns a `RoleKeys` representation of the role
1120    pub fn keys(&self) -> RoleKeys {
1121        RoleKeys {
1122            keyids: self.keyids.clone(),
1123            threshold: self.threshold,
1124            _extra: HashMap::new(),
1125        }
1126    }
1127}
1128
1129// =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=
1130
1131/// Represents a `timestamp.json` file.
1132/// TUF 4.6: The timestamp file is signed by a timestamp key. It indicates the latest version of the
1133/// snapshot metadata and is frequently resigned to limit the amount of time a client can be kept
1134/// unaware of interference with obtaining updates.
1135#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
1136#[serde(tag = "_type")]
1137#[serde(rename = "timestamp")]
1138pub struct Timestamp {
1139    /// A string that contains the version number of the TUF specification. Its format follows the
1140    /// Semantic Versioning 2.0.0 (semver) specification.
1141    pub spec_version: String,
1142
1143    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
1144    /// number less than the one currently trusted.
1145    pub version: NonZeroU64,
1146
1147    /// Determines when metadata should be considered expired and no longer trusted by clients.
1148    #[serde(serialize_with = "ser::serialize_timestamp")]
1149    pub expires: jiff::Timestamp,
1150
1151    /// METAFILES is the same as described for the snapshot.json file. In the case of the
1152    /// timestamp.json file, this MUST only include a description of the snapshot.json file.
1153    pub meta: HashMap<String, Metafile>,
1154
1155    /// Extra arguments found during deserialization.
1156    ///
1157    /// We must store these to correctly verify signatures for this object.
1158    ///
1159    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
1160    #[serde(flatten)]
1161    #[serde(deserialize_with = "de::extra_skip_type")]
1162    pub _extra: HashMap<String, Value>,
1163}
1164
1165impl Timestamp {
1166    /// Creates a new `Timestamp` object.
1167    pub fn new(spec_version: String, version: NonZeroU64, expires: jiff::Timestamp) -> Self {
1168        Timestamp {
1169            spec_version,
1170            version,
1171            expires,
1172            meta: HashMap::new(),
1173            _extra: HashMap::new(),
1174        }
1175    }
1176}
1177
1178impl Role for Timestamp {
1179    const TYPE: RoleType = RoleType::Timestamp;
1180
1181    fn expires(&self) -> jiff::Timestamp {
1182        self.expires
1183    }
1184
1185    fn version(&self) -> NonZeroU64 {
1186        self.version
1187    }
1188
1189    fn filename(&self, _consistent_snapshot: bool) -> String {
1190        "timestamp.json".to_string()
1191    }
1192}
1193
1194#[test]
1195fn targets_iter_and_map_test() {
1196    use maplit::hashmap;
1197
1198    // Create a dummy Target object.
1199    let nothing = Target {
1200        length: 0,
1201        hashes: Hashes {
1202            sha256: [0u8].to_vec().into(),
1203            _extra: HashMap::default(),
1204        },
1205        custom: HashMap::default(),
1206        _extra: HashMap::default(),
1207    };
1208
1209    // Create a hierarchy of targets/delegations: a -> b -> c
1210    let c_role = DelegatedRole {
1211        name: "c-role".to_string(),
1212        keyids: vec![],
1213        threshold: NonZeroU64::new(1).unwrap(),
1214        paths: PathSet::Paths(vec![PathPattern::new("*").unwrap()]),
1215        terminating: false,
1216        targets: Some(Signed {
1217            signed: Targets {
1218                spec_version: String::new(),
1219                version: NonZeroU64::new(1).unwrap(),
1220                expires: jiff::Timestamp::now(),
1221                targets: hashmap! {
1222                    TargetName::new("c.txt").unwrap() => nothing.clone(),
1223                },
1224                delegations: None,
1225                _extra: HashMap::default(),
1226            },
1227            signatures: vec![],
1228        }),
1229    };
1230    let b_delegations = Delegations {
1231        keys: HashMap::default(),
1232        roles: vec![c_role],
1233    };
1234    let b_role = DelegatedRole {
1235        name: "b-role".to_string(),
1236        keyids: vec![],
1237        threshold: NonZeroU64::new(1).unwrap(),
1238        paths: PathSet::Paths(vec![PathPattern::new("*").unwrap()]),
1239        terminating: false,
1240        targets: Some(Signed {
1241            signed: Targets {
1242                spec_version: String::new(),
1243                version: NonZeroU64::new(1).unwrap(),
1244                expires: jiff::Timestamp::now(),
1245                targets: hashmap! {
1246                    TargetName::new("b.txt").unwrap() => nothing.clone(),
1247                },
1248                delegations: Some(b_delegations),
1249                _extra: HashMap::default(),
1250            },
1251            signatures: vec![],
1252        }),
1253    };
1254    let a_delegations = Delegations {
1255        keys: HashMap::default(),
1256        roles: vec![b_role],
1257    };
1258    let a = Targets {
1259        spec_version: String::new(),
1260        version: NonZeroU64::new(1).unwrap(),
1261        expires: jiff::Timestamp::now(),
1262        targets: hashmap! {
1263            TargetName::new("a.txt").unwrap() => nothing,
1264        },
1265        delegations: Some(a_delegations),
1266        _extra: HashMap::default(),
1267    };
1268
1269    // Assert that targets_iter is recursive and thus has a.txt, b.txt and c.txt
1270    assert!(a
1271        .targets_iter()
1272        .map(|(key, _)| key)
1273        .any(|item| item.raw() == "a.txt"));
1274    assert!(a
1275        .targets_iter()
1276        .map(|(key, _)| key)
1277        .any(|item| item.raw() == "b.txt"));
1278    assert!(a
1279        .targets_iter()
1280        .map(|(key, _)| key)
1281        .any(|item| item.raw() == "c.txt"));
1282
1283    // Assert that targets_map is also recursive
1284    let map = a.targets_map();
1285    assert!(map.contains_key(&TargetName::new("a.txt").unwrap()));
1286    assert!(map.contains_key(&TargetName::new("b.txt").unwrap()));
1287    assert!(map.contains_key(&TargetName::new("c.txt").unwrap()));
1288}