in tough/src/lib.rs [872:1004]
fn load_snapshot(
transport: &dyn Transport,
root: &Signed<Root>,
timestamp: &Signed<Timestamp>,
datastore: &Datastore,
metadata_base_url: &Url,
expiration_enforcement: ExpirationEnforcement,
) -> Result<Signed<Snapshot>> {
// 3. Download snapshot metadata file, up to the number of bytes specified in the timestamp
// metadata file. If consistent snapshots are not used (see Section 7), then the filename
// used to download the snapshot metadata file is of the fixed form FILENAME.EXT (e.g.,
// snapshot.json). Otherwise, the filename is of the form VERSION_NUMBER.FILENAME.EXT (e.g.,
// 42.snapshot.json), where VERSION_NUMBER is the version number of the snapshot metadata
// file listed in the timestamp metadata file. In either case, the client MUST write the
// file to non-volatile storage as FILENAME.EXT.
let snapshot_meta =
timestamp
.signed
.meta
.get("snapshot.json")
.context(error::MetaMissingSnafu {
file: "snapshot.json",
role: RoleType::Timestamp,
})?;
let path = if root.signed.consistent_snapshot {
format!("{}.snapshot.json", snapshot_meta.version)
} else {
"snapshot.json".to_owned()
};
let reader = fetch_sha256(
transport,
metadata_base_url.join(&path).context(error::JoinUrlSnafu {
path,
url: metadata_base_url.clone(),
})?,
snapshot_meta.length,
"timestamp.json",
&snapshot_meta.hashes.sha256,
)?;
let snapshot: Signed<Snapshot> =
serde_json::from_reader(reader).context(error::ParseMetadataSnafu {
role: RoleType::Snapshot,
})?;
// 3.1. Check against timestamp metadata. The hashes and version number of the new snapshot
// metadata file MUST match the hashes and version number listed in timestamp metadata. If
// hashes and version do not match, discard the new snapshot metadata, abort the update
// cycle, and report the failure.
//
// (We already checked the hash in `fetch_sha256` above.)
ensure!(
snapshot.signed.version == snapshot_meta.version,
error::VersionMismatchSnafu {
role: RoleType::Snapshot,
fetched: snapshot.signed.version,
expected: snapshot_meta.version
}
);
// 3.2. Check signatures. The new snapshot metadata file MUST have been signed by a threshold
// of keys specified in the trusted root metadata file. If the new snapshot metadata file is
// not signed as required, discard it, abort the update cycle, and report the signature
// failure.
root.signed
.verify_role(&snapshot)
.context(error::VerifyMetadataSnafu {
role: RoleType::Snapshot,
})?;
// 3.3. Check for a rollback attack.
//
// 3.3.1. Note that the trusted snapshot metadata file may be checked for authenticity, but its
// expiration does not matter for the following purposes.
if let Some(Ok(old_snapshot)) = datastore
.reader("snapshot.json")?
.map(serde_json::from_reader::<_, Signed<Snapshot>>)
{
// 3.3.2. The version number of the trusted snapshot metadata file, if any, MUST be less
// than or equal to the version number of the new snapshot metadata file. If the new
// snapshot metadata file is older than the trusted metadata file, discard it, abort the
// update cycle, and report the potential rollback attack.
if root.signed.verify_role(&old_snapshot).is_ok() {
ensure!(
old_snapshot.signed.version <= snapshot.signed.version,
error::OlderMetadataSnafu {
role: RoleType::Snapshot,
current_version: old_snapshot.signed.version,
new_version: snapshot.signed.version
}
);
// 3.3.3. The version number of the targets metadata file, and all delegated targets
// metadata files (if any), in the trusted snapshot metadata file, if any, MUST be
// less than or equal to its version number in the new snapshot metadata file.
// Furthermore, any targets metadata filename that was listed in the trusted snapshot
// metadata file, if any, MUST continue to be listed in the new snapshot metadata
// file. If any of these conditions are not met, discard the new snaphot metadadata
// file, abort the update cycle, and report the failure.
if let Some(old_targets_meta) = old_snapshot.signed.meta.get("targets.json") {
let targets_meta =
snapshot
.signed
.meta
.get("targets.json")
.context(error::MetaMissingSnafu {
file: "targets.json",
role: RoleType::Snapshot,
})?;
ensure!(
old_targets_meta.version <= targets_meta.version,
error::OlderMetadataSnafu {
role: RoleType::Targets,
current_version: old_targets_meta.version,
new_version: targets_meta.version,
}
);
}
}
}
// TUF v1.0.16, 5.4.5. Check for a freeze attack. The expiration timestamp in the new snapshot
// metadata file MUST be higher than the fixed update start time. If so, the new snapshot
// metadata file becomes the trusted snapshot metadata file. If the new snapshot metadata file
// is expired, discard it, abort the update cycle, and report the potential freeze attack.
if expiration_enforcement == ExpirationEnforcement::Safe {
check_expired(datastore, &snapshot.signed)?;
}
// Now that everything seems okay, write the snapshot file to the datastore.
datastore.create("snapshot.json", &snapshot)?;
Ok(snapshot)
}