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

cosign/tuf: use trustroot #305

Merged
merged 3 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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"] }
jleightcap marked this conversation as resolved.
Show resolved Hide resolved
crypto_secretbox = "0.1.1"
zeroize = "1.5.7"
rustls-webpki = { version = "0.102.0-alpha.4", features = ["alloc"] }
jleightcap marked this conversation as resolved.
Show resolved Hide resolved
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
jleightcap marked this conversation as resolved.
Show resolved Hide resolved
#[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
Loading