in tough/src/lib.rs [1007:1142]
fn load_targets(
transport: &dyn Transport,
root: &Signed<Root>,
snapshot: &Signed<Snapshot>,
datastore: &Datastore,
max_targets_size: u64,
metadata_base_url: &Url,
expiration_enforcement: ExpirationEnforcement,
) -> Result<Signed<crate::schema::Targets>> {
// 4. Download the top-level targets metadata file, up to either the number of bytes specified
// in the snapshot metadata file, or some Z number of bytes. The value for Z is set by the
// authors of the application using TUF. For example, Z may be tens of kilobytes. If
// consistent snapshots are not used (see Section 7), then the filename used to download the
// targets metadata file is of the fixed form FILENAME.EXT (e.g., targets.json). Otherwise,
// the filename is of the form VERSION_NUMBER.FILENAME.EXT (e.g., 42.targets.json), where
// VERSION_NUMBER is the version number of the targets metadata file listed in the snapshot
// metadata file. In either case, the client MUST write the file to non-volatile storage as
// FILENAME.EXT.
let targets_meta =
snapshot
.signed
.meta
.get("targets.json")
.context(error::MetaMissingSnafu {
file: "targets.json",
role: RoleType::Timestamp,
})?;
let path = if root.signed.consistent_snapshot {
format!("{}.targets.json", targets_meta.version)
} else {
"targets.json".to_owned()
};
let targets_url = metadata_base_url.join(&path).context(error::JoinUrlSnafu {
path,
url: metadata_base_url.clone(),
})?;
let (max_targets_size, specifier) = match targets_meta.length {
Some(length) => (length, "snapshot.json"),
None => (max_targets_size, "max_targets_size parameter"),
};
let reader = if let Some(hashes) = &targets_meta.hashes {
Box::new(fetch_sha256(
transport,
targets_url,
max_targets_size,
specifier,
&hashes.sha256,
)?) as Box<dyn Read>
} else {
Box::new(fetch_max_size(
transport,
targets_url,
max_targets_size,
specifier,
)?)
};
let mut targets: Signed<crate::schema::Targets> =
serde_json::from_reader(reader).context(error::ParseMetadataSnafu {
role: RoleType::Targets,
})?;
// 4.1. Check against snapshot metadata. The hashes (if any), and version number of the new
// targets metadata file MUST match the trusted snapshot metadata. This is done, in part, to
// prevent a mix-and-match attack by man-in-the-middle attackers. If the new targets metadata
// file does not match, discard it, abort the update cycle, and report the failure.
//
// (We already checked the hash in `fetch_sha256` above.)
ensure!(
targets.signed.version == targets_meta.version,
error::VersionMismatchSnafu {
role: RoleType::Targets,
fetched: targets.signed.version,
expected: targets_meta.version
}
);
// 4.2. Check for an arbitrary software attack. The new targets metadata file MUST have been
// signed by a threshold of keys specified in the trusted root metadata file. If the new
// targets metadata file is not signed as required, discard it, abort the update cycle, and
// report the failure.
root.signed
.verify_role(&targets)
.context(error::VerifyMetadataSnafu {
role: RoleType::Targets,
})?;
// 4.3. Check for a rollback attack. The version number of the trusted targets metadata file,
// if any, MUST be less than or equal to the version number of the new targets metadata file.
// If the new targets metadata file is older than the trusted targets metadata file, discard
// it, abort the update cycle, and report the potential rollback attack.
if let Some(Ok(old_targets)) = datastore
.reader("targets.json")?
.map(serde_json::from_reader::<_, Signed<crate::schema::Targets>>)
{
if root.signed.verify_role(&old_targets).is_ok() {
ensure!(
old_targets.signed.version <= targets.signed.version,
error::OlderMetadataSnafu {
role: RoleType::Targets,
current_version: old_targets.signed.version,
new_version: targets.signed.version
}
);
}
}
// TUF v1.0.16, 5.5.4. Check for a freeze attack. The expiration timestamp in the new targets
// metadata file MUST be higher than the fixed update start time. If so, the new targets
// metadata file becomes the trusted targets metadata file. If the new targets metadata file is
// expired, discard it, abort the update cycle, and report the potential freeze attack.
if expiration_enforcement == ExpirationEnforcement::Safe {
check_expired(datastore, &targets.signed)?;
}
// Now that everything seems okay, write the targets file to the datastore.
datastore.create("targets.json", &targets)?;
// 4.5. Perform a preorder depth-first search for metadata about the desired target, beginning
// with the top-level targets role.
if let Some(delegations) = &mut targets.signed.delegations {
load_delegations(
transport,
snapshot,
root.signed.consistent_snapshot,
metadata_base_url,
max_targets_size,
delegations,
datastore,
)?;
}
// This validation can only be done from the top level targets.json role. This check verifies
// that each target's delegate hierarchy is a match (i.e. it's delegate ownership is valid).
targets.signed.validate().context(error::InvalidPathSnafu)?;
Ok(targets)
}