in tough/src/lib.rs [635:798]
fn load_root<R: Read>(
transport: &dyn Transport,
root: R,
datastore: &Datastore,
max_root_size: u64,
max_root_updates: u64,
metadata_base_url: &Url,
expiration_enforcement: ExpirationEnforcement,
) -> Result<Signed<Root>> {
// 0. Load the trusted root metadata file. We assume that a good, trusted copy of this file was
// shipped with the package manager or software updater using an out-of-band process. Note
// that the expiration of the trusted root metadata file does not matter, because we will
// attempt to update it in the next step.
let mut root: Signed<Root> =
serde_json::from_reader(root).context(error::ParseTrustedMetadataSnafu)?;
root.signed
.verify_role(&root)
.context(error::VerifyTrustedMetadataSnafu)?;
// Used in step 1.2
let original_root_version = root.signed.version.get();
// Used in step 1.9
let original_timestamp_keys = root
.signed
.keys(RoleType::Timestamp)
.cloned()
.collect::<Vec<_>>();
let original_snapshot_keys = root
.signed
.keys(RoleType::Snapshot)
.cloned()
.collect::<Vec<_>>();
// 1. Update the root metadata file. Since it may now be signed using entirely different keys,
// the client must somehow be able to establish a trusted line of continuity to the latest
// set of keys. To do so, the client MUST download intermediate root metadata files, until
// the latest available one is reached. Therefore, it MUST temporarily turn on consistent
// snapshots in order to download versioned root metadata files as described next.
loop {
// 1.1. Let N denote the version number of the trusted root metadata file.
//
// 1.2. Try downloading version N+1 of the root metadata file, up to some X number of bytes
// (because the size is unknown). The value for X is set by the authors of the
// application using TUF. For example, X may be tens of kilobytes. The filename used to
// download the root metadata file is of the fixed form VERSION_NUMBER.FILENAME.EXT
// (e.g., 42.root.json). If this file is not available, or we have downloaded more than Y
// number of root metadata files (because the exact number is as yet unknown), then go to
// step 1.8. The value for Y is set by the authors of the application using TUF. For
// example, Y may be 2^10.
ensure!(
root.signed.version.get() < original_root_version + max_root_updates,
error::MaxUpdatesExceededSnafu { max_root_updates }
);
let path = format!("{}.root.json", root.signed.version.get() + 1);
match fetch_max_size(
transport,
metadata_base_url.join(&path).context(error::JoinUrlSnafu {
path,
url: metadata_base_url.clone(),
})?,
max_root_size,
"max_root_size argument",
) {
Err(_) => break, // If this file is not available, then go to step 1.8.
Ok(reader) => {
let new_root: Signed<Root> =
serde_json::from_reader(reader).context(error::ParseMetadataSnafu {
role: RoleType::Root,
})?;
// 1.3. Check signatures. Version N+1 of the root metadata file MUST have been
// signed by: (1) a threshold of keys specified in the trusted root metadata file
// (version N), and (2) a threshold of keys specified in the new root metadata
// file being validated (version N+1). If version N+1 is not signed as required,
// discard it, abort the update cycle, and report the signature failure. On the
// next update cycle, begin at step 0 and version N of the root metadata file.
root.signed
.verify_role(&new_root)
.context(error::VerifyMetadataSnafu {
role: RoleType::Root,
})?;
new_root
.signed
.verify_role(&new_root)
.context(error::VerifyMetadataSnafu {
role: RoleType::Root,
})?;
// 1.4. Check for a rollback attack. The version number of the trusted root
// metadata file (version N) must be less than or equal to the version number of
// the new root metadata file (version N+1). Effectively, this means checking
// that the version number signed in the new root metadata file is indeed N+1. If
// the version of the new root metadata file is less than the trusted metadata
// file, discard it, abort the update cycle, and report the rollback attack. On
// the next update cycle, begin at step 0 and version N of the root metadata
// file.
ensure!(
root.signed.version <= new_root.signed.version,
error::OlderMetadataSnafu {
role: RoleType::Root,
current_version: root.signed.version,
new_version: new_root.signed.version
}
);
// Off-spec: 1.4 specifies that the version number of the trusted root metadata
// file must be less than or equal to the version number of the new root metadata
// file. If they are equal, this will create an infinite loop, so we ignore the new
// root metadata file but do not report an error. This could only happen if the
// path we built above, referencing N+1, has a filename that doesn't match its
// contents, which would have to list version N.
if root.signed.version == new_root.signed.version {
break;
}
// 1.5. Note that the expiration of the new (intermediate) root metadata file does
// not matter yet, because we will check for it in step 1.8.
//
// 1.6. Set the trusted root metadata file to the new root metadata file.
//
// (This is where version N+1 becomes version N.)
root = new_root;
// 1.7. Repeat steps 1.1 to 1.7.
continue;
}
}
}
// TUF v1.0.16, 5.2.9. Check for a freeze attack. The expiration timestamp in the trusted root
// metadata file MUST be higher than the fixed update start time. If the trusted root metadata
// file has expired, abort the update cycle, report the potential freeze attack. On the next
// update cycle, begin at step 5.1 and version N of the root metadata file.
if expiration_enforcement == ExpirationEnforcement::Safe {
check_expired(datastore, &root.signed)?;
}
// 1.9. If the timestamp and / or snapshot keys have been rotated, then delete the trusted
// timestamp and snapshot metadata files. This is done in order to recover from fast-forward
// attacks after the repository has been compromised and recovered. A fast-forward attack
// happens when attackers arbitrarily increase the version numbers of: (1) the timestamp
// metadata, (2) the snapshot metadata, and / or (3) the targets, or a delegated targets,
// metadata file in the snapshot metadata.
if original_timestamp_keys
.iter()
.ne(root.signed.keys(RoleType::Timestamp))
|| original_snapshot_keys
.iter()
.ne(root.signed.keys(RoleType::Snapshot))
{
let r1 = datastore.remove("timestamp.json");
let r2 = datastore.remove("snapshot.json");
r1.and(r2)?;
}
// 1.10. Set whether consistent snapshots are used as per the trusted root metadata file (see
// Section 4.3).
//
// (This is done by checking the value of root.signed.consistent_snapshot throughout this
// library.)
Ok(root)
}