tough 0.12.5

The Update Framework (TUF) repository client
Documentation
#![allow(clippy::used_underscore_binding)] // #20

//! Provides the schema objects as defined by the TUF spec.

mod de;
pub mod decoded;
mod error;
mod iter;
pub mod key;
mod spki;
mod verify;

use crate::schema::decoded::{Decoded, Hex};
pub use crate::schema::error::{Error, Result};
use crate::schema::iter::KeysIter;
use crate::schema::key::Key;
use crate::sign::Sign;
pub use crate::transport::{FilesystemTransport, Transport};
use crate::{encode_filename, TargetName};
use chrono::{DateTime, Utc};
use globset::{Glob, GlobMatcher};
use hex::ToHex;
use olpc_cjson::CanonicalFormatter;
use ring::digest::{digest, Context, SHA256};
use serde::de::Error as SerdeDeError;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use serde_plain::{derive_display_from_serialize, derive_fromstr_from_deserialize};
use snafu::ResultExt;
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::num::NonZeroU64;
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::str::FromStr;

/// The type of metadata role.
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum RoleType {
    /// The root role delegates trust to specific keys trusted for all other top-level roles used in
    /// the system.
    Root,
    /// The snapshot role signs a metadata file that provides information about the latest version
    /// of all targets metadata on the repository (the top-level targets role and all delegated
    /// roles).
    Snapshot,
    /// The targets role's signature indicates which target files are trusted by clients.
    Targets,
    /// The timestamp role is used to prevent an adversary from replaying an out-of-date signed
    /// metadata file whose signature has not yet expired.
    Timestamp,
    /// A delegated targets role
    DelegatedTargets,
}

derive_display_from_serialize!(RoleType);
derive_fromstr_from_deserialize!(RoleType);

/// A role identifier
#[derive(Debug, Clone)]
pub enum RoleId {
    /// Top level roles are identified by a RoleType
    StandardRole(RoleType),
    /// A delegated role is identified by a String
    DelegatedRole(String),
}

/// Common trait implemented by all roles.
pub trait Role: Serialize {
    /// The type of role this object represents.
    const TYPE: RoleType;

    /// Determines when metadata should be considered expired and no longer trusted by clients.
    fn expires(&self) -> DateTime<Utc>;

    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
    /// number less than the one currently trusted.
    fn version(&self) -> NonZeroU64;

    /// The filename that the role metadata should be written to
    fn filename(&self, consistent_snapshot: bool) -> String;

    /// The `RoleId` corresponding to the role
    fn role_id(&self) -> RoleId {
        RoleId::StandardRole(Self::TYPE)
    }

    /// A deterministic JSON serialization used when calculating the digest of a metadata object.
    /// [More info on canonical JSON](http://wiki.laptop.org/go/Canonical_JSON)
    fn canonical_form(&self) -> Result<Vec<u8>> {
        let mut data = Vec::new();
        let mut ser = serde_json::Serializer::with_formatter(&mut data, CanonicalFormatter::new());
        self.serialize(&mut ser)
            .context(error::JsonSerializationSnafu { what: "role" })?;
        Ok(data)
    }
}

/// A signed metadata object.
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
pub struct Signed<T> {
    /// The role that is signed.
    pub signed: T,
    /// A list of signatures and their key IDs.
    pub signatures: Vec<Signature>,
}

/// A signature and the key ID that made it.
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
pub struct Signature {
    /// The key ID (listed in root.json) that made this signature.
    pub keyid: Decoded<Hex>,
    /// A hex-encoded signature of the canonical JSON form of a role.
    pub sig: Decoded<Hex>,
}

/// A `KeyHolder` is metadata that is responsible for verifying the signatures of a role.
/// `KeyHolder` contains either a `Delegations` of a `Targets` or a `Root`
#[derive(Debug, Clone)]
pub enum KeyHolder {
    /// Delegations verify delegated targets
    Delegations(Delegations),
    /// Root verifies the top level targets, snapshot, timestamp, and root
    Root(Root),
}

// =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=

/// TUF 4.3: The root.json file is signed by the root role's keys. It indicates which keys are
/// authorized for all top-level roles, including the root role itself. Revocation and replacement
/// of top-level role keys, including for the root role, is done by changing the keys listed for the
/// roles in this file.
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
#[serde(tag = "_type")]
#[serde(rename = "root")]
pub struct Root {
    /// A string that contains the version number of the TUF specification. Its format follows the
    /// Semantic Versioning 2.0.0 (semver) specification.
    pub spec_version: String,

    /// A boolean indicating whether the repository supports consistent snapshots. When consistent
    /// snapshots is `true`, targets and certain metadata filenames are prefixed with either a
    /// a version number or digest.
    pub consistent_snapshot: bool,

    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
    /// number less than the one currently trusted.
    pub version: NonZeroU64,

    /// Determines when metadata should be considered expired and no longer trusted by clients.
    pub expires: DateTime<Utc>,

