From aae2fc6fc1f8f1da70da2867ce544d25d1c16155 Mon Sep 17 00:00:00 2001 From: Flavio Castelli Date: Tue, 18 Jan 2022 09:05:02 +0100 Subject: [PATCH] Add TUF support Allow Fulcio and Rekor data to be fetched from the official TUF repository of Sigstore. A new module is introduced, called `tuf`, which provides helper structs to interact with a remote TUF repository. Fixes https://github.com/sigstore/sigstore-rs/issues/9 Signed-off-by: Flavio Castelli --- Cargo.toml | 6 +- examples/verify/main.rs | 76 +++- src/lib.rs | 10 + src/tuf/constants.rs | 166 +++++++++ src/tuf/mod.rs | 159 ++++++++ src/tuf/repository_helper.rs | 345 ++++++++++++++++++ tests/data/repository/1.root.json | 130 +++++++ tests/data/repository/2.root.json | 144 ++++++++ tests/data/repository/rekor.json | 23 ++ tests/data/repository/root.json | 144 ++++++++ tests/data/repository/snapshot.json | 48 +++ tests/data/repository/staging.json | 15 + tests/data/repository/targets.json | 117 ++++++ tests/data/repository/targets/artifact.pub | 4 + tests/data/repository/targets/ctfe.pub | 4 + tests/data/repository/targets/fulcio.crt.pem | 13 + .../data/repository/targets/fulcio_v1.crt.pem | 13 + tests/data/repository/targets/rekor.0.pub | 4 + tests/data/repository/targets/rekor.json | 23 ++ tests/data/repository/targets/rekor.pub | 4 + tests/data/repository/timestamp.json | 24 ++ 21 files changed, 1453 insertions(+), 19 deletions(-) create mode 100644 src/tuf/constants.rs create mode 100644 src/tuf/mod.rs create mode 100644 src/tuf/repository_helper.rs create mode 100644 tests/data/repository/1.root.json create mode 100644 tests/data/repository/2.root.json create mode 100644 tests/data/repository/rekor.json create mode 100644 tests/data/repository/root.json create mode 100644 tests/data/repository/snapshot.json create mode 100644 tests/data/repository/staging.json create mode 100644 tests/data/repository/targets.json create mode 100644 tests/data/repository/targets/artifact.pub create mode 100644 tests/data/repository/targets/ctfe.pub create mode 100644 tests/data/repository/targets/fulcio.crt.pem create mode 100644 tests/data/repository/targets/fulcio_v1.crt.pem create mode 100644 tests/data/repository/targets/rekor.0.pub create mode 100644 tests/data/repository/targets/rekor.json create mode 100644 tests/data/repository/targets/rekor.pub create mode 100644 tests/data/repository/timestamp.json diff --git a/Cargo.toml b/Cargo.toml index 73044a4143..d62fea3357 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,14 +20,18 @@ ecdsa = { version = "0.12.4", features = ["verify", "pem", "der", "pkcs8"] } oci-distribution = { version = "0.8.1", default-features = false } olpc-cjson = "0.1.1" p256 = {version = "0.9.0", features = ["ecdsa-core"]} +sha2 = "0.10.1" serde_json = "1.0.68" serde = {version = "1.0.130", features = ["derive"]} tokio = { version = "1.12.0", features = ["full"]} +tough = { version = "0.12.1", features = [ "http" ] } tracing = "0.1.29" +url = "2.2.2" x509-parser = { version = "0.12.0", features = ["verify"]} [dev-dependencies] chrono = "0.4.19" clap = "2.33.3" openssl = "0.10.38" -tracing-subscriber = "0.2.25" \ No newline at end of file +tempdir = "0.3.7" +tracing-subscriber = "0.2.25" diff --git a/examples/verify/main.rs b/examples/verify/main.rs index ec0521ac2d..2bed283635 100644 --- a/examples/verify/main.rs +++ b/examples/verify/main.rs @@ -16,16 +16,19 @@ extern crate sigstore; use sigstore::cosign::CosignCapabilities; use sigstore::simple_signing::SimpleSigning; +use sigstore::tuf::SigstoreRepository; extern crate anyhow; use anyhow::{anyhow, Result}; -use std::{collections::HashMap, fs}; - extern crate clap; use clap::{App, Arg}; +use std::{collections::HashMap, fs}; +use tokio::task::spawn_blocking; + extern crate tracing_subscriber; +use tracing::info; use tracing_subscriber::prelude::*; use tracing_subscriber::{fmt, EnvFilter}; @@ -41,6 +44,13 @@ fn cli() -> App<'static, 'static> { .required(false) .takes_value(true), ) + .arg( + Arg::with_name("use-sigstore-tuf-data") + .long("use-sigstore-tuf-data") + .help("Fetch Rekor and Fulcio data from Sigstore's TUF repository") + .required(false) + .takes_value(false), + ) .arg( Arg::with_name("rekor-pub-key") .long("rekor-pub-key") @@ -97,27 +107,69 @@ fn cli() -> App<'static, 'static> { async fn run_app() -> Result> { let matches = cli().get_matches(); + // setup logging + let level_filter = if matches.is_present("verbose") { + "debug" + } else { + "info" + }; + let filter_layer = EnvFilter::new(level_filter); + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt::layer().with_writer(std::io::stderr)) + .init(); + let auth = &sigstore::registry::Auth::Anonymous; let rekor_pub_key: Option = matches .value_of("rekor-pub-key") - .map(|path| fs::read_to_string(path).expect("Error reading rekor public key from disk")); + .map(|path| { + fs::read_to_string(path) + .map_err(|e| anyhow!("Error reading rekor public key from disk: {}", e)) + }) + .transpose()?; let fulcio_cert: Option> = matches .value_of("fulcio-crt") - .map(|path| fs::read(path).expect("Error reading fulcio certificate from disk")); + .map(|path| { + fs::read(path).map_err(|e| anyhow!("Error reading fulcio certificate from disk: {}", e)) + }) + .transpose()?; + + let sigstore_repo: Option = if matches.is_present("use-sigstore-tuf-data") { + let repo: Result = spawn_blocking(|| { + info!("Downloading data from Sigstore TUF repository"); + sigstore::tuf::SigstoreRepository::fetch(None) + }) + .await + .map_err(|e| anyhow!("Error spawining blocking task inside of tokio: {}", e))?; + + Some(repo?) + } else { + None + }; let mut client_builder = sigstore::cosign::ClientBuilder::default(); + + if let Some(repo) = sigstore_repo { + client_builder = client_builder.with_rekor_pub_key(repo.rekor_pub_key()); + client_builder = client_builder.with_fulcio_cert(repo.fulcio_cert()); + } + + // Set Rekor public key. Give higher precendece to the key specified by the user over the + // one that can be obtained from Sigstore's TUF repository if let Some(key) = rekor_pub_key { client_builder = client_builder.with_rekor_pub_key(&key); } + // Set Fulcio certificate. Give higher precendece to the certificate specified by the user over the + // one that can be obtained from Sigstore's TUF repository if let Some(cert) = fulcio_cert { client_builder = client_builder.with_fulcio_cert(&cert); } + let mut client = client_builder .with_cert_email(matches.value_of("cert-email")) - .build() - .expect("Error while building cosign client"); + .build()?; let image: &str = matches.value_of("IMAGE").unwrap(); @@ -149,18 +201,6 @@ async fn run_app() -> Result> { } }; - // setup logging - let level_filter = if matches.is_present("verbose") { - "debug" - } else { - "info" - }; - let filter_layer = EnvFilter::new(level_filter); - tracing_subscriber::registry() - .with(filter_layer) - .with(fmt::layer().with_writer(std::io::stderr)) - .init(); - client .verify( auth, diff --git a/src/lib.rs b/src/lib.rs index caf82cfdeb..01fe622442 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -108,6 +108,15 @@ //! } //! ``` //! +//! ## Fulcio and Rekor integration +//! +//! [`cosign::Client`](crate::cosign::Client) integration with Fulcio and Rekor +//! requires the following data to work: Fulcio's certificate and Rekor's public key. +//! +//! These files are safely distributed by the Sigstore project via a TUF repository. +//! The [`sigstore::tuf`](crate::tuf) module provides the helper structures to deal +//! with it. +//! //! # Examples //! //! Additional examples can be found inside of the [`examples`](https://github.com/sigstore/sigstore-rs/tree/main/examples/) @@ -119,3 +128,4 @@ mod mock_client; pub mod cosign; pub mod registry; pub mod simple_signing; +pub mod tuf; diff --git a/src/tuf/constants.rs b/src/tuf/constants.rs new file mode 100644 index 0000000000..74ad6a32d3 --- /dev/null +++ b/src/tuf/constants.rs @@ -0,0 +1,166 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub(crate) const SIGSTORE_METADATA_BASE: &str = "http://sigstore-tuf-root.storage.googleapis.com/"; +pub(crate) const SIGSTORE_TARGET_BASE: &str = + "http://sigstore-tuf-root.storage.googleapis.com/targets"; + +pub(crate) const SIGSTORE_FULCIO_CERT_TARGET: &str = "fulcio.crt.pem"; +pub(crate) const SIGSTORE_REKOR_PUB_KEY_TARGET: &str = "rekor.pub"; + +pub(crate) const SIGSTORE_ROOT: &str = r#"{ + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3046022100d3ea59490b253beae0926c6fa63f54336dea1ed700555be9f27ff55cd347639c0221009157d1ba012cead81948a4ab777d355451d57f5c4a2d333fc68d2e3f358093c2" + }, + { + "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "sig": "304502206eaef40564403ce572c6d062e0c9b0aab5e0223576133e081e1b495e8deb9efd02210080fd6f3464d759601b4afec596bbd5952f3a224cd06ed1cdfc3c399118752ba2" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "304502207baace02f56d8e6069f10b6ff098a26e7f53a7f9324ad62cffa0557bdeb9036c022100fb3032baaa090d0040c3f2fd872571c84479309b773208601d65948df87a9720" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304402205180c01905505dd88acd7a2dad979dd75c979b3722513a7bdedac88c6ae8dbeb022056d1ddf7a192f0b1c2c90ff487de2fb3ec9f0c03f66ea937c78d3b6a493504ca" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "3046022100c8806d4647c514d80fd8f707d3369444c4fd1d0812a2d25f828e564c99790e3f022100bb51f12e862ef17a7d3da2ac103bebc5c7e792237006c4cafacd76267b249c2f" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2022-05-11T19:09:02.663975009Z", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d" + ], + "threshold": 1 + } + }, + "spec_version": "1.0", + "version": 2 + } +}"#; diff --git a/src/tuf/mod.rs b/src/tuf/mod.rs new file mode 100644 index 0000000000..ceb85a2593 --- /dev/null +++ b/src/tuf/mod.rs @@ -0,0 +1,159 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Helper Structs to interact with the Sigstore TUF repository. +//! +//! The main interaction point is [`SigstoreRepository`], which fetches Rekor's +//! public key and Fulcio's certificate. +//! +//! These can later be given to [`cosign::ClientBuilder`](crate::cosign::ClientBuilder) +//! to enable Fulcio and Rekor integrations. +//! +//! # Example +//! +//! The `SigstoreRepository` instance can be created via the [`SigstoreRepository::fetch`] +//! method. +//! +//! ```rust,no_run +//! use sigstore::tuf::SigstoreRepository; +//! use sigstore::cosign; +//! +//! let repo = SigstoreRepository::fetch(None) +//! .expect("Error while building SigstoreRepository"); +//! let client = cosign::ClientBuilder::default() +//! .with_rekor_pub_key(repo.rekor_pub_key()) +//! .with_fulcio_cert(repo.fulcio_cert()) +//! .build() +//! .expect("Error while building cosign client"); +//! ``` +//! +//! The `SigstoreRepository::fetch` method can attempt to leverage local copies +//! of the Rekor and Fulcio files. Please refer to the +//! [method docs](SigstoreRepository::fetch) for more details. +//! +//! **Warning:** the `SigstoreRepository::fetch` method currently needs +//! special handling when invoked inside of an async context. Please refer to the +//! [method docs](SigstoreRepository::fetch) for more details. +//! +use anyhow::{anyhow, Result}; +use std::path::Path; + +mod constants; +use constants::*; + +mod repository_helper; +use repository_helper::RepositoryHelper; + +/// Securely fetches Rekor public key and Fulcio certificate from Sigstore's TUF repository +pub struct SigstoreRepository { + rekor_pub_key: String, + fulcio_cert: Vec, +} + +impl SigstoreRepository { + /// Fetch relevant information from the remote Sigstore TUF repository. + /// + /// ## Parameters + /// + /// * `checkout_dir`: path to a local directory where Rekor's public + /// key and Fulcio's certificate can be found + /// + /// ## Behaviour + /// + /// This method requires network connectivity, because it will always + /// reach out to Sigstore's TUF repository. + /// + /// This crates embeds a trusted copy of the `root.json` file of Sigstore's + /// TUF repository. The `fetch` function will always connect to the online + /// Sigstore's repository to update this embedded file to the latest version. + /// The update process happens using the TUF protocol. + /// + /// When `checkout_dir` is specified, this method will look for the + /// Fulcio and Rekor files inside of this directory. It will then compare the + /// checksums of these local files with the ones reported inside of the + /// TUF repository metadata. + /// + /// If the files are not found, or if their local checksums do not match + /// with the ones reported by TUF's metdata, the files are then downloaded + /// from the TUF repository and then written to the local filesystem. + /// + /// When `checkout_dir` is `None`, the `fetch` method will always fetch the + /// Fulcio and Rekor files from the remote TUF repository and keep them + /// in memory. + /// + /// ## Usage inside of async code + /// + /// **Warning:** this method needs special handling when invoked from + /// an async function because it peforms blocking operations. + /// + /// If needed, this can be solved in that way: + /// + /// ```rust,no_run + /// use tokio::task::spawn_blocking; + /// use sigstore::tuf::SigstoreRepository; + /// + /// async fn my_async_function() { + /// // ... your code + /// + /// let repo: anyhow::Result = spawn_blocking(|| + /// sigstore::tuf::SigstoreRepository::fetch(None) + /// ) + /// .await + /// .expect("Error spawning blocking task"); + /// + /// // handle the case of `repo` being an `Err` + /// // ... your code + /// } + /// ``` + /// + /// This of course has a performance hit when used inside of an async function. + pub fn fetch(checkout_dir: Option<&Path>) -> Result { + let metadata_base = url::Url::parse(SIGSTORE_METADATA_BASE)?; + let target_base = url::Url::parse(SIGSTORE_TARGET_BASE)?; + + let repository_helper = RepositoryHelper::new( + SIGSTORE_ROOT.as_bytes(), + metadata_base, + target_base, + checkout_dir, + )?; + + let fulcio_cert = repository_helper.fulcio_cert()?; + + let rekor_pub_key = repository_helper.rekor_pub_key().map(|data| { + String::from_utf8(data).map_err(|e| { + anyhow!( + "Cannot parse Rekor's public key obtained from TUF repository: {}", + e + ) + }) + })??; + + Ok(SigstoreRepository { + rekor_pub_key, + fulcio_cert, + }) + } + + /// Rekor public key + pub fn rekor_pub_key(&self) -> &str { + &self.rekor_pub_key + } + + /// Fulcio certificate + pub fn fulcio_cert(&self) -> &[u8] { + &self.fulcio_cert + } +} diff --git a/src/tuf/repository_helper.rs b/src/tuf/repository_helper.rs new file mode 100644 index 0000000000..f8e1652878 --- /dev/null +++ b/src/tuf/repository_helper.rs @@ -0,0 +1,345 @@ +// +// Copyright 2021 The Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use anyhow::{anyhow, Result}; +use sha2::{Digest, Sha256}; +use std::fs; +use std::io::Read; +use std::path::{Path, PathBuf}; +use tough::{RepositoryLoader, TargetName}; +use url::Url; + +use super::constants::{SIGSTORE_FULCIO_CERT_TARGET, SIGSTORE_REKOR_PUB_KEY_TARGET}; + +pub(crate) struct RepositoryHelper { + repository: tough::Repository, + checkout_dir: Option, +} + +impl RepositoryHelper { + pub(crate) fn new( + root: R, + metadata_base: Url, + target_base: Url, + checkout_dir: Option<&Path>, + ) -> Result + where + R: Read, + { + let repository = RepositoryLoader::new(root, metadata_base, target_base) + .expiration_enforcement(tough::ExpirationEnforcement::Safe) + .load() + .map_err(|e| anyhow!("Cannot load Sigstore TUF repository: {}", e))?; + + Ok(Self { + repository, + checkout_dir: checkout_dir.map(|s| s.to_owned()), + }) + } + + /// Fetch Fulcio certificate from the given TUF repository or reuse + /// the local cache is used if its contents are not outdated. + /// + /// The contents of the local cache are updated when they are outdated. + pub(crate) fn fulcio_cert(&self) -> Result> { + let fulcio_target_name = TargetName::new(SIGSTORE_FULCIO_CERT_TARGET)?; + + let local_fulcio_path = self + .checkout_dir + .as_ref() + .map(|d| Path::new(d).join(SIGSTORE_FULCIO_CERT_TARGET)); + + fetch_target_or_reuse_local_cache( + &self.repository, + &fulcio_target_name, + local_fulcio_path.as_ref(), + ) + } + + /// Fetch Rekor public key from the given TUF repository or reuse + /// the local cache if it's not outdated. + /// + /// The contents of the local cache are updated when they are outdated. + pub(crate) fn rekor_pub_key(&self) -> Result> { + let rekor_target_name = TargetName::new(SIGSTORE_REKOR_PUB_KEY_TARGET)?; + + let local_rekor_path = self + .checkout_dir + .as_ref() + .map(|d| Path::new(d).join(SIGSTORE_REKOR_PUB_KEY_TARGET)); + + fetch_target_or_reuse_local_cache( + &self.repository, + &rekor_target_name, + local_rekor_path.as_ref(), + ) + } +} + +/// Download a file stored inside of a TUF repository, try to reuse a local +/// cache when possible. +/// +/// * `repository`: TUF repository holding the file +/// * `target`: TUF representation of the file to be downloaded +/// * `local_file`: location where the file should be downloaded +/// +/// This function will reuse the local copy of the file if contents +/// didn't change. +/// This check is done by comparing the digest of the local file, if found, +/// with the digest reported inside of the TUF repository metadata. +/// +/// **Note well:** the `local_file` is updated whenever its contents are +/// outdated. +fn fetch_target_or_reuse_local_cache( + repository: &tough::Repository, + target_name: &TargetName, + local_file: Option<&PathBuf>, +) -> Result> { + let (local_file_outdated, local_file_contents) = if let Some(path) = local_file { + is_local_file_outdated(repository, target_name, path) + } else { + Ok((true, None)) + }?; + + let data = if local_file_outdated { + let data = fetch_target(repository, target_name)?; + if let Some(path) = local_file { + // update the local file to have latest data from the TUF repo + fs::write(path, data.clone()).map_err(|e| { + anyhow!("Error updating local copy of {}: {}", target_name.raw(), e) + })?; + } + data + } else { + local_file_contents + .expect("local file contents to not be 'None'") + .as_bytes() + .to_owned() + }; + + Ok(data) +} + +/// Download a file from a TUF repository +fn fetch_target(repository: &tough::Repository, target_name: &TargetName) -> Result> { + let data: Vec; + match repository.read_target(target_name) { + Err(e) => return Err(anyhow!("Cannot fetch target {}: {}", target_name.raw(), e)), + Ok(None) => { + return Err(anyhow!( + "target {} cannot be found inside of Sigstore TUF repository", + target_name.raw() + )) + } + Ok(Some(reader)) => { + data = read_to_end(reader)?; + } + }; + + Ok(data) +} + +/// Compares the checksum of a local file, with the digest reported inside of +/// TUF repository metadata +fn is_local_file_outdated( + repository: &tough::Repository, + target_name: &TargetName, + local_file: &Path, +) -> Result<(bool, Option)> { + let target = repository + .targets() + .signed + .targets + .get(target_name) + .ok_or_else(|| { + anyhow!( + "Cannot find {} target inside of TUF repository", + target_name.raw() + ) + })?; + + if local_file.exists() { + let data = fs::read_to_string(local_file)?; + let local_checksum = Sha256::digest(data.clone()); + let expected_digest: Vec = target.hashes.sha256.to_vec(); + + if local_checksum.as_slice() == expected_digest.as_slice() { + // local data is not outdated + Ok((false, Some(data))) + } else { + Ok((true, None)) + } + } else { + Ok((true, None)) + } +} + +/// Gets the goods from a read and makes a Vec +fn read_to_end(mut reader: R) -> Result> { + let mut v = Vec::new(); + reader.read_to_end(&mut v)?; + Ok(v) +} + +#[cfg(test)] +mod tests { + use super::super::constants::*; + use super::*; + use std::path::PathBuf; + use tempdir::TempDir; + + /// Returns the path to our test data directory + fn test_data() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests") + .join("data") + } + + fn local_tuf_repo() -> Result { + let metadata_base_path = test_data().join("repository"); + let targets_base_path = metadata_base_path.join("targets"); + + let metadata_base_url = format!( + "file://{}", + metadata_base_path + .to_str() + .ok_or_else(|| anyhow!("Cannot convert metadata_base_path into a str"))? + ); + let metadata_base_url = url::Url::parse(&metadata_base_url)?; + + let target_base_url = format!( + "file://{}", + targets_base_path + .to_str() + .ok_or_else(|| anyhow!("Cannot convert targets_base_path into a str"))? + ); + let target_base_url = url::Url::parse(&target_base_url)?; + + // It's fine to ignore timestamp.json expiration inside of test env + RepositoryLoader::new(SIGSTORE_ROOT.as_bytes(), metadata_base_url, target_base_url) + .expiration_enforcement(tough::ExpirationEnforcement::Unsafe) + .load() + .map_err(|e| anyhow!("Cannot create repository: {}", e)) + } + + #[test] + fn get_files_without_using_local_cache() { + let repository = local_tuf_repo().expect("Local TUF repo should not fail"); + let helper = RepositoryHelper { + repository, + checkout_dir: None, + }; + + let actual = helper.fulcio_cert().expect("fulcio cert cannot be read"); + let expected = fs::read( + test_data() + .join("repository") + .join("targets") + .join("fulcio.crt.pem"), + ) + .expect("cannot read fulcio cert from test data"); + + assert_eq!( + actual, expected, + "The fulcio cert read from the TUF repository is not what was expected" + ); + + let actual = helper.rekor_pub_key().expect("rekor key cannot be read"); + let expected = fs::read( + test_data() + .join("repository") + .join("targets") + .join("rekor.pub"), + ) + .expect("cannot read rekor key from test data"); + + assert_eq!( + actual, expected, + "The rekor key read from the TUF repository is not what was expected" + ); + } + + #[test] + fn download_files_to_local_cache() { + let cache_dir = + TempDir::new("sigstore-test-tuf-cache").expect("Cannot create temp cache dir"); + + let repository = local_tuf_repo().expect("Local TUF repo should not fail"); + let helper = RepositoryHelper { + repository, + checkout_dir: Some(cache_dir.path().to_path_buf()), + }; + + let expected = helper.fulcio_cert().expect("fulcio cert cannot be read"); + let actual = fs::read(cache_dir.path().join("fulcio.crt.pem")) + .expect("cannot read fulcio cert from test data"); + + assert_eq!( + actual, expected, + "The fulcio cert read from the cache dir is not what was expected" + ); + + let expected = helper.rekor_pub_key().expect("rekor key cannot be read"); + let actual = fs::read(cache_dir.path().join("rekor.pub")) + .expect("cannot read rekor key from cache dir"); + + assert_eq!( + actual, expected, + "The rekor key read from the cache dir is not what was expected" + ); + } + + #[test] + fn update_local_cache() { + let cache_dir = + TempDir::new("sigstore-test-tuf-cache").expect("Cannot create temp cache dir"); + + // put some outdated files inside of the cache + fs::write( + cache_dir.path().join(SIGSTORE_FULCIO_CERT_TARGET), + b"fake fulcio", + ) + .expect("Cannot write file to cache dir"); + fs::write( + cache_dir.path().join(SIGSTORE_REKOR_PUB_KEY_TARGET), + b"fake rekor", + ) + .expect("Cannot write file to cache dir"); + + let repository = local_tuf_repo().expect("Local TUF repo should not fail"); + let helper = RepositoryHelper { + repository, + checkout_dir: Some(cache_dir.path().to_path_buf()), + }; + + let expected = helper.fulcio_cert().expect("fulcio cert cannot be read"); + let actual = fs::read(cache_dir.path().join("fulcio.crt.pem")) + .expect("cannot read fulcio cert from test data"); + + assert_eq!( + actual, expected, + "The fulcio cert read from the cache dir is not what was expected" + ); + + let expected = helper.rekor_pub_key().expect("rekor key cannot be read"); + let actual = fs::read(cache_dir.path().join("rekor.pub")) + .expect("cannot read rekor key from cache dir"); + + assert_eq!( + actual, expected, + "The rekor key read from the cache dir is not what was expected" + ); + } +} diff --git a/tests/data/repository/1.root.json b/tests/data/repository/1.root.json new file mode 100644 index 0000000000..dcc71f963a --- /dev/null +++ b/tests/data/repository/1.root.json @@ -0,0 +1,130 @@ +{ + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "30450221008a35d51da0f845301a5eac98ad0df00a934f59b709c1eaf81c86be734d9356f80220742942325599749800f52675f6efe124345980a2a636c0dc76f9caf9fc3123b0" + }, + { + "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "sig": "3045022100ef9157ece2a09baec1eab80adfc00b04da20b1f9a0d1b47c5dabc4506719ef2c022074f72acd57398e4ddc8c2a5040df902961e9615dca48f3fbe38cbb506e500066" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "30450220420fdc9a09cd069b8b15fd8db9cedf7d0dee75871bd1cfee77c926d4120a770002210097553b5ad0d6b4a13902ed37509638bb63a9009f78230cd56c802909ffbfead7" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304502202aaf32e66f90752f658672b085ecfe45cc1ad31ee6cf5c9ad05f3267685f8d88022100b5df02acdaa371123db9d7a42219553fe079b230b168833e951be7ee56ded347" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "304402205d420c7d05c58980c1c9f7d221f53b5334aae27a447d2a91c2ceddd685269749022039ec83e51f8e1779d7f0142dfa4a5bbecfe327fc0b91b7416090fea2416fd53a" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2021-12-18T13:28:12.99008-06:00", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + } + }, + "spec_version": "1.0", + "version": 1 + } +} \ No newline at end of file diff --git a/tests/data/repository/2.root.json b/tests/data/repository/2.root.json new file mode 100644 index 0000000000..386ebe62c1 --- /dev/null +++ b/tests/data/repository/2.root.json @@ -0,0 +1,144 @@ +{ + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3046022100d3ea59490b253beae0926c6fa63f54336dea1ed700555be9f27ff55cd347639c0221009157d1ba012cead81948a4ab777d355451d57f5c4a2d333fc68d2e3f358093c2" + }, + { + "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "sig": "304502206eaef40564403ce572c6d062e0c9b0aab5e0223576133e081e1b495e8deb9efd02210080fd6f3464d759601b4afec596bbd5952f3a224cd06ed1cdfc3c399118752ba2" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "304502207baace02f56d8e6069f10b6ff098a26e7f53a7f9324ad62cffa0557bdeb9036c022100fb3032baaa090d0040c3f2fd872571c84479309b773208601d65948df87a9720" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304402205180c01905505dd88acd7a2dad979dd75c979b3722513a7bdedac88c6ae8dbeb022056d1ddf7a192f0b1c2c90ff487de2fb3ec9f0c03f66ea937c78d3b6a493504ca" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "3046022100c8806d4647c514d80fd8f707d3369444c4fd1d0812a2d25f828e564c99790e3f022100bb51f12e862ef17a7d3da2ac103bebc5c7e792237006c4cafacd76267b249c2f" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2022-05-11T19:09:02.663975009Z", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d" + ], + "threshold": 1 + } + }, + "spec_version": "1.0", + "version": 2 + } +} \ No newline at end of file diff --git a/tests/data/repository/rekor.json b/tests/data/repository/rekor.json new file mode 100644 index 0000000000..f86930d537 --- /dev/null +++ b/tests/data/repository/rekor.json @@ -0,0 +1,23 @@ +{ + "signatures": [ + { + "keyid": "ae0c689c6347ada7359df48934991f4e013193d6ddf3482a5ffb293f74f3b217", + "sig": "3045022076eadd73f6664bac5cc91f12d3a7ddcdd53f9bde661f147651196ff66e7235d1022100f7b3143792405f9e8a75331a05d4128bdf083de302801e99c3d027919a4b03da" + } + ], + "signed": { + "_type": "targets", + "expires": "2022-05-11T19:10:11Z", + "spec_version": "1.0", + "targets": { + "rekor.0.pub": { + "hashes": { + "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd", + "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35" + }, + "length": 178 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tests/data/repository/root.json b/tests/data/repository/root.json new file mode 100644 index 0000000000..386ebe62c1 --- /dev/null +++ b/tests/data/repository/root.json @@ -0,0 +1,144 @@ +{ + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3046022100d3ea59490b253beae0926c6fa63f54336dea1ed700555be9f27ff55cd347639c0221009157d1ba012cead81948a4ab777d355451d57f5c4a2d333fc68d2e3f358093c2" + }, + { + "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "sig": "304502206eaef40564403ce572c6d062e0c9b0aab5e0223576133e081e1b495e8deb9efd02210080fd6f3464d759601b4afec596bbd5952f3a224cd06ed1cdfc3c399118752ba2" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "304502207baace02f56d8e6069f10b6ff098a26e7f53a7f9324ad62cffa0557bdeb9036c022100fb3032baaa090d0040c3f2fd872571c84479309b773208601d65948df87a9720" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304402205180c01905505dd88acd7a2dad979dd75c979b3722513a7bdedac88c6ae8dbeb022056d1ddf7a192f0b1c2c90ff487de2fb3ec9f0c03f66ea937c78d3b6a493504ca" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "3046022100c8806d4647c514d80fd8f707d3369444c4fd1d0812a2d25f828e564c99790e3f022100bb51f12e862ef17a7d3da2ac103bebc5c7e792237006c4cafacd76267b249c2f" + } + ], + "signed": { + "_type": "root", + "consistent_snapshot": false, + "expires": "2022-05-11T19:09:02.663975009Z", + "keys": { + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cbc5cab2684160323c25cd06c3307178a6b1d1c9b949328453ae473c5ba7527e35b13f298b41633382241f3fd8526c262d43b45adee5c618fa0642c82b8a9803" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04fa1a3e42f2300cd3c5487a61509348feb1e936920fef2f83b7cd5dbe7ba045f538725ab8f18a666e6233edb7e0db8766c8dc336633449c5e1bbe0c182b02df0b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04a71aacd835dc170ba6db3fa33a1a33dee751d4f8b0217b805b9bd3242921ee93672fdcfd840576c5bb0dc0ed815edf394c1ee48c2b5e02485e59bfc512f3adc7" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04117b33dd265715bf23315e368faa499728db8d1f0a377070a1c7b1aba2cc21be6ab1628e42f2cdd7a35479f2dce07b303a8ba646c55569a8d2a504ba7e86e447" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "04cc1cd53a61c23e88cc54b488dfae168a257c34fac3e88811c55962b24cffbfecb724447999c54670e365883716302e49da57c79a33cd3e16f81fbc66f0bcdf48" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "048a78a44ac01099890d787e5e62afc29c8ccb69a70ec6549a6b04033b0a8acbfb42ab1ab9c713d225cdb52b858886cf46c8e90a7f3b9e6371882f370c259e1c5b" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "044c7793ab74b9ddd713054e587b8d9c75c5f6025633d0fef7ca855ed5b8d5a474b23598fe33eb4a63630d526f74d4bdaec8adcb51993ed65652d651d7c49203eb" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": { + "root": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "snapshot": { + "keyids": [ + "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451" + ], + "threshold": 1 + }, + "targets": { + "keyids": [ + "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209" + ], + "threshold": 3 + }, + "timestamp": { + "keyids": [ + "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d" + ], + "threshold": 1 + } + }, + "spec_version": "1.0", + "version": 2 + } +} \ No newline at end of file diff --git a/tests/data/repository/snapshot.json b/tests/data/repository/snapshot.json new file mode 100644 index 0000000000..61636531c6 --- /dev/null +++ b/tests/data/repository/snapshot.json @@ -0,0 +1,48 @@ +{ + "signatures": [ + { + "keyid": "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451", + "sig": "3046022100f59f6f92d8c61519afd0de0642ff45419ac960954cf412549874c247c6ae509902210085da85c9df818c3072c0b7744b75e92d2ee521402d4bac77c985b8fc6d138e41" + } + ], + "signed": { + "_type": "snapshot", + "expires": "2022-01-05T00:40:06Z", + "meta": { + "rekor.json": { + "hashes": { + "sha256": "a7412a87f8d7b330e0380b19a4a76c00357c39a1aa7f56fd87445d4e12faafe4", + "sha512": "720cb3c42bac50c5bc3cb7076e730301ef29f1893ea52e25f9393fc05851c7a531638c42d9fc992969805982a2bf51d676e33d28a7382ea589b5a9f87474c63f" + }, + "length": 697, + "version": 1 + }, + "root.json": { + "hashes": { + "sha256": "f5ad897c9414cca99629f400ac3585e41bd8ebb44c5af07fb08dd636a9eced9c", + "sha512": "7445ddfdd338ef786c324fc3d68f75be28cb95b7fb581d2a383e3e5dde18aa17029a5636ec0a22e9631931bbcb34057788311718ea41e21e7cdd3c0de13ede42" + }, + "length": 5297, + "version": 2 + }, + "staging.json": { + "hashes": { + "sha256": "c7f32379c2a76f0ec0af84e86794a8f4fe285e44fb62f336d598810dccdc7343", + "sha512": "5462cb15fe5248a12cc12387a732ad43caf42391361f36113ea3d4b7e5e193cdf39fbe91c309c0691134377cb83afeba50cf6d711537d8280ce16ce9cd8752ba" + }, + "length": 399, + "version": 1 + }, + "targets.json": { + "hashes": { + "sha256": "18d10c07c8d6bd7484772b02dcc988d0abf8a0fa379d5893a502410590c17fe6", + "sha512": "c2ba2a84820288997c8fae264776df7b262dde97c4f9e0320ad354879ce5afabd1d43494734fecffd23253442a14cfe217787de8b65cf7fd1f03130b72a0767c" + }, + "length": 4167, + "version": 2 + } + }, + "spec_version": "1.0", + "version": 6 + } +} \ No newline at end of file diff --git a/tests/data/repository/staging.json b/tests/data/repository/staging.json new file mode 100644 index 0000000000..084010de75 --- /dev/null +++ b/tests/data/repository/staging.json @@ -0,0 +1,15 @@ +{ + "signatures": [ + { + "keyid": "b811bd53f2d7adcf5d93e6bb4a8ed2e0ca0f83d454a3e51f105c8e8376bc80d4", + "sig": "304502204486f7b23eadb69df87776ac7a4938ac75a8a2b2e93c84c05d962373837ea91c022100aaeb0fa587430f49618711bb4bd0c1092637c22c223d03c0f1b5a09baea0ed9f" + } + ], + "signed": { + "_type": "targets", + "expires": "2022-02-11T20:10:16Z", + "spec_version": "1.0", + "targets": {}, + "version": 1 + } +} \ No newline at end of file diff --git a/tests/data/repository/targets.json b/tests/data/repository/targets.json new file mode 100644 index 0000000000..b26926a438 --- /dev/null +++ b/tests/data/repository/targets.json @@ -0,0 +1,117 @@ +{ + "signatures": [ + { + "keyid": "2f64fb5eac0cf94dd39bb45308b98920055e9a0d8e012a7220787834c60aef97", + "sig": "3046022100cc1b2ed390e75a112c0fdd6bcbd8bb775300a410f5737ae39996b1858753c8e4022100b591f73370e9378914fb2fab837f700661abd1a74c680f139f6164ec12cb538f" + }, + { + "keyid": "bdde902f5ec668179ff5ca0dabf7657109287d690bf97e230c21d65f99155c62", + "sig": "3045022100bc6c45a125e45507339af96aa63983e847565c769f20d7d71bcd2deb7bd36ea902202bf6bd3b76d434c318287899e53f64b4dc178eb0ba403080f1c4fba88a2177ca" + }, + { + "keyid": "eaf22372f417dd618a46f6c627dbc276e9fd30a004fc94f9be946e73f8bd090b", + "sig": "304502210085d5bc8a158d31536b4e76cddceef25185c7abbe9091b84f5f2b0d615d9b4ee90220136a36fed2d5986c2519b7d165556f20dfe41fddececda48dffa8dec5258cb95" + }, + { + "keyid": "f40f32044071a9365505da3d1e3be6561f6f22d0e60cf51df783999f6c3429cb", + "sig": "304402202fe73a61dfe05b4202bc50f66e52bba3d3475134434dab9576735caed659b03c0220449755a87f4dab9961566f10477204637b2415f87e162b58a23b13327dec53e3" + }, + { + "keyid": "f505595165a177a41750a8e864ed1719b1edfccd5a426fd2c0ffda33ce7ff209", + "sig": "304602210091f453ef75c5178299175734355a65a2fc2d0ee137410f46ba8439d99037fc08022100fc800d15f0b751fa225a77542928f4264835c013054a5c409c674e2ea5a70384" + } + ], + "signed": { + "_type": "targets", + "delegations": { + "keys": { + "ae0c689c6347ada7359df48934991f4e013193d6ddf3482a5ffb293f74f3b217": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "043463588ae9df33a419d1099761245af52aaf7e638b2047bc0f739a62de9808c50a21ea8a1a273799f857f31a1bcb66e6661dd9d5ac7ac3ca260b0b8130c3fed8" + }, + "scheme": "ecdsa-sha2-nistp256" + }, + "b811bd53f2d7adcf5d93e6bb4a8ed2e0ca0f83d454a3e51f105c8e8376bc80d4": { + "keyid_hash_algorithms": [ + "sha256", + "sha512" + ], + "keytype": "ecdsa-sha2-nistp256", + "keyval": { + "public": "041b4b13a6e7110292d284c0dbfc3962a12d2a779a800c99aff59c6afe779296943c75d84aa5bad0be28e4061cf93e0cd3d372d9b2f75ea9f29b907cbccd82006f" + }, + "scheme": "ecdsa-sha2-nistp256" + } + }, + "roles": [ + { + "keyids": [ + "ae0c689c6347ada7359df48934991f4e013193d6ddf3482a5ffb293f74f3b217" + ], + "name": "rekor", + "paths": [ + "rekor.*.pub" + ], + "terminating": true, + "threshold": 1 + }, + { + "keyids": [ + "b811bd53f2d7adcf5d93e6bb4a8ed2e0ca0f83d454a3e51f105c8e8376bc80d4" + ], + "name": "staging", + "paths": [ + "*" + ], + "terminating": false, + "threshold": 1 + } + ] + }, + "expires": "2022-05-11T19:10:16Z", + "spec_version": "1.0", + "targets": { + "artifact.pub": { + "hashes": { + "sha256": "59ebf97a9850aecec4bc39c1f5c1dc46e6490a6b5fd2a6cacdcac0c3a6fc4cbf", + "sha512": "308fd1d1d95d7f80aa33b837795251cc3e886792982275e062409e13e4e236ffc34d676682aa96fdc751414de99c864bf132dde71581fa651c6343905e3bf988" + }, + "length": 177 + }, + "ctfe.pub": { + "hashes": { + "sha256": "7fcb94a5d0ed541260473b990b99a6c39864c1fb16f3f3e594a5a3cebbfe138a", + "sha512": "4b20747d1afe2544238ad38cc0cc3010921b177d60ac743767e0ef675b915489bd01a36606c0ff83c06448622d7160f0d866c83d20f0c0f44653dcc3f9aa0bd4" + }, + "length": 177 + }, + "fulcio.crt.pem": { + "hashes": { + "sha256": "f360c53b2e13495a628b9b8096455badcb6d375b185c4816d95a5d746ff29908", + "sha512": "0713252a7fd17f7f3ab12f88a64accf2eb14b8ad40ca711d7fe8b4ecba3b24db9e9dffadb997b196d3867b8f9ff217faf930d80e4dab4e235c7fc3f07be69224" + }, + "length": 744 + }, + "fulcio_v1.crt.pem": { + "hashes": { + "sha256": "f989aa23def87c549404eadba767768d2a3c8d6d30a8b793f9f518a8eafd2cf5", + "sha512": "f2e33a6dc208cee1f51d33bbea675ab0f0ced269617497985f9a0680689ee7073e4b6f8fef64c91bda590d30c129b3070dddce824c05bc165ac9802f0705cab6" + }, + "length": 740 + }, + "rekor.pub": { + "hashes": { + "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd", + "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35" + }, + "length": 178 + } + }, + "version": 2 + } +} \ No newline at end of file diff --git a/tests/data/repository/targets/artifact.pub b/tests/data/repository/targets/artifact.pub new file mode 100644 index 0000000000..d6e745bdd0 --- /dev/null +++ b/tests/data/repository/targets/artifact.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEhyQCx0E9wQWSFI9ULGwy3BuRklnt +IqozONbbdbqz11hlRJy9c7SG+hdcFl9jE9uE/dwtuwU2MqU9T/cN0YkWww== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/tests/data/repository/targets/ctfe.pub b/tests/data/repository/targets/ctfe.pub new file mode 100644 index 0000000000..1bb1488c99 --- /dev/null +++ b/tests/data/repository/targets/ctfe.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbfwR+RJudXscgRBRpKX1XFDy3Pyu +dDxz/SfnRi1fT8ekpfBd2O1uoz7jr3Z8nKzxA69EUQ+eFCFI3zeubPWU7w== +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/tests/data/repository/targets/fulcio.crt.pem b/tests/data/repository/targets/fulcio.crt.pem new file mode 100644 index 0000000000..6a06ff300b --- /dev/null +++ b/tests/data/repository/targets/fulcio.crt.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq +MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx +MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu +ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy +A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas +taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm +MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE +FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u +Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx +Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup +Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/data/repository/targets/fulcio_v1.crt.pem b/tests/data/repository/targets/fulcio_v1.crt.pem new file mode 100644 index 0000000000..3afc46bb6e --- /dev/null +++ b/tests/data/repository/targets/fulcio_v1.crt.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB9zCCAXygAwIBAgIUALZNAPFdxHPwjeDloDwyYChAO/4wCgYIKoZIzj0EAwMw +KjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTAeFw0y +MTEwMDcxMzU2NTlaFw0zMTEwMDUxMzU2NThaMCoxFTATBgNVBAoTDHNpZ3N0b3Jl +LmRldjERMA8GA1UEAxMIc2lnc3RvcmUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAT7 +XeFT4rb3PQGwS4IajtLk3/OlnpgangaBclYpsYBr5i+4ynB07ceb3LP0OIOZdxex +X69c5iVuyJRQ+Hz05yi+UF3uBWAlHpiS5sh0+H2GHE7SXrk1EC5m1Tr19L9gg92j +YzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRY +wB5fkUWlZql6zJChkyLQKsXF+jAfBgNVHSMEGDAWgBRYwB5fkUWlZql6zJChkyLQ +KsXF+jAKBggqhkjOPQQDAwNpADBmAjEAj1nHeXZp+13NWBNa+EDsDP8G1WWg1tCM +WP/WHPqpaVo0jhsweNFZgSs0eE7wYI4qAjEA2WB9ot98sIkoF3vZYdd3/VtWB5b9 +TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ +-----END CERTIFICATE----- \ No newline at end of file diff --git a/tests/data/repository/targets/rekor.0.pub b/tests/data/repository/targets/rekor.0.pub new file mode 100644 index 0000000000..050ef60149 --- /dev/null +++ b/tests/data/repository/targets/rekor.0.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr +kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw== +-----END PUBLIC KEY----- diff --git a/tests/data/repository/targets/rekor.json b/tests/data/repository/targets/rekor.json new file mode 100644 index 0000000000..f86930d537 --- /dev/null +++ b/tests/data/repository/targets/rekor.json @@ -0,0 +1,23 @@ +{ + "signatures": [ + { + "keyid": "ae0c689c6347ada7359df48934991f4e013193d6ddf3482a5ffb293f74f3b217", + "sig": "3045022076eadd73f6664bac5cc91f12d3a7ddcdd53f9bde661f147651196ff66e7235d1022100f7b3143792405f9e8a75331a05d4128bdf083de302801e99c3d027919a4b03da" + } + ], + "signed": { + "_type": "targets", + "expires": "2022-05-11T19:10:11Z", + "spec_version": "1.0", + "targets": { + "rekor.0.pub": { + "hashes": { + "sha256": "dce5ef715502ec9f3cdfd11f8cc384b31a6141023d3e7595e9908a81cb6241bd", + "sha512": "0ae7705e02db33e814329746a4a0e5603c5bdcd91c96d072158d71011a2695788866565a2fec0fe363eb72cbcaeda39e54c5fe8d416daf9f3101fdba4217ef35" + }, + "length": 178 + } + }, + "version": 1 + } +} \ No newline at end of file diff --git a/tests/data/repository/targets/rekor.pub b/tests/data/repository/targets/rekor.pub new file mode 100644 index 0000000000..050ef60149 --- /dev/null +++ b/tests/data/repository/targets/rekor.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr +kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw== +-----END PUBLIC KEY----- diff --git a/tests/data/repository/timestamp.json b/tests/data/repository/timestamp.json new file mode 100644 index 0000000000..8cb4f094b7 --- /dev/null +++ b/tests/data/repository/timestamp.json @@ -0,0 +1,24 @@ +{ + "signatures": [ + { + "keyid": "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d", + "sig": "30440220590dc4d9eb4e3b2745315348c1ea5481f29f981dfd6c2d72bde13256a25e0caf02205704352c828451bf1e41bba154db9ecb4e901b4bc47d721a91fabfb84a48c61f" + } + ], + "signed": { + "_type": "timestamp", + "expires": "2022-01-05T00:40:07Z", + "meta": { + "snapshot.json": { + "hashes": { + "sha256": "e202c20580ac4edc7a52ad2bcbe97c5af557c04463f10f2d9a28e2624e0c8edf", + "sha512": "f0b9f17797fe6d89a745f8fc9a39a073823bc04400307711eebe3b00dfe418e4d1d4419697eee29445c9cd5e03c3e24532d4fb03824d7555ecc0de54bd73ffd1" + }, + "length": 1658, + "version": 6 + } + }, + "spec_version": "1.0", + "version": 6 + } +} \ No newline at end of file