Skip to content

Commit

Permalink
feat(TUF)!: handle multiple Fulcio certificates
Browse files Browse the repository at this point in the history
Extend the TUF repository helper to handle multiple Fulcio certificates.

Signed-off-by: Flavio Castelli <fcastelli@suse.com>
  • Loading branch information
flavio committed Mar 8, 2022
1 parent 3d12f0b commit bfb71f0
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 44 deletions.
24 changes: 23 additions & 1 deletion src/tuf/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use lazy_static::lazy_static;
use regex::Regex;

lazy_static! {
pub(crate) static ref SIGSTORE_FULCIO_CERT_TARGET_REGEX: Regex =
Regex::new(r#"fulcio(_v\d+)?\.crt\.pem"#).unwrap();
}

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#"{
Expand Down Expand Up @@ -164,3 +171,18 @@ pub(crate) const SIGSTORE_ROOT: &str = r#"{
"version": 2
}
}"#;

#[cfg(test)]
mod test {
use super::*;
use rstest::*;

#[rstest]
#[case("fulcio.crt.pem", true)]
#[case("fulcio_v1.crt.pem", true)]
#[case("fulcio-v2.crt.pem", false)]
#[case("foo.crt.pem", false)]
fn check_fulcio_regex(#[case] input: &str, #[case] matches: bool) {
assert_eq!(SIGSTORE_FULCIO_CERT_TARGET_REGEX.is_match(input), matches);
}
}
16 changes: 8 additions & 8 deletions src/tuf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
//! .expect("Error while building SigstoreRepository");
//! let client = cosign::ClientBuilder::default()
//! .with_rekor_pub_key(repo.rekor_pub_key())
//! .with_fulcio_cert(repo.fulcio_cert())
//! .with_fulcio_certs(repo.fulcio_certs())
//! .build()
//! .expect("Error while building cosign client");
//! ```
Expand All @@ -57,10 +57,10 @@ use repository_helper::RepositoryHelper;

use super::errors::{Result, SigstoreError};

/// Securely fetches Rekor public key and Fulcio certificate from Sigstore's TUF repository
/// Securely fetches Rekor public key and Fulcio certificates from Sigstore's TUF repository
pub struct SigstoreRepository {
rekor_pub_key: String,
fulcio_cert: Vec<u8>,
fulcio_certs: Vec<crate::registry::Certificate>,
}

