Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: timestamp.json meta can has optional fields #778

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions tough/src/cache.rs
Original file line number Diff line number Diff line change
@@ -96,7 +96,8 @@ impl Repository {
{
self.cache_file_from_transport(
self.snapshot_filename().as_str(),
self.max_snapshot_size()?,
self.max_snapshot_size()?
.unwrap_or(self.limits.max_snapshot_size),
"timestamp.json",
&metadata_outdir,
)
@@ -237,7 +238,7 @@ impl Repository {
}

/// Gets the max size of the snapshot.json file as specified by the timestamp file.
fn max_snapshot_size(&self) -> Result<u64> {
fn max_snapshot_size(&self) -> Result<Option<u64>> {
let snapshot_meta =
self.timestamp()
.signed
22 changes: 11 additions & 11 deletions tough/src/editor/mod.rs
Original file line number Diff line number Diff line change
@@ -17,8 +17,8 @@ use crate::key_source::KeySource;
use crate::schema::decoded::{Decoded, Hex};
use crate::schema::key::Key;
use crate::schema::{
Hashes, KeyHolder, PathSet, Role, RoleType, Root, Signed, Snapshot, SnapshotMeta, Target,
Targets, Timestamp, TimestampMeta,
Hashes, KeyHolder, Metafile, PathSet, Role, RoleType, Root, Signed, Snapshot, Target, Targets,
Timestamp,
};
use crate::transport::{IntoVec, Transport};
use crate::{encode_filename, Limits};
@@ -700,13 +700,13 @@ impl RepositoryEditor {
Ok(snapshot)
}

/// Build a `SnapshotMeta` struct from a given `SignedRole<R>`. This metadata
/// Build a `Metafiles` struct from a given `SignedRole<R>`. This metadata
/// includes the sha256 and length of the signed role.
fn snapshot_meta<R>(role: &SignedRole<R>) -> SnapshotMeta
fn snapshot_meta<R>(role: &SignedRole<R>) -> Metafile
where
R: Role,
{
SnapshotMeta {
Metafile {
hashes: Some(Hashes {
sha256: role.sha256.to_vec().into(),
_extra: HashMap::new(),
@@ -738,18 +738,18 @@ impl RepositoryEditor {
Ok(timestamp)
}

/// Build a `TimestampMeta` struct from a given `SignedRole<R>`. This metadata
/// Build a `Metafiles` struct from a given `SignedRole<R>`. This metadata
/// includes the sha256 and length of the signed role.
fn timestamp_meta<R>(role: &SignedRole<R>) -> TimestampMeta
fn timestamp_meta<R>(role: &SignedRole<R>) -> Metafile
where
R: Role,
{
TimestampMeta {
hashes: Hashes {
Metafile {
hashes: Some(Hashes {
sha256: role.sha256.to_vec().into(),
_extra: HashMap::new(),
},
length: role.length,
}),
length: Some(role.length),
version: role.signed.signed.version(),
_extra: HashMap::new(),
}
39 changes: 29 additions & 10 deletions tough/src/lib.rs
Original file line number Diff line number Diff line change
@@ -250,8 +250,8 @@ impl<'a> RepositoryLoader<'a> {
/// are set higher than what would reasonably be expected by a repository, but not so high that the
/// amount of data could interfere with the system.
///
/// `max_root_size` and `max_timestamp_size` are the maximum size for the `root.json` and
/// `timestamp.json` files, respectively, downloaded from the repository. These must be
/// `max_root_size`, `max_timestamp_size` and `max_snapshot_size` are the maximum size for the `root.json`,
/// `timestamp.json` and `snapshot.json` files, respectively, downloaded from the repository. These must be
/// sufficiently large such that future updates to your repository's key management strategy
/// will still be supported, but sufficiently small such that you are protected against an
/// endless data attack (defined by TUF as an attacker responding to clients with extremely
@@ -261,6 +261,7 @@ impl<'a> RepositoryLoader<'a> {
/// * `max_root_size`: 1 MiB
/// * `max_targets_size`: 10 MiB
/// * `max_timestamp_size`: 1 MiB
/// * `max_snapshot_size`: 1 MiB
/// * `max_root_updates`: 1024
#[derive(Debug, Clone, Copy)]
pub struct Limits {
@@ -275,6 +276,9 @@ pub struct Limits {
/// The maximum allowable size in bytes for the downloaded timestamp.json file.
pub max_timestamp_size: u64,

/// The maximum allowable size in bytes for the downloaded snapshot.json file.
pub max_snapshot_size: u64,

/// The maximum number of updates to root.json to download.
pub max_root_updates: u64,
}
@@ -285,6 +289,7 @@ impl Default for Limits {
max_root_size: 1024 * 1024, // 1 MiB
max_targets_size: 1024 * 1024 * 10, // 10 MiB
max_timestamp_size: 1024 * 1024, // 1 MiB
max_snapshot_size: 1024 * 1024, // 1 MiB
max_root_updates: 1024,
}
}
@@ -360,6 +365,7 @@ impl Repository {
transport.as_ref(),
&root,
&timestamp,
limits.max_snapshot_size,
&datastore,
&metadata_base_url,
expiration_enforcement,
@@ -906,10 +912,12 @@ async fn load_timestamp(
}

/// Step 3 of the client application, which loads the snapshot metadata file.
#[allow(clippy::too_many_lines)]
async fn load_snapshot(
transport: &dyn Transport,
root: &Signed<Root>,
timestamp: &Signed<Timestamp>,
max_snapshot_size: u64,
datastore: &Datastore,
metadata_base_url: &Url,
expiration_enforcement: ExpirationEnforcement,
@@ -941,14 +949,25 @@ async fn load_snapshot(
path: path.clone(),
url: metadata_base_url.clone(),
})?;
let stream = fetch_sha256(
transport,
url.clone(),
snapshot_meta.length,
"timestamp.json",
&snapshot_meta.hashes.sha256,
)
.await?;
let stream = if let Some(hashes) = &snapshot_meta.hashes {
fetch_sha256(
transport,
url.clone(),
snapshot_meta.length.unwrap_or(max_snapshot_size),
"timestamp.json",
&hashes.sha256,
)
.await?
} else {
fetch_max_size(
transport,
url.clone(),
snapshot_meta.length.unwrap_or(max_snapshot_size),
"timestamp.json",
)
.await?
};

let data = stream
.into_vec()
.await
33 changes: 5 additions & 28 deletions tough/src/schema/mod.rs
Original file line number Diff line number Diff line change
@@ -256,11 +256,11 @@ pub struct Snapshot {
/// 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
/// A list of what the TUF spec calls 'METAFILES' (`Metafiles` 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>,
pub meta: HashMap<String, Metafile>,

/// Extra arguments found during deserialization.
///
@@ -272,7 +272,7 @@ pub struct Snapshot {
pub _extra: HashMap<String, Value>,
}

/// Represents a metadata file in a `snapshot.json` file.
/// Represents a metadata file in a `snapshot.json` and in a `timestamp.json` file.
/// TUF 4.4: METAFILES is an object whose format is the following:
/// ```text
/// { METAPATH : {
@@ -292,7 +292,7 @@ pub struct Snapshot {
/// },
/// ```
#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
pub struct SnapshotMeta {
pub struct Metafile {
/// 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.
@@ -1112,7 +1112,7 @@ pub struct Timestamp {

/// 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>,
pub meta: HashMap<String, Metafile>,

/// Extra arguments found during deserialization.
///
@@ -1124,29 +1124,6 @@ pub struct Timestamp {
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 {
1 change: 1 addition & 0 deletions tough/tests/interop.rs
Original file line number Diff line number Diff line change
@@ -80,6 +80,7 @@ async fn test_tuf_reference_impl_default_transport() {
max_root_size: 1000,
max_targets_size: 2000,
max_timestamp_size: 3000,
max_snapshot_size: 4000,
max_root_updates: 1,
})
.datastore(datastore.path())