    /// The KEYID must be correct for the specified KEY. Clients MUST calculate each KEYID to verify
    /// this is correct for the associated key. Clients MUST ensure that for any KEYID represented
    /// in this key list and in other files, only one unique key has that KEYID.
    #[serde(deserialize_with = "de::deserialize_keys")]
    pub keys: HashMap<Decoded<Hex>, Key>,

    /// A list of roles, the keys associated with each role, and the threshold of signatures used
    /// for each role.
    pub roles: HashMap<RoleType, RoleKeys>,

    /// Extra arguments found during deserialization.
    ///
    /// We must store these to correctly verify signatures for this object.
    ///
    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
    #[serde(flatten)]
    #[serde(deserialize_with = "de::extra_skip_type")]
    pub _extra: HashMap<String, Value>,
}

/// Represents the key IDs used for a role and the threshold of signatures required to validate it.
/// TUF 4.3: A ROLE is one of "root", "snapshot", "targets", "timestamp", or "mirrors". A role for
/// each of "root", "snapshot", "timestamp", and "targets" MUST be specified in the key list.
/// The role of "mirror" is optional. If not specified, the mirror list will not need to be signed
/// if mirror lists are being used. The THRESHOLD for a role is an integer of the number of keys of
/// that role whose signatures are required in order to consider a file as being properly signed by
/// that role.
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
pub struct RoleKeys {
    /// The key IDs used for the role.
    pub keyids: Vec<Decoded<Hex>>,

    /// The threshold of signatures required to validate the role.
    pub threshold: NonZeroU64,

    /// Extra arguments found during deserialization.
    ///
    /// We must store these to correctly verify signatures for this object.
    ///
    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
    #[serde(flatten)]
    pub _extra: HashMap<String, Value>,
}

impl Root {
    /// An iterator over the keys for a given role.
    pub fn keys(&self, role: RoleType) -> impl Iterator<Item = &Key> {
        KeysIter {
            keyids_iter: match self.roles.get(&role) {
                Some(role_keys) => role_keys.keyids.iter(),
                None => [].iter(),
            },
            keys: &self.keys,
        }
    }

    /// Given an object/key that impls Sign, return the corresponding
    /// key ID from Root
    pub fn key_id(&self, key_pair: &dyn Sign) -> Option<Decoded<Hex>> {
        for (key_id, key) in &self.keys {
            if key_pair.tuf_key() == *key {
                return Some(key_id.clone());
            }
        }
        None
    }
}

impl Role for Root {
    const TYPE: RoleType = RoleType::Root;

    fn expires(&self) -> DateTime<Utc> {
        self.expires
    }

    fn version(&self) -> NonZeroU64 {
        self.version
    }

    fn filename(&self, _consistent_snapshot: bool) -> String {
        format!("{}.root.json", self.version())
    }
}

// =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=

/// TUF 4.4 The snapshot.json file is signed by the snapshot role. It MUST list the version numbers
/// of the top-level targets metadata and all delegated targets metadata. It MAY also list their
/// lengths and file hashes.
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
#[serde(tag = "_type")]
#[serde(rename = "snapshot")]
pub struct Snapshot {
    /// A string that contains the version number of the TUF specification. Its format follows the
    /// Semantic Versioning 2.0.0 (semver) specification.
    pub spec_version: String,

    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
    /// number less than the one currently trusted.
    pub version: NonZeroU64,

    /// Determines when metadata should be considered expired and no longer trusted by clients.
    pub expires: DateTime<Utc>,

    /// A list of what the TUF spec calls 'METAFILES' (`SnapshotMeta` objects). The TUF spec
    /// describes the hash key in 4.4: METAPATH is the file path of the metadata on the repository
    /// relative to the metadata base URL. For snapshot.json, these are top-level targets metadata
    /// and delegated targets metadata.
    pub meta: HashMap<String, SnapshotMeta>,

    /// Extra arguments found during deserialization.
    ///
    /// We must store these to correctly verify signatures for this object.
    ///
    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
    #[serde(flatten)]
    #[serde(deserialize_with = "de::extra_skip_type")]
    pub _extra: HashMap<String, Value>,
}

/// Represents a metadata file in a `snapshot.json` file.
/// TUF 4.4: METAFILES is an object whose format is the following:
/// ```text
///  { METAPATH : {
///        "version" : VERSION,
///        ("length" : LENGTH, |
///         "hashes" : HASHES) }
///    , ...
///  }
/// ```
/// e.g.
/// ```json
///    "project1.json": {
///     "version": 1,
///     "hashes": {
///      "sha256": "f592d072e1193688a686267e8e10d7257b4ebfcf28133350dae88362d82a0c8a"
///     }
///    },
/// ```
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
pub struct SnapshotMeta {
    /// LENGTH is the integer length in bytes of the metadata file at METAPATH. It is OPTIONAL and
    /// can be omitted to reduce the snapshot metadata file size. In that case the client MUST use a
    /// custom download limit for the listed metadata.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub length: Option<u64>,

