Skip to content

Commit

Permalink
cosign/tuf: init
Browse files Browse the repository at this point in the history
Signed-off-by: Jack Leightcap <[email protected]>
Co-authored-by: Andrew Pan <[email protected]>
  • Loading branch information
jleightcap and tnytown committed Nov 9, 2023
1 parent 7b61297 commit 8a269a3
Show file tree
Hide file tree
Showing 17 changed files with 1,048 additions and 747 deletions.
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -110,16 +110,20 @@ rsa = "0.9.2"
scrypt = "0.11.0"
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
serde_with = { version = "3.4.0", features = ["base64"] }
sha2 = { version = "0.10.6", features = ["oid"] }
signature = { version = "2.0" }
thiserror = "1.0.30"
tokio = { version = "1.17.0", features = ["rt"] }
tough = { version = "0.14", features = ["http"], optional = true }
tracing = "0.1.31"
url = "2.2.2"
x509-cert = { version = "0.2.2", features = ["pem", "std"] }
x509-cert = { version = "0.2.2", features = ["builder", "pem", "std"] }
crypto_secretbox = "0.1.1"
zeroize = "1.5.7"
rustls-webpki = { version = "0.102.0-alpha.4", features = ["alloc"] }
rustls-pki-types = { version = "0.2.1", features = ["std"] }
serde_repr = "0.1.16"

