Skip to content

Commit

Permalink
[wip] Sigstore bundle support
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Pan <[email protected]>
  • Loading branch information
tnytown authored and jleightcap committed Oct 19, 2023
1 parent 4386eaf commit 07dbbda
Show file tree
Hide file tree
Showing 60 changed files with 3,334 additions and 1,187 deletions.
21 changes: 13 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ readme = "README.md"
repository = "https://github.com/sigstore/sigstore-rs"

[features]
default = ["full-native-tls", "cached-client", "tuf"]
default = ["full-native-tls", "cached-client", "tuf", "sign", "verify"]
wasm = ["getrandom/js"]

full-native-tls = [
Expand Down Expand Up @@ -42,6 +42,9 @@ rekor = ["reqwest"]

tuf = ["tough", "regex"]

sign = []
verify = []

cosign-native-tls = [
"oci-distribution/native-tls",
"cert",
Expand Down Expand Up @@ -72,7 +75,7 @@ async-trait = "0.1.52"
base64 = "0.21.0"
cached = { version = "0.46.0", optional = true, features = ["async"] }
cfg-if = "1.0.0"
chrono = { version = "0.4.27", default-features = false }
chrono = { version = "0.4.27", default-features = false, features = ["serde"] }
const-oid = "0.9.1"
digest = { version = "0.10.3", default-features = false }
ecdsa = { version = "0.16.7", features = ["pkcs8", "digest", "der", "signing"] }
Expand All @@ -88,10 +91,7 @@ openidconnect = { version = "3.0", default-features = false, features = [
p256 = "0.13.2"
p384 = "0.13"
webbrowser = "0.8.4"
pem = "3.0"
picky = { version = "7.0.0-rc.8", default-features = false, features = [
"x509",
] }
pem = { version = "3.0", features = ["serde"] }
pkcs1 = { version = "0.7.5", features = ["std"] }
pkcs8 = { version = "0.10.2", features = [
"pem",
Expand All @@ -112,14 +112,20 @@ serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.79"
sha2 = { version = "0.10.6", features = ["oid"] }
signature = { version = "2.0" }
sigstore_protobuf_specs = "0.1.0-rc.2"
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"
hex = "0.4.3"
json-syntax = { version = "0.9.6", features = ["canonicalize", "serde"] }

[dev-dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
Expand All @@ -133,7 +139,6 @@ serial_test = "2.0.0"
tempfile = "3.3.0"
testcontainers = "0.15"
tracing-subscriber = { version = "0.3.9", features = ["env-filter"] }
hex = "0.4.3"

# cosign example mappings

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)
Ok(sigstore::tuf::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
65 changes: 65 additions & 0 deletions src/bundle/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2023 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Useful types for Sigstore bundles.

use std::fmt::Display;

pub use sigstore_protobuf_specs::Bundle;

macro_rules! required {

Check warning on line 21 in src/bundle/mod.rs

View workflow job for this annotation

GitHub Actions / Check WASM

unused macro definition: `required`

Check warning on line 21 in src/bundle/mod.rs

View workflow job for this annotation

GitHub Actions / Check WASM

unused macro definition: `required`
($($base:expr )? ; $first_attr:ident $( . $rest_attrs:ident)* $( , $else_err:expr)?) => {
$( $base . )? $first_attr.as_ref()
$(
.and_then(|v| v.$rest_attrs.as_ref())
)*
$( .ok_or($else_err) )?
}
}
pub(crate) use required;

Check warning on line 30 in src/bundle/mod.rs

View workflow job for this annotation

GitHub Actions / Check WASM

unused import: `required`

Check warning on line 30 in src/bundle/mod.rs

View workflow job for this annotation

GitHub Actions / Check WASM

unused import: `required`

// Known Sigstore bundle media types.
#[derive(Clone, Copy, Debug)]
pub enum Version {
Bundle0_1,
Bundle0_2,
}

impl TryFrom<&str> for Version {
type Error = ();

fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"application/vnd.dev.sigstore.bundle+json;version=0.1" => Ok(Version::Bundle0_1),
"application/vnd.dev.sigstore.bundle+json;version=0.2" => Ok(Version::Bundle0_2),
_ => Err(()),
}
}
}

impl From<Version> for &str {
fn from(value: Version) -> Self {
match value {
Version::Bundle0_1 => "application/vnd.dev.sigstore.bundle+json;version=0.1",
Version::Bundle0_2 => "application/vnd.dev.sigstore.bundle+json;version=0.2",
}
}
}

impl Display for Version {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str((*self).into())?;
Ok(())
}
}
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
Loading

0 comments on commit 07dbbda

Please sign in to comment.