    /// HASHES is a dictionary that specifies one or more hashes of the metadata file at METAPATH,
    /// including their cryptographic hash function. For example: `{ "sha256": HASH, ... }`. HASHES
    /// is OPTIONAL and can be omitted to reduce the snapshot metadata file size. In that case the
    /// repository MUST guarantee that VERSION alone unambiguously identifies the metadata at
    /// METAPATH.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hashes: Option<Hashes>,

    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
    /// number less than the one currently trusted.
    pub version: NonZeroU64,

    /// Extra arguments found during deserialization.
    ///
    /// We must store these to correctly verify signatures for this object.
    ///
    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
    #[serde(flatten)]
    pub _extra: HashMap<String, Value>,
}

/// Represents the hash dictionary in a `snapshot.json` file.
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
pub struct Hashes {
    /// The SHA 256 digest of a metadata file.
    pub sha256: Decoded<Hex>,

    /// Extra arguments found during deserialization.
    ///
    /// We must store these to correctly verify signatures for this object.
    ///
    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
    #[serde(flatten)]
    pub _extra: HashMap<String, Value>,
}

impl Snapshot {
    /// Create a new `Snapshot` object.
    pub fn new(spec_version: String, version: NonZeroU64, expires: DateTime<Utc>) -> Self {
        Snapshot {
            spec_version,
            version,
            expires,
            meta: HashMap::new(),
            _extra: HashMap::new(),
        }
    }
}
impl Role for Snapshot {
    const TYPE: RoleType = RoleType::Snapshot;

    fn expires(&self) -> DateTime<Utc> {
        self.expires
    }

    fn version(&self) -> NonZeroU64 {
        self.version
    }

    fn filename(&self, consistent_snapshot: bool) -> String {
        if consistent_snapshot {
            format!("{}.snapshot.json", self.version())
        } else {
            "snapshot.json".to_string()
        }
    }
}

// =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=

/// Represents a `targets.json` file.
/// TUF 4.5:
/// The "signed" portion of targets.json is as follows:
/// ```text
/// { "_type" : "targets",
///   "spec_version" : SPEC_VERSION,
///   "version" : VERSION,
///   "expires" : EXPIRES,
///   "targets" : TARGETS,
///   ("delegations" : DELEGATIONS)
/// }
/// ```
///
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(tag = "_type")]
#[serde(rename = "targets")]
pub struct Targets {
    /// A string that contains the version number of the TUF specification. Its format follows the
    /// Semantic Versioning 2.0.0 (semver) specification.
    pub spec_version: String,

    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
    /// number less than the one currently trusted.
    pub version: NonZeroU64,

    /// Determines when metadata should be considered expired and no longer trusted by clients.
    pub expires: DateTime<Utc>,

    /// Each key of the TARGETS object is a TARGETPATH. A TARGETPATH is a path to a file that is
    /// relative to a mirror's base URL of targets.
    pub targets: HashMap<TargetName, Target>,

    /// Delegations describes subsets of the targets for which responsibility is delegated to
    /// another role.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub delegations: Option<Delegations>,

    /// Extra arguments found during deserialization.
    ///
    /// We must store these to correctly verify signatures for this object.
    ///
    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
    #[serde(flatten)]
    #[serde(deserialize_with = "de::extra_skip_type")]
    pub _extra: HashMap<String, Value>,
}

/// TUF 4.5: TARGETS is an object whose format is the following:
/// ```text
/// { TARGETPATH : {
///       "length" : LENGTH,
///       "hashes" : HASHES,
///       ("custom" : { ... }) }
///   , ...
/// }
/// ```
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
pub struct Target {
    /// LENGTH is the integer length in bytes of the target file at TARGETPATH.
    pub length: u64,

    /// HASHES is a dictionary that specifies one or more hashes, including the cryptographic hash
    /// function. For example: `{ "sha256": HASH, ... }`. HASH is the hexdigest of the cryptographic
    /// function computed on the target file.
    pub hashes: Hashes,

    /// If defined, the elements and values of "custom" will be made available to the client
    /// application. The information in "custom" is opaque to the framework and can include version
    /// numbers, dependencies, requirements, and any other data that the application wants to
    /// include to describe the file at TARGETPATH. The application may use this information to
    /// guide download decisions.
    #[serde(default)]
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    pub custom: HashMap<String, Value>,

    /// Extra arguments found during deserialization.
    ///
    /// We must store these to correctly verify signatures for this object.
    ///
    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
    #[serde(flatten)]
    pub _extra: HashMap<String, Value>,
}

impl Target {
    /// Given a path, returns a Target struct
    pub fn from_path<P>(path: P) -> Result<Target>
    where
        P: AsRef<Path>,
    {
        // Ensure the given path is a file
        let path = path.as_ref();
        if !path.is_file() {
            return error::TargetNotAFileSnafu { path }.fail();
        }

        // Get the sha256 and length of the target
        let mut file = File::open(path).context(error::FileOpenSnafu { path })?;
        let mut digest = Context::new(&SHA256);
        let mut buf = [0; 8 * 1024];
        let mut length = 0;
        loop {
            match file.read(&mut buf).context(error::FileReadSnafu { path })? {
                0 => break,
                n => {
                    digest.update(&buf[..n]);
                    length += n as u64;
                }
            }
        }

        Ok(Target {
            length,
            hashes: Hashes {
                sha256: Decoded::from(digest.finish().as_ref().to_vec()),
                _extra: HashMap::new(),
            },
            custom: HashMap::new(),
            _extra: HashMap::new(),
        })
    }
}

