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 2 commits
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
74 changes: 24 additions & 50 deletions src/cosign/client_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,30 @@
// 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.
///
/// ## Rekor integration
///
/// Rekor integration can be enabled by specifying Rekor's public key.
/// This can be provided via the [`ClientBuilder::with_rekor_pub_key`] method.
/// This can be provided via a [`crate::tuf::FakeRepository`].
///
/// > 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.
///
/// ## 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 a [`crate::tuf::FakeRepository`].
///
/// > 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
37 changes: 18 additions & 19 deletions src/cosign/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,13 +102,14 @@ pub trait CosignCapabilities {
/// must be satisfied:
///
/// * The [`sigstore::cosign::Client`](crate::cosign::client::Client) must
/// have been created with Rekor integration enabled (see
/// [`sigstore::cosign::ClientBuilder::with_rekor_pub_key`](crate::cosign::ClientBuilder::with_rekor_pub_key))
/// have been created with Rekor integration enabled (see [`crate::tuf::FakeRepository`])
/// * The [`sigstore::cosign::Client`](crate::cosign::client::Client) must
/// have been created with Fulcio integration enabled (see
/// [`sigstore::cosign::ClientBuilder::with_fulcio_certs`](crate::cosign::ClientBuilder::with_fulcio_certs))
/// have been created with Fulcio integration enabled (see [`crate::tuf::FakeRepository])
/// * The layer must include a bundle produced by Rekor
///
/// > 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.
///
/// When the embedded certificate cannot be verified, [`SignatureLayer::certificate_signature`]
/// is going to be `None`.
///
Expand Down Expand Up @@ -199,7 +200,7 @@ pub trait CosignCapabilities {
/// verification.
///
/// Returns a `Result` with either `Ok()` for passed verification or
/// [`SigstoreVerifyConstraintsError`](crate::errors::SigstoreVerifyConstraintsError),
/// [`SigstoreVerifyConstraintsError`]
/// which contains a vector of references to unsatisfied constraints.
///
/// See the documentation of the [`cosign::verification_constraint`](crate::cosign::verification_constraint) module for more
Expand Down Expand Up @@ -249,7 +250,7 @@ where
/// passes applying constraints process.
///
/// Returns a `Result` with either `Ok()` for success or
/// [`SigstoreApplicationConstraintsError`](crate::errors::SigstoreApplicationConstraintsError),
/// [`SigstoreApplicationConstraintsError`]
/// which contains a vector of references to unapplied constraints.
///
/// See the documentation of the [`cosign::sign_constraint`](crate::cosign::sign_constraint) module for more
Expand Down Expand Up @@ -282,6 +283,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 +337,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 +644,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
Loading
Loading