[dev-dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
Expand Down
42 changes: 14 additions & 28 deletions examples/cosign/verify/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ struct Cli {

async fn run_app(
cli: &Cli,
frd: &FulcioAndRekorData,
frd: &dyn sigstore::tuf::Repository,
) -> anyhow::Result<(Vec<SignatureLayer>, VerificationConstraintVec)> {
// Note well: this a limitation deliberately introduced by this example.
if cli.cert_email.is_some() && cli.cert_url.is_some() {
Expand All @@ -133,20 +133,13 @@ async fn run_app(

let mut client_builder =
sigstore::cosign::ClientBuilder::default().with_oci_client_config(oci_client_config);

if let Some(key) = frd.rekor_pub_key.as_ref() {
client_builder = client_builder.with_rekor_pub_key(key);
}
client_builder = client_builder.with_trust_repository(frd)?;

let cert_chain: Option<Vec<sigstore::registry::Certificate>> = match cli.cert_chain.as_ref() {
None => None,
Some(cert_chain_path) => Some(parse_cert_bundle(cert_chain_path)?),
};

if !frd.fulcio_certs.is_empty() {
client_builder = client_builder.with_fulcio_certs(&frd.fulcio_certs);
}

if cli.enable_registry_caching {
client_builder = client_builder.enable_registry_caching();
}
Expand Down Expand Up @@ -194,7 +187,7 @@ async fn run_app(
}
if let Some(path_to_cert) = cli.cert.as_ref() {
let cert = fs::read(path_to_cert).map_err(|e| anyhow!("Cannot read cert: {:?}", e))?;
let require_rekor_bundle = if frd.rekor_pub_key.is_some() {
let require_rekor_bundle = if !frd.rekor_keys()?.is_empty() {
true
} else {
warn!("certificate based verification is weaker when Rekor integration is disabled");
Expand Down Expand Up @@ -235,31 +228,22 @@ async fn run_app(
Ok((trusted_layers, verification_constraints))
}

#[derive(Default)]
struct FulcioAndRekorData {
pub rekor_pub_key: Option<String>,
pub fulcio_certs: Vec<sigstore::registry::Certificate>,
}

async fn fulcio_and_rekor_data(cli: &Cli) -> anyhow::Result<FulcioAndRekorData> {
let mut data = FulcioAndRekorData::default();

async fn fulcio_and_rekor_data(cli: &Cli) -> anyhow::Result<Box<dyn sigstore::tuf::Repository>> {
if cli.use_sigstore_tuf_data {
let repo: sigstore::errors::Result<SigstoreRepository> = spawn_blocking(|| {
info!("Downloading data from Sigstore TUF repository");
sigstore::tuf::SigstoreRepository::fetch(None)
SigstoreRepository::new(None)?.prefetch()
})
.await
.map_err(|e| anyhow!("Error spawning blocking task inside of tokio: {}", e))?;

let repo: SigstoreRepository = repo?;
data.fulcio_certs = repo.fulcio_certs().into();
data.rekor_pub_key = Some(repo.rekor_pub_key().to_string());
return Ok(Box::new(repo?));
};

let mut data = sigstore::tuf::FakeRepository::default();
if let Some(path) = cli.rekor_pub_key.as_ref() {
data.rekor_pub_key = Some(
fs::read_to_string(path)
data.rekor_key = Some(
fs::read(path)
.map_err(|e| anyhow!("Error reading rekor public key from disk: {}", e))?,
);
}
Expand All @@ -272,10 +256,12 @@ async fn fulcio_and_rekor_data(cli: &Cli) -> anyhow::Result<FulcioAndRekorData>
encoding: sigstore::registry::CertificateEncoding::Pem,
data: cert_data,
};
data.fulcio_certs.push(certificate);
data.fulcio_certs
.get_or_insert(Vec::new())
.push(certificate.try_into()?);
}

Ok(data)
Ok(Box::new(data))
}

#[tokio::main]
Expand Down Expand Up @@ -304,7 +290,7 @@ pub async fn main() {
println!("Loop {}/{}", n + 1, cli.loops);
}

match run_app(&cli, &frd).await {
match run_app(&cli, frd.as_ref()).await {
Ok((trusted_layers, verification_constraints)) => {
let filter_result = sigstore::cosign::verify_constraints(
&trusted_layers,
Expand Down
10 changes: 5 additions & 5 deletions src/cosign/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@ pub const CONFIG_DATA: &str = "{}";
/// Cosign Client
///
/// Instances of `Client` can be built via [`sigstore::cosign::ClientBuilder`](crate::cosign::ClientBuilder).
pub struct Client {
pub struct Client<'a> {
pub(crate) registry_client: Box<dyn crate::registry::ClientCapabilities>,
pub(crate) rekor_pub_key: Option<CosignVerificationKey>,
pub(crate) fulcio_cert_pool: Option<CertificatePool>,
pub(crate) fulcio_cert_pool: Option<CertificatePool<'a>>,
}

#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
impl CosignCapabilities for Client {
impl CosignCapabilities for Client<'_> {
async fn triangulate(
&mut self,
image: &OciReference,
Expand Down Expand Up @@ -140,7 +140,7 @@ impl CosignCapabilities for Client {
}
}

impl Client {
impl Client<'_> {
/// Internal helper method used to fetch data from an OCI registry
async fn fetch_manifest_and_layers(
&mut self,
Expand Down Expand Up @@ -177,7 +177,7 @@ mod tests {
use crate::crypto::SigningScheme;
use crate::mock_client::test::MockOciClient;

fn build_test_client(mock_client: MockOciClient) -> Client {
fn build_test_client(mock_client: MockOciClient) -> Client<'static> {
let rekor_pub_key =
CosignVerificationKey::from_pem(REKOR_PUB_KEY.as_bytes(), &SigningScheme::default())
.expect("Cannot create CosignVerificationKey");
Expand Down
72 changes: 23 additions & 49 deletions src/cosign/client_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use rustls_pki_types::CertificateDer;
use tracing::info;

use super::client::Client;
use crate::crypto::SigningScheme;
use crate::crypto::{certificate_pool::CertificatePool, CosignVerificationKey};
use crate::errors::Result;
use crate::registry::{Certificate, ClientConfig};
use crate::registry::ClientConfig;
use crate::tuf::Repository;

/// A builder that generates Client objects.
///
Expand All @@ -34,7 +36,7 @@ use crate::registry::{Certificate, ClientConfig};
/// ## Fulcio integration
///
/// Fulcio integration can be enabled by specifying Fulcio's certificate.
/// This can be provided via the [`ClientBuilder::with_fulcio_cert`] method.
/// This can be provided via the [`ClientBuilder::with_fulcio_certs`] method.
///
/// > Note well: the [`tuf`](crate::tuf) module provides helper structs and methods
/// > to obtain this data from the official TUF repository of the Sigstore project.
Expand All @@ -50,63 +52,35 @@ use crate::registry::{Certificate, ClientConfig};
///
/// Each cached entry will automatically expire after 60 seconds.
#[derive(Default)]
pub struct ClientBuilder {
pub struct ClientBuilder<'a> {
oci_client_config: ClientConfig,
rekor_pub_key: Option<String>,
fulcio_certs: Vec<Certificate>,
rekor_pub_key: Option<&'a [u8]>,
fulcio_certs: Vec<CertificateDer<'a>>,
// repo: Repository
#[cfg(feature = "cached-client")]
enable_registry_caching: bool,
}

impl ClientBuilder {
impl<'a> ClientBuilder<'a> {
/// Enable caching of data returned from remote OCI registries
#[cfg(feature = "cached-client")]
pub fn enable_registry_caching(mut self) -> Self {
self.enable_registry_caching = true;
self
}

/// Specify the public key used by Rekor.
/// Optional - Configures the roots of trust.
///
/// The public key can be obtained by using the helper methods under the
/// [`tuf`](crate::tuf) module.
///
/// `key` is a PEM encoded public key
///
/// When provided, this enables Rekor's integration.
pub fn with_rekor_pub_key(mut self, key: &str) -> Self {
self.rekor_pub_key = Some(key.to_string());
self
}

/// Specify the certificate used by Fulcio. This method can be invoked
/// multiple times to add all the certificates that Fulcio used over the
/// time.
///
/// `cert` is a PEM encoded certificate
///
/// The certificates can be obtained by using the helper methods under the
/// [`tuf`](crate::tuf) module.
///
/// When provided, this enables Fulcio's integration.
pub fn with_fulcio_cert(mut self, cert: &[u8]) -> Self {
let certificate = Certificate {
encoding: crate::registry::CertificateEncoding::Pem,
data: cert.to_owned(),
};
self.fulcio_certs.push(certificate);
self
}
/// Enables Fulcio and Rekor integration with the given trust repository.
/// See [crate::tuf::Repository] for more details on trust repositories.
pub fn with_trust_repository<R: Repository + ?Sized>(mut self, repo: &'a R) -> Result<Self> {
let rekor_keys = repo.rekor_keys()?;
if !rekor_keys.is_empty() {
self.rekor_pub_key = Some(rekor_keys[0]);
}
self.fulcio_certs = repo.fulcio_certs()?;

/// Specify the certificates used by Fulcio.
///
/// The certificates can be obtained by using the helper methods under the
/// [`tuf`](crate::tuf) module.
///
/// When provided, this enables Fulcio's integration.
pub fn with_fulcio_certs(mut self, certs: &[crate::registry::Certificate]) -> Self {
self.fulcio_certs = certs.to_vec();
self
Ok(self)
}

/// Optional - the configuration to be used by the OCI client.
Expand All @@ -118,14 +92,14 @@ impl ClientBuilder {
self
}

pub fn build(self) -> Result<Client> {
pub fn build(self) -> Result<Client<'a>> {
let rekor_pub_key = match self.rekor_pub_key {
None => {
info!("Rekor public key not provided. Rekor integration disabled");
None
}
Some(data) => Some(CosignVerificationKey::from_pem(
data.as_bytes(),
Some(data) => Some(CosignVerificationKey::from_der(
data,
&SigningScheme::default(),
)?),
};
Expand All @@ -134,7 +108,7 @@ impl ClientBuilder {
info!("No Fulcio cert has been provided. Fulcio integration disabled");
None
} else {
let cert_pool = CertificatePool::from_certificates(&self.fulcio_certs)?;
let cert_pool = CertificatePool::from_certificates(self.fulcio_certs, [])?;
Some(cert_pool)
};

Expand Down
24 changes: 11 additions & 13 deletions src/cosign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ where

#[cfg(test)]
mod tests {
use rustls_pki_types::CertificateDer;
use serde_json::json;
use std::collections::HashMap;

Expand Down Expand Up @@ -335,18 +336,15 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
#[cfg(feature = "test-registry")]
const SIGNED_IMAGE: &str = "busybox:1.34";

pub(crate) fn get_fulcio_cert_pool() -> CertificatePool {
let certificates = vec![
crate::registry::Certificate {
encoding: crate::registry::CertificateEncoding::Pem,
data: FULCIO_CRT_1_PEM.as_bytes().to_vec(),
},
crate::registry::Certificate {
encoding: crate::registry::CertificateEncoding::Pem,
data: FULCIO_CRT_2_PEM.as_bytes().to_vec(),
},
];
CertificatePool::from_certificates(&certificates).unwrap()
pub(crate) fn get_fulcio_cert_pool() -> CertificatePool<'static> {
fn pem_to_der<'a>(input: &'a str) -> CertificateDer<'a> {
let pem_cert = pem::parse(input).unwrap();
assert_eq!(pem_cert.tag(), "CERTIFICATE");
CertificateDer::from(pem_cert.into_contents())
}
let certificates = vec![pem_to_der(FULCIO_CRT_1_PEM), pem_to_der(FULCIO_CRT_2_PEM)];

CertificatePool::from_certificates(certificates, []).unwrap()
}

pub(crate) fn get_rekor_public_key() -> CosignVerificationKey {
Expand Down Expand Up @@ -645,7 +643,7 @@ TNMea7Ix/stJ5TfcLLeABLE4BNJOsQ4vnBHJ
}

#[cfg(feature = "test-registry")]
async fn prepare_image_to_be_signed(client: &mut Client, image_ref: &OciReference) {
async fn prepare_image_to_be_signed(client: &mut Client<'_>, image_ref: &OciReference) {
let data = client
.registry_client
.pull(
Expand Down
25 changes: 18 additions & 7 deletions src/cosign/signature_layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,12 @@ impl CertificateSignature {
let integrated_time = trusted_bundle.payload.integrated_time;

// ensure the certificate has been issued by Fulcio
fulcio_cert_pool.verify_pem_cert(cert_pem)?;
fulcio_cert_pool.verify_pem_cert(
cert_pem,
Some(rustls_pki_types::UnixTime::since_unix_epoch(
cert.tbs_certificate.validity.not_before.to_unix_duration(),
)),
)?;

crypto::certificate::is_trusted(&cert, integrated_time)?;

Expand Down Expand Up @@ -899,8 +904,10 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ==

let issued_cert_pem = issued_cert.cert.to_pem()?;

let certs = vec![crate::registry::Certificate::try_from(ca_data.cert).unwrap()];
let cert_pool = CertificatePool::from_certificates(&certs).unwrap();
let certs = vec![crate::registry::Certificate::try_from(ca_data.cert)
.unwrap()
.try_into()?];
let cert_pool = CertificatePool::from_certificates(certs, []).unwrap();

let integrated_time = Utc::now().checked_sub_signed(Duration::minutes(1)).unwrap();
let bundle = Bundle {
Expand Down Expand Up @@ -946,8 +953,10 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ==

let issued_cert_pem = issued_cert.cert.to_pem()?;

let certs = vec![crate::registry::Certificate::try_from(ca_data.cert).unwrap()];
let cert_pool = CertificatePool::from_certificates(&certs).unwrap();
let certs = vec![crate::registry::Certificate::try_from(ca_data.cert)
.unwrap()
.try_into()?];
let cert_pool = CertificatePool::from_certificates(certs, []).unwrap();

let integrated_time = Utc::now().checked_sub_signed(Duration::minutes(1)).unwrap();
let bundle = Bundle {
Expand Down Expand Up @@ -992,8 +1001,10 @@ JsB89BPhZYch0U0hKANx5TY+ncrm0s8bfJxxHoenAEFhwhuXeb4PqIrtoQ==

let issued_cert_pem = issued_cert.cert.to_pem()?;

let certs = vec![crate::registry::Certificate::try_from(ca_data.cert).unwrap()];
let cert_pool = CertificatePool::from_certificates(&certs).unwrap();
let certs = vec![crate::registry::Certificate::try_from(ca_data.cert)
.unwrap()
.try_into()?];
let cert_pool = CertificatePool::from_certificates(certs, []).unwrap();

let integrated_time = Utc::now().checked_sub_signed(Duration::minutes(1)).unwrap();
let bundle = Bundle {
Expand Down
Loading

0 comments on commit 8a269a3

Please sign in to comment.