impl Targets {
    /// Create a new `Targets` object.
    pub fn new(spec_version: String, version: NonZeroU64, expires: DateTime<Utc>) -> Self {
        Targets {
            spec_version,
            version,
            expires,
            targets: HashMap::new(),
            _extra: HashMap::new(),
            delegations: Some(Delegations::new()),
        }
    }

    /// Given a target url, returns a reference to the Target struct or error if the target is
    /// unreachable.
    ///
    /// **Caution**: does not imply that delegations in this struct or any child are valid.
    ///
    pub fn find_target(&self, target_name: &TargetName) -> Result<&Target> {
        if let Some(target) = self.targets.get(target_name) {
            return Ok(target);
        }
        if let Some(delegations) = &self.delegations {
            for role in &delegations.roles {
                // If the target cannot match this DelegatedRole, then we do not want to recurse and
                // check any of its child roles either.
                if !role.paths.matches_target_name(target_name) {
                    continue;
                }
                if let Some(targets) = &role.targets {
                    if let Ok(target) = targets.signed.find_target(target_name) {
                        return Ok(target);
                    }
                }
            }
        }
        error::TargetNotFoundSnafu {
            name: target_name.clone(),
        }
        .fail()
    }

    /// Returns a hashmap of all targets and all delegated targets recursively
    pub fn targets_map(&self) -> HashMap<TargetName, &Target> {
        self.targets_iter()
            .map(|(target_name, target)| (target_name.clone(), target))
            .collect()
    }

    /// Returns an iterator of all targets and all delegated targets recursively
    pub fn targets_iter(&self) -> impl Iterator<Item = (&TargetName, &Target)> + '_ {
        let mut iter: Box<dyn Iterator<Item = (&TargetName, &Target)>> =
            Box::new(self.targets.iter());
        if let Some(delegations) = &self.delegations {
            for role in &delegations.roles {
                if let Some(targets) = &role.targets {
                    iter = Box::new(iter.chain(targets.signed.targets_iter()));
                }
            }
        }
        iter
    }

    /// Recursively clears all targets
    pub fn clear_targets(&mut self) {
        self.targets = HashMap::new();
        if let Some(delegations) = &mut self.delegations {
            for delegated_role in &mut delegations.roles {
                if let Some(targets) = &mut delegated_role.targets {
                    targets.signed.clear_targets();
                }
            }
        }
    }

    /// Add a target to targets
    pub fn add_target(&mut self, name: TargetName, target: Target) {
        self.targets.insert(name, target);
    }

    /// Remove a target from targets
    pub fn remove_target(&mut self, name: &TargetName) -> Option<Target> {
        self.targets.remove(name)
    }

    /// Returns the `&Signed<Targets>` for `name`
    pub fn delegated_targets(&self, name: &str) -> Result<&Signed<Targets>> {
        self.delegated_role(name)?
            .targets
            .as_ref()
            .ok_or(error::Error::NoTargets)
    }

    /// Returns a mutable `Signed<Targets>` for `name`
    pub fn delegated_targets_mut(&mut self, name: &str) -> Result<&mut Signed<Targets>> {
        self.delegated_role_mut(name)?
            .targets
            .as_mut()
            .ok_or(error::Error::NoTargets)
    }

    /// Returns the `&DelegatedRole` for `name`
    pub fn delegated_role(&self, name: &str) -> Result<&DelegatedRole> {
        for role in &self
            .delegations
            .as_ref()
            .ok_or(error::Error::NoDelegations)?
            .roles
        {
            if role.name == name {
                return Ok(role);
            } else if let Ok(role) = role
                .targets
                .as_ref()
                .ok_or(error::Error::NoTargets)?
                .signed
                .delegated_role(name)
            {
                return Ok(role);
            }
        }
        Err(error::Error::RoleNotFound {
            name: name.to_string(),
        })
    }

    /// Returns a mutable `DelegatedRole` for `name`
    pub fn delegated_role_mut(&mut self, name: &str) -> Result<&mut DelegatedRole> {
        for role in &mut self
            .delegations
            .as_mut()
            .ok_or(error::Error::NoDelegations)?
            .roles
        {
            if role.name == name {
                return Ok(role);
            } else if let Ok(role) = role
                .targets
                .as_mut()
                .ok_or(error::Error::NoTargets)?
                .signed
                .delegated_role_mut(name)
            {
                return Ok(role);
            }
        }
        Err(error::Error::RoleNotFound {
            name: name.to_string(),
        })
    }

    ///Returns a vec of all rolenames
    pub fn role_names(&self) -> Vec<&String> {
        let mut roles = Vec::new();
        if let Some(delelegations) = &self.delegations {
            for role in &delelegations.roles {
                roles.push(&role.name);
                if let Some(targets) = &role.targets {
                    roles.append(&mut targets.signed.role_names());
                }
            }
        }

        roles
    }

    /// Returns a reference to the parent delegation of `name`
    pub fn parent_of(&self, name: &str) -> Result<&Delegations> {
        if let Some(delegations) = &self.delegations {
            for role in &delegations.roles {
                if role.name == name {
                    return Ok(delegations);
                }
                if let Some(targets) = &role.targets {
                    if let Ok(delegation) = targets.signed.parent_of(name) {
                        return Ok(delegation);
                    }
                }
            }
        }
        Err(error::Error::RoleNotFound {
            name: name.to_string(),
        })
    }

    /// Returns a vec of all targets roles delegated by this role
    pub fn signed_delegated_targets(&self) -> Vec<Signed<DelegatedTargets>> {
        let mut delegated_targets = Vec::new();
        if let Some(delegations) = &self.delegations {
            for role in &delegations.roles {
                if let Some(targets) = &role.targets {
                    delegated_targets.push(targets.clone().delegated_targets(&role.name));
                    delegated_targets.extend(targets.signed.signed_delegated_targets());
                }
            }
        }
        delegated_targets
    }

    /// Link all current targets to `new_targets` metadata, returns a list of new `Targets` not included in the original `Targets`' delegated roles
    /// This is used to insert a set of updated `Targets` metadata without reloading the rest of the chain.
    pub fn update_targets(&self, new_targets: &mut Signed<Targets>) -> Vec<String> {
        let mut needed_roles = Vec::new();
        // Copy existing targets into proper places of new_targets
        if let Some(delegations) = &mut new_targets.signed.delegations {
            for mut role in &mut delegations.roles {
                // Check to see if `role.name` has already been loaded
                if let Ok(targets) = self.delegated_targets(&role.name) {
                    // If it has been loaded, use it as the targets for the role
                    role.targets = Some(targets.clone());
                } else {
                    // If not make sure we keep track that it needs to be loaded
                    needed_roles.push(role.name.clone());
                }
            }
        }

        needed_roles
    }

    /// Calls `find_target` on each target (recursively provided by `targets_iter`). This
    /// proves that the target is either owned by us, or correctly matches through some hierarchy of
    /// [`PathSets`] below us. When called on the top level [`Targets`] of a repository, this proves
    /// that the ownership of each target is valid.
    pub(crate) fn validate(&self) -> Result<()> {
        for (target_name, _) in self.targets_iter() {
            self.find_target(target_name)?;
        }
        Ok(())
    }
}