impl SigstoreRepository {
Expand All @@ -69,7 +69,7 @@ impl SigstoreRepository {
/// ## Parameters
///
/// * `checkout_dir`: path to a local directory where Rekor's public
/// key and Fulcio's certificate can be found
/// key and Fulcio's certificates can be found
///
/// ## Behaviour
///
Expand Down Expand Up @@ -135,7 +135,7 @@ impl SigstoreRepository {
checkout_dir,
)?;

let fulcio_cert = repository_helper.fulcio_cert()?;
let fulcio_certs = repository_helper.fulcio_certs()?;

let rekor_pub_key = repository_helper.rekor_pub_key().map(|data| {
String::from_utf8(data).map_err(|e| {
Expand All @@ -148,7 +148,7 @@ impl SigstoreRepository {

Ok(SigstoreRepository {
rekor_pub_key,
fulcio_cert,
fulcio_certs,
})
}

Expand All @@ -158,7 +158,7 @@ impl SigstoreRepository {
}

/// Fulcio certificate
pub fn fulcio_cert(&self) -> &[u8] {
&self.fulcio_cert
pub fn fulcio_certs(&self) -> &[crate::registry::Certificate] {
&self.fulcio_certs
}
}
138 changes: 103 additions & 35 deletions src/tuf/repository_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use url::Url;

use super::{
super::errors::{Result, SigstoreError},
constants::{SIGSTORE_FULCIO_CERT_TARGET, SIGSTORE_REKOR_PUB_KEY_TARGET},
constants::{SIGSTORE_FULCIO_CERT_TARGET_REGEX, SIGSTORE_REKOR_PUB_KEY_TARGET},
};

pub(crate) struct RepositoryHelper {
Expand Down Expand Up @@ -50,23 +50,46 @@ impl RepositoryHelper {
})
}

/// Fetch Fulcio certificate from the given TUF repository or reuse
/// the local cache is used if its contents are not outdated.
/// Fetch Fulcio certificates from the given TUF repository or reuse
/// the local cache 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<Vec<u8>> {
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));
pub(crate) fn fulcio_certs(&self) -> Result<Vec<crate::registry::Certificate>> {
let fulcio_target_names = self.fulcio_cert_target_names();
let mut certs = vec![];

for fulcio_target_name in &fulcio_target_names {
let local_fulcio_path = self
.checkout_dir
.as_ref()
.map(|d| Path::new(d).join(fulcio_target_name.raw()));

let cert_data = fetch_target_or_reuse_local_cache(
&self.repository,
fulcio_target_name,
local_fulcio_path.as_ref(),
)?;
certs.push(crate::registry::Certificate {
data: cert_data,
encoding: crate::registry::CertificateEncoding::Pem,
});
}
Ok(certs)
}

fetch_target_or_reuse_local_cache(
&self.repository,
&fulcio_target_name,
local_fulcio_path.as_ref(),
)
fn fulcio_cert_target_names(&self) -> Vec<TargetName> {
self.repository
.targets()
.signed
.targets_iter()
.filter_map(|(target_name, _target)| {
if SIGSTORE_FULCIO_CERT_TARGET_REGEX.is_match(target_name.raw()) {
Some(target_name.clone())
} else {
None
}
})
.collect()
}

/// Fetch Rekor public key from the given TUF repository or reuse
Expand Down Expand Up @@ -243,14 +266,26 @@ mod tests {
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");
let mut actual = helper.fulcio_certs().expect("fulcio certs cannot be read");
actual.sort();
let mut expected: Vec<crate::registry::Certificate> =
vec!["fulcio.crt.pem", "fulcio_v1.crt.pem"]
.iter()
.map(|filename| {
let data = fs::read(
test_data()
.join("repository")
.join("targets")
.join(filename),
)
.expect(format!("cannot read {} from test data", filename).as_str());
crate::registry::Certificate {
data,
encoding: crate::registry::CertificateEncoding::Pem,
}
})
.collect();
expected.sort();

assert_eq!(
actual, expected,
Expand Down Expand Up @@ -282,9 +317,26 @@ mod tests {
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");
let mut actual = helper.fulcio_certs().expect("fulcio certs cannot be read");
actual.sort();
let mut expected: Vec<crate::registry::Certificate> =
vec!["fulcio.crt.pem", "fulcio_v1.crt.pem"]
.iter()
.map(|filename| {
let data = fs::read(
test_data()
.join("repository")
.join("targets")
.join(filename),
)
.expect(format!("cannot read {} from test data", filename).as_str());
crate::registry::Certificate {
data,
encoding: crate::registry::CertificateEncoding::Pem,
}
})
.collect();
expected.sort();

assert_eq!(
actual, expected,
Expand All @@ -306,11 +358,10 @@ mod tests {
let cache_dir = TempDir::new().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");
for filename in vec!["fulcio.crt.pem", "fulcio_v1.crt.pem"] {
fs::write(cache_dir.path().join(filename), b"fake fulcio")
.expect("Cannot write file to cache dir");
}
fs::write(
cache_dir.path().join(SIGSTORE_REKOR_PUB_KEY_TARGET),
b"fake rekor",
Expand All @@ -323,13 +374,30 @@ mod tests {
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");
let mut actual = helper.fulcio_certs().expect("fulcio certs cannot be read");
actual.sort();
let mut expected: Vec<crate::registry::Certificate> =
vec!["fulcio.crt.pem", "fulcio_v1.crt.pem"]
.iter()
.map(|filename| {
let data = fs::read(
test_data()
.join("repository")
.join("targets")
.join(filename),
)
.expect(format!("cannot read {} from test data", filename).as_str());
crate::registry::Certificate {
data,
encoding: crate::registry::CertificateEncoding::Pem,
}
})
.collect();
expected.sort();

assert_eq!(
actual, expected,
"The fulcio cert read from the cache dir is not what was expected"
"The fulcio cert read from the TUF repository is not what was expected"
);

let expected = helper.rekor_pub_key().expect("rekor key cannot be read");
Expand Down

0 comments on commit bfb71f0

Please sign in to comment.