From bfb71f059e6e739c4619a4f222dcb37a9f973eaf Mon Sep 17 00:00:00 2001 From: Flavio Castelli Date: Mon, 7 Mar 2022 09:39:06 +0100 Subject: [PATCH] feat(TUF)!: handle multiple Fulcio certificates Extend the TUF repository helper to handle multiple Fulcio certificates. Signed-off-by: Flavio Castelli --- src/tuf/constants.rs | 24 +++++- src/tuf/mod.rs | 16 ++-- src/tuf/repository_helper.rs | 138 ++++++++++++++++++++++++++--------- 3 files changed, 134 insertions(+), 44 deletions(-) diff --git a/src/tuf/constants.rs b/src/tuf/constants.rs index 74ad6a32d3..74df990f53 100644 --- a/src/tuf/constants.rs +++ b/src/tuf/constants.rs @@ -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#"{ @@ -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); + } +} diff --git a/src/tuf/mod.rs b/src/tuf/mod.rs index ff2879e723..56de97bfa3 100644 --- a/src/tuf/mod.rs +++ b/src/tuf/mod.rs @@ -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"); //! ``` @@ -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, + fulcio_certs: Vec, } impl SigstoreRepository { @@ -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 /// @@ -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| { @@ -148,7 +148,7 @@ impl SigstoreRepository { Ok(SigstoreRepository { rekor_pub_key, - fulcio_cert, + fulcio_certs, }) } @@ -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 } } diff --git a/src/tuf/repository_helper.rs b/src/tuf/repository_helper.rs index 79251ca4cf..d5e36a3a8b 100644 --- a/src/tuf/repository_helper.rs +++ b/src/tuf/repository_helper.rs @@ -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 { @@ -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> { - 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> { + 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 { + 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 @@ -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 = + 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, @@ -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 = + 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, @@ -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", @@ -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 = + 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");