impl Role for Targets {
    const TYPE: RoleType = RoleType::Targets;

    fn expires(&self) -> DateTime<Utc> {
        self.expires
    }

    fn version(&self) -> NonZeroU64 {
        self.version
    }

    fn filename(&self, consistent_snapshot: bool) -> String {
        if consistent_snapshot {
            format!("{}.targets.json", self.version())
        } else {
            "targets.json".to_string()
        }
    }
}

/// Wrapper for `Targets` so that a `Targets` role can be given a name
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct DelegatedTargets {
    /// The name of the role
    #[serde(skip)]
    pub name: String,
    /// The targets representing the role metadata
    #[serde(flatten)]
    pub targets: Targets,
}

impl Deref for DelegatedTargets {
    type Target = Targets;

    fn deref(&self) -> &Targets {
        &self.targets
    }
}

impl DerefMut for DelegatedTargets {
    fn deref_mut(&mut self) -> &mut Targets {
        &mut self.targets
    }
}

impl Role for DelegatedTargets {
    const TYPE: RoleType = RoleType::DelegatedTargets;

    fn expires(&self) -> DateTime<Utc> {
        self.targets.expires
    }

    fn version(&self) -> NonZeroU64 {
        self.targets.version
    }

    fn filename(&self, consistent_snapshot: bool) -> String {
        if consistent_snapshot {
            format!("{}.{}.json", self.version(), encode_filename(&self.name))
        } else {
            format!("{}.json", encode_filename(&self.name))
        }
    }

    fn role_id(&self) -> RoleId {
        if self.name == "targets" {
            RoleId::StandardRole(RoleType::Targets)
        } else {
            RoleId::DelegatedRole(self.name.clone())
        }
    }
}

impl Signed<DelegatedTargets> {
    /// Convert a `Signed<DelegatedTargets>` to the string representing the role and its `Signed<Targets>`
    pub fn targets(self) -> (String, Signed<Targets>) {
        (
            self.signed.name,
            Signed {
                signed: self.signed.targets,
                signatures: self.signatures,
            },
        )
    }
}

impl Signed<Targets> {
    /// Use a string and a `Signed<Targets>` to create a `Signed<DelegatedTargets>`
    pub fn delegated_targets(self, name: &str) -> Signed<DelegatedTargets> {
        Signed {
            signed: DelegatedTargets {
                name: name.to_string(),
                targets: self.signed,
            },
            signatures: self.signatures,
        }
    }
}

/// Delegations are found in a `targets.json` file.
/// TUF 4.5: DELEGATIONS is an object whose format is the following:
/// ```text
/// { "keys" : {
///       KEYID : KEY,
///       ... },
///   "roles" : [{
///       "name": ROLENAME,
///       "keyids" : [ KEYID, ... ] ,
///       "threshold" : THRESHOLD,
///       ("path_hash_prefixes" : [ HEX_DIGEST, ... ] |
///        "paths" : [ PATHPATTERN, ... ]),
///       "terminating": TERMINATING,
///   }, ... ]
/// }
/// ```
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
pub struct Delegations {
    /// Lists the public keys to verify signatures of delegated targets roles. Revocation and
    /// replacement of delegated targets roles keys is done by changing the keys in this field in
    /// the delegating role's metadata.
    #[serde(deserialize_with = "de::deserialize_keys")]
    pub keys: HashMap<Decoded<Hex>, Key>,

    /// The list of delegated roles.
    pub roles: Vec<DelegatedRole>,
}

/// Each role delegated in a targets file is considered a delegated role
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub struct DelegatedRole {
    /// The name of the delegated role. For example, "projects".
    pub name: String,

    /// The key IDs used by this role.
    pub keyids: Vec<Decoded<Hex>>,

    /// The threshold of signatures required to validate the role.
    pub threshold: NonZeroU64,

    /// The paths governed by this role.
    #[serde(flatten)]
    pub paths: PathSet,

    /// Indicates whether subsequent delegations should be considered.
    pub terminating: bool,

    /// The targets that are signed by this role.
    #[serde(skip)]
    pub targets: Option<Signed<Targets>>,
}

/// Specifies the target paths that a delegated role controls.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
pub enum PathSet {
    /// The "paths" list describes paths that the role is trusted to provide. Clients MUST check
    /// that a target is in one of the trusted paths of all roles in a delegation chain, not just in
    /// a trusted path of the role that describes the target file. PATHPATTERN can include shell-
    /// style wildcards and supports the Unix filename pattern matching convention. Its format may
    /// either indicate a path to a single file, or to multiple paths with the use of shell-style
    /// wildcards. For example, the path pattern "targets/*.tgz" would match file paths
    /// "targets/foo.tgz" and "targets/bar.tgz", but not "targets/foo.txt". Likewise, path pattern
    /// "foo-version-?.tgz" matches "foo-version-2.tgz" and "foo-version-a.tgz", but not
    /// "foo-version-alpha.tgz". To avoid surprising behavior when matching targets with
    /// PATHPATTERN, it is RECOMMENDED that PATHPATTERN uses the forward slash (/) as directory
    /// separator and does not start with a directory separator, akin to TARGETSPATH.
    #[serde(rename = "paths")]
    Paths(Vec<PathPattern>),

    /// The "path_hash_prefixes" list is used to succinctly describe a set of target paths.
    /// Specifically, each HEX_DIGEST in "path_hash_prefixes" describes a set of target paths;
    /// therefore, "path_hash_prefixes" is the union over each prefix of its set of target paths.
    /// The target paths must meet this condition: each target path, when hashed with the SHA-256
    /// hash function to produce a 64-byte hexadecimal digest (HEX_DIGEST), must share the same
    /// prefix as one of the prefixes in "path_hash_prefixes". This is useful to split a large
    /// number of targets into separate bins identified by consistent hashing.
    #[serde(rename = "path_hash_prefixes")]
    PathHashPrefixes(Vec<PathHashPrefix>),
}

/// A glob-like path pattern for matching delegated targets, e.g. `foo/bar/*`.
///
/// `PATHPATTERN` supports the Unix shell pattern matching convention for paths
/// ([glob](https://man7.org/linux/man-pages/man7/glob.7.html)bing pathnames). Its format may either
/// indicate a path to a single file, or to multiple files with the use of shell-style wildcards
/// (`*` or `?`). To avoid surprising behavior when matching targets with `PATHPATTERN` it is
/// RECOMMENDED that `PATHPATTERN` uses the forward slash (`/`) as directory separator and does
/// not start with a directory separator, as is also recommended for `TARGETPATH`. A path
/// separator in a path SHOULD NOT be matched by a wildcard in the `PATHPATTERN`.
///
/// Some example `PATHPATTERN`s and expected matches:
/// * a `PATHPATTERN` of `"targets/*.tgz"` would match file paths `"targets/foo.tgz"` and
///   `"targets/bar.tgz"`, but not `"targets/foo.txt"`.
/// * a `PATHPATTERN` of `"foo-version-?.tgz"` matches `"foo-version-2.tgz"` and
///     `"foo-version-a.tgz"`, but not `"foo-version-alpha.tgz"`.
/// * a `PATHPATTERN` of `"*.tgz"` would match `"foo.tgz"` and `"bar.tgz"`,
///   but not `"targets/foo.tgz"`
/// * a `PATHPATTERN` of `"foo.tgz"` would match only `"foo.tgz"`
#[derive(Clone, Debug)]
pub struct PathPattern {
    value: String,
    glob: GlobMatcher,
}

impl PathPattern {
    /// Create a new, valid `PathPattern`. This will fail if we cannot parse the value as a glob. It is important that
    /// our implementation stop if it encounters a glob it cannot parse so that we do not load repositories where we
    /// cannot enforce delegate ownership.
    pub fn new<S: Into<String>>(value: S) -> Result<Self> {
        let value = value.into();
        let glob = Glob::new(&value)
            .context(error::GlobSnafu { pattern: &value })?
            .compile_matcher();
        Ok(Self { value, glob })
    }

    /// Get the inner value of this `PathPattern` as a string.
    pub fn value(&self) -> &str {
        &self.value
    }

    fn matches_target_name(&self, target_name: &TargetName) -> bool {
        self.glob.is_match(target_name.resolved())
    }
}

impl FromStr for PathPattern {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        PathPattern::new(s)
    }
}

impl PartialEq for PathPattern {
    fn eq(&self, other: &Self) -> bool {
        PartialEq::eq(&self.value, &other.value)
    }
}

impl Serialize for PathPattern {
    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serializer.serialize_str(self.value().as_ref())
    }
}

impl<'de> Deserialize<'de> for PathPattern {
    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = <String>::deserialize(deserializer)?;
        PathPattern::new(s).map_err(|e| D::Error::custom(format!("{}", e)))
    }
}

/// The first characters found in the string representation of a sha256 digest. This can be used for
/// randomly sharding a repository. See [`PathSet::PathHashDigest`] for the description of how this
/// is used.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
pub struct PathHashPrefix(String);

impl PathHashPrefix {
    /// Create a new, valid `PathPattern`.
    pub fn new<S: Into<String>>(value: S) -> Result<Self> {
        // In case we choose to reject some of these in the future, we return a result. For now this
        // will always succeed.
        Ok(PathHashPrefix(value.into()))
    }

    /// Get the inner value of this `PathPattern` as a string.
    pub fn value(&self) -> &str {
        &self.0
    }

    fn matches_target_name(&self, target_name: &TargetName) -> bool {
        let target_name_digest =
            digest(&SHA256, target_name.resolved().as_bytes()).encode_hex::<String>();
        target_name_digest.starts_with(self.value())
    }
}

impl FromStr for PathHashPrefix {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        PathHashPrefix::new(s)
    }
}

impl PathSet {
    /// Given a `target_name`, returns whether or not this `PathSet` contains a pattern or hash
    /// prefix that matches.
    fn matches_target_name(&self, target_name: &TargetName) -> bool {
        match self {
            Self::Paths(paths) => {
                for path in paths {
                    if path.matches_target_name(target_name) {
                        return true;
                    }
                }
            }

            Self::PathHashPrefixes(path_prefixes) => {
                for prefix in path_prefixes {
                    if prefix.matches_target_name(target_name) {
                        return true;
                    }
                }
            }
        }
        false
    }
}

impl Delegations {
    /// Creates a new Delegations with no keys or roles
    pub fn new() -> Self {
        Delegations {
            keys: HashMap::new(),
            roles: Vec::new(),
        }
    }

    /// Determines if target passes pathset specific matching
    pub fn target_is_delegated(&self, target: &TargetName) -> bool {
        for role in &self.roles {
            if role.paths.matches_target_name(target) {
                return true;
            }
        }
        false
    }

    /// Given an object/key that impls Sign, return the corresponding
    /// key ID from Delegation
    pub fn key_id(&self, key_pair: &dyn Sign) -> Option<Decoded<Hex>> {
        for (key_id, key) in &self.keys {
            if key_pair.tuf_key() == *key {
                return Some(key_id.clone());
            }
        }
        None
    }
}

impl DelegatedRole {
    /// Returns a `RoleKeys` representation of the role
    pub fn keys(&self) -> RoleKeys {
        RoleKeys {
            keyids: self.keyids.clone(),
            threshold: self.threshold,
            _extra: HashMap::new(),
        }
    }
}

// =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=   =^..^=

/// Represents a `timestamp.json` file.
/// TUF 4.6: The timestamp file is signed by a timestamp key. It indicates the latest version of the
/// snapshot metadata and is frequently resigned to limit the amount of time a client can be kept
/// unaware of interference with obtaining updates.
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
#[serde(tag = "_type")]
#[serde(rename = "timestamp")]
pub struct Timestamp {
    /// A string that contains the version number of the TUF specification. Its format follows the
    /// Semantic Versioning 2.0.0 (semver) specification.
    pub spec_version: String,

    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
    /// number less than the one currently trusted.
    pub version: NonZeroU64,

    /// Determines when metadata should be considered expired and no longer trusted by clients.
    pub expires: DateTime<Utc>,

    /// METAFILES is the same as described for the snapshot.json file. In the case of the
    /// timestamp.json file, this MUST only include a description of the snapshot.json file.
    pub meta: HashMap<String, TimestampMeta>,

    /// Extra arguments found during deserialization.
    ///
    /// We must store these to correctly verify signatures for this object.
    ///
    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
    #[serde(flatten)]
    #[serde(deserialize_with = "de::extra_skip_type")]
    pub _extra: HashMap<String, Value>,
}

/// METAFILES is the same as described for the snapshot.json file. In the case of the timestamp.json
/// file, this MUST only include a description of the snapshot.json file.
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
pub struct TimestampMeta {
    /// The integer length in bytes of the snapshot.json file.
    pub length: u64,

    /// The hashes of the snapshot.json file.
    pub hashes: Hashes,

    /// An integer that is greater than 0. Clients MUST NOT replace a metadata file with a version
    /// number less than the one currently trusted.
    pub version: NonZeroU64,

    /// Extra arguments found during deserialization.
    ///
    /// We must store these to correctly verify signatures for this object.
    ///
    /// If you're instantiating this struct, you should make this `HashMap::empty()`.
    #[serde(flatten)]
    pub _extra: HashMap<String, Value>,
}

impl Timestamp {
    /// Creates a new `Timestamp` object.
    pub fn new(spec_version: String, version: NonZeroU64, expires: DateTime<Utc>) -> Self {
        Timestamp {
            spec_version,
            version,
            expires,
            meta: HashMap::new(),
            _extra: HashMap::new(),
        }
    }
}

impl Role for Timestamp {
    const TYPE: RoleType = RoleType::Timestamp;

    fn expires(&self) -> DateTime<Utc> {
        self.expires
    }

    fn version(&self) -> NonZeroU64 {
        self.version
    }

    fn filename(&self, _consistent_snapshot: bool) -> String {
        "timestamp.json".to_string()
    }
}

#[test]
fn targets_iter_and_map_test() {
    use maplit::hashmap;

    // Create a dummy Target object.
    let nothing = Target {
        length: 0,
        hashes: Hashes {
            sha256: [0u8].to_vec().into(),
            _extra: HashMap::default(),
        },
        custom: HashMap::default(),
        _extra: HashMap::default(),
    };

    // Create a hierarchy of targets/delegations: a -> b -> c
    let c_role = DelegatedRole {
        name: "c-role".to_string(),
        keyids: vec![],
        threshold: NonZeroU64::new(1).unwrap(),
        paths: PathSet::Paths(vec![PathPattern::new("*").unwrap()]),
        terminating: false,
        targets: Some(Signed {
            signed: Targets {
                spec_version: "".to_string(),
                version: NonZeroU64::new(1).unwrap(),
                expires: Utc::now(),
                targets: hashmap! {
                    TargetName::new("c.txt").unwrap() => nothing.clone(),
                },
                delegations: None,
                _extra: HashMap::default(),
            },
            signatures: vec![],
        }),
    };
    let b_delegations = Delegations {
        keys: HashMap::default(),
        roles: vec![c_role],
    };
    let b_role = DelegatedRole {
        name: "b-role".to_string(),
        keyids: vec![],
        threshold: NonZeroU64::new(1).unwrap(),
        paths: PathSet::Paths(vec![PathPattern::new("*").unwrap()]),
        terminating: false,
        targets: Some(Signed {
            signed: Targets {
                spec_version: "".to_string(),
                version: NonZeroU64::new(1).unwrap(),
                expires: Utc::now(),
                targets: hashmap! {
                    TargetName::new("b.txt").unwrap() => nothing.clone(),
                },
                delegations: Some(b_delegations),
                _extra: HashMap::default(),
            },
            signatures: vec![],
        }),
    };
    let a_delegations = Delegations {
        keys: HashMap::default(),
        roles: vec![b_role],
    };
    let a = Targets {
        spec_version: "".to_string(),
        version: NonZeroU64::new(1).unwrap(),
        expires: Utc::now(),
        targets: hashmap! {
            TargetName::new("a.txt").unwrap() => nothing,
        },
        delegations: Some(a_delegations),
        _extra: HashMap::default(),
    };

    // Assert that targets_iter is recursive and thus has a.txt, b.txt and c.txt
    assert!(a
        .targets_iter()
        .map(|(key, _)| key)
        .any(|item| item.raw() == "a.txt"));
    assert!(a
        .targets_iter()
        .map(|(key, _)| key)
        .any(|item| item.raw() == "b.txt"));
    assert!(a
        .targets_iter()
        .map(|(key, _)| key)
        .any(|item| item.raw() == "c.txt"));

    // Assert that targets_map is also recursive
    let map = a.targets_map();
    assert!(map.contains_key(&TargetName::new("a.txt").unwrap()));
    assert!(map.contains_key(&TargetName::new("b.txt").unwrap()));
    assert!(map.contains_key(&TargetName::new("c.txt").unwrap()));
}