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

Conformance suite feature parity #354

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
28 changes: 16 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/sigstore/sigstore-rs"

[workspace]
members = ["tests/conformance"]
resolver = "2"

[features]
default = ["full-native-tls", "cached-client", "sigstore-trust-root", "bundle"]
wasm = ["getrandom/js", "ring/wasm32_unknown_unknown_js", "chrono/wasmbind"]
Expand Down Expand Up @@ -40,11 +44,11 @@ rekor-native-tls = ["reqwest/native-tls", "rekor"]
rekor-rustls-tls = ["reqwest/rustls-tls", "rekor"]
rekor = ["reqwest"]

sign = ["sigstore_protobuf_specs", "fulcio", "rekor", "cert"]
verify = ["sigstore_protobuf_specs", "fulcio", "rekor", "cert"]
sign = ["fulcio", "rekor", "cert"]
verify = ["fulcio", "rekor", "cert"]
bundle = ["sign", "verify"]

sigstore-trust-root = ["sigstore_protobuf_specs", "futures-util", "tough", "regex", "tokio/sync"]
sigstore-trust-root = ["futures-util", "tough", "regex", "tokio/sync"]

cosign-native-tls = [
"oci-distribution/native-tls",
Expand Down Expand Up @@ -77,7 +81,7 @@ base64 = "0.22.0"
cached = { version = "0.49.2", optional = true, features = ["async"] }
cfg-if = "1.0.0"
chrono = { version = "0.4.27", default-features = false, features = ["now", "serde"] }
const-oid = { version = "0.9.6", features = ["db"] }
const-oid = { version = "0.9", features = ["db"] }
digest = { version = "0.10.3", default-features = false }
ecdsa = { version = "0.16.7", features = ["pkcs8", "digest", "der", "signing"] }
ed25519 = { version = "2.2.1", features = ["alloc"] }
Expand Down Expand Up @@ -113,25 +117,25 @@ 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", "json"], optional = true }
serde_with = { version = "3.4", features = ["base64", "json"], optional = true }
sha2 = { version = "0.10.6", features = ["oid"] }
signature = { version = "2.0" }
sigstore_protobuf_specs = { version = "0.3.2", optional = true }
sigstore_protobuf_specs = "0.3"
thiserror = "1.0.30"
tokio = { version = "1.17.0", features = ["rt"] }
tokio-util = { version = "0.7.10", features = ["io-util"] }
tough = { version = "0.17.1", features = ["http"], optional = true }
tracing = "0.1.31"
url = "2.2.2"
x509-cert = { version = "0.2.5", features = ["builder", "pem", "std", "sct"] }
x509-cert = { version = "0.2", features = ["builder", "pem", "std", "sct"] }
crypto_secretbox = "0.1.1"
zeroize = "1.5.7"
rustls-webpki = { version = "0.102.1", features = ["alloc"] }
serde_repr = "0.1.16"
rustls-webpki = { version = "0.102", features = ["alloc"] }
serde_repr = "0.1"
hex = "0.4.3"
json-syntax = { version = "0.12.2", features = ["canonicalize", "serde"] }
tls_codec = { version = "0.4.1", features = ["derive"] }
ring = "0.17.6"
json-syntax = { version = "0.12", features = ["canonicalize", "serde"] }
tls_codec = { version = "0.4", features = ["derive"] }
ring = "0.17"

[dev-dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
Expand Down
26 changes: 13 additions & 13 deletions examples/cosign/verify/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use sigstore::cosign::{CosignCapabilities, SignatureLayer};
use sigstore::crypto::SigningScheme;
use sigstore::errors::SigstoreVerifyConstraintsError;
use sigstore::registry::{ClientConfig, ClientProtocol, OciReference};
use sigstore::trust::sigstore::SigstoreTrustRoot;
use sigstore::trust::sigstore::{Instance, TrustRootOptions};
use std::time::Instant;

extern crate anyhow;
Expand Down Expand Up @@ -107,7 +107,7 @@ struct Cli {

async fn run_app(
cli: &Cli,
frd: &dyn sigstore::trust::TrustRoot,
frd: &dyn sigstore::trust::RawTrustRoot,
) -> 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 Down Expand Up @@ -184,7 +184,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_keys()?.is_empty() {
let require_rekor_bundle = if !frd.raw_tlog_keys().is_empty() {
true
} else {
warn!("certificate based verification is weaker when Rekor integration is disabled");
Expand Down Expand Up @@ -225,21 +225,23 @@ async fn run_app(
Ok((trusted_layers, verification_constraints))
}

async fn fulcio_and_rekor_data(cli: &Cli) -> anyhow::Result<Box<dyn sigstore::trust::TrustRoot>> {
async fn fulcio_and_rekor_data(
cli: &Cli,
) -> anyhow::Result<Box<dyn sigstore::trust::RawTrustRoot>> {
if cli.use_sigstore_tuf_data {
info!("Downloading data from Sigstore TUF repository");

let repo: sigstore::errors::Result<SigstoreTrustRoot> = SigstoreTrustRoot::new(None).await;
let repo = Instance::Prod
.trust_config(TrustRootOptions { cache_dir: None })
.await?;

return Ok(Box::new(repo?));
return Ok(Box::new(repo.trust_root));
};

let mut data = sigstore::trust::ManualTrustRoot::default();
if let Some(path) = cli.rekor_pub_key.as_ref() {
data.rekor_key = Some(
fs::read(path)
.map_err(|e| anyhow!("Error reading rekor public key from disk: {}", e))?,
);
data.tlog_keys = vec![fs::read(path)
.map_err(|e| anyhow!("Error reading rekor public key from disk: {}", e))?];
}

if let Some(path) = cli.fulcio_cert.as_ref() {
Expand All @@ -250,9 +252,7 @@ async fn fulcio_and_rekor_data(cli: &Cli) -> anyhow::Result<Box<dyn sigstore::tr
encoding: sigstore::registry::CertificateEncoding::Pem,
data: cert_data,
};
data.fulcio_certs
.get_or_insert(Vec::new())
.push(certificate.try_into()?);
data.ca_certs.push(certificate.try_into()?);
}

Ok(Box::new(data))
Expand Down
81 changes: 47 additions & 34 deletions src/bundle/sign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

//! Types for signing artifacts and producing Sigstore bundles.

use std::error::Error;
use std::io::{self, Read};
use std::time::SystemTime;

Expand All @@ -39,13 +40,15 @@ use x509_cert::builder::{Builder, RequestBuilder as CertRequestBuilder};
use x509_cert::ext::pkix as x509_ext;

use crate::bundle::models::Version;
use crate::crypto::transparency::{verify_sct, CertificateEmbeddedSCT};
use crate::errors::{Result as SigstoreResult, SigstoreError};
use crate::fulcio::oauth::OauthTokenProvider;
use crate::fulcio::{self, FulcioClient, FULCIO_ROOT};
use crate::fulcio::{self, FulcioClient};
use crate::oauth::IdentityToken;
use crate::rekor::apis::configuration::Configuration as RekorConfiguration;
use crate::rekor::apis::entries_api::create_log_entry;
use crate::rekor::models::{hashedrekord, proposed_entry::ProposedEntry as ProposedLogEntry};
use crate::trust::{CTFEKeyring, TrustConfig, TrustRoot};

/// An asynchronous Sigstore signing session.
///
Expand Down Expand Up @@ -79,25 +82,27 @@ impl<'ctx> SigningSession<'ctx> {
fulcio: &FulcioClient,
token: &IdentityToken,
) -> SigstoreResult<(ecdsa::SigningKey<NistP256>, fulcio::CertificateResponse)> {
let subject =
// SEQUENCE OF RelativeDistinguishedName
vec![
// SET OF AttributeTypeAndValue
vec![
// AttributeTypeAndValue, `emailAddress=...`
AttributeTypeAndValue {
oid: const_oid::db::rfc3280::EMAIL_ADDRESS,
value: AttributeValue::new(
pkcs8::der::Tag::Utf8String,
token.unverified_claims().email.as_ref(),
)?,
}
].try_into()?
].into();
let subject = token
.unverified_claims()
.subject()
.ok_or(SigstoreError::UnexpectedError(
"OIDC token does not contain a subject".into(),
))?;
// AttributeTypeAndValue, `emailAddress=...`
let subject_attr = AttributeTypeAndValue {
oid: const_oid::db::rfc3280::EMAIL_ADDRESS,
value: AttributeValue::new(pkcs8::der::Tag::Utf8String, subject.as_ref())?,
};
// SEQUENCE OF RelativeDistinguishedName
let subject_seq = vec![
// SET OF AttributeTypeAndValue
vec![subject_attr].try_into()?,
]
.into();

let mut rng = rand::thread_rng();
let private_key = ecdsa::SigningKey::from(p256::SecretKey::random(&mut rng));
let mut builder = CertRequestBuilder::new(subject, &private_key)?;
let mut builder = CertRequestBuilder::new(subject_seq, &private_key)?;
builder.add_extension(&x509_ext::BasicConstraints {
ca: false,
path_len_constraint: None,
Expand Down Expand Up @@ -128,7 +133,12 @@ impl<'ctx> SigningSession<'ctx> {
return Err(SigstoreError::ExpiredSigningSession());
}

// TODO(tnytown): verify SCT here, sigstore-rs#326
if let Some(detached_sct) = &self.certs.detached_sct {
verify_sct(detached_sct, &self.context.ctfe_keyring)?;
} else {
let sct = CertificateEmbeddedSCT::new(&self.certs.cert, &self.certs.chain)?;
verify_sct(&sct, &self.context.ctfe_keyring)?;
}

// Sign artifact.
let input_hash: &[u8] = &hasher.clone().finalize();
Expand Down Expand Up @@ -247,27 +257,30 @@ pub mod blocking {
pub struct SigningContext {
fulcio: FulcioClient,
rekor_config: RekorConfiguration,
ctfe_keyring: CTFEKeyring,
}

impl SigningContext {
/// Manually constructs a [`SigningContext`] from its constituent data.
pub fn new(fulcio: FulcioClient, rekor_config: RekorConfiguration) -> Self {
Self {
pub fn new<R>(trust: TrustConfig<R>) -> Result<Self, Box<dyn Error + Send + Sync>>
where
R: TrustRoot,
{
let fulcio_url = Url::parse(&trust.signing_config.ca_url)?;
let oauth = OauthTokenProvider::default().with_issuer(&trust.signing_config.oidc_url);
let fulcio = FulcioClient::new(fulcio_url, crate::fulcio::TokenProvider::Oauth(oauth));

let mut rekor_config: RekorConfiguration = Default::default();
// XX: At the time of writing, the ecosystem only uses one log. In the future, we
// may want to check multiple logs to mitigate split-view attacks.
rekor_config.base_path = trust.signing_config.tlog_urls[0].clone();

let ctfe_keyring = trust.trust_root.ctfe_keys()?;

Ok(Self {
fulcio,
rekor_config,
}
}

/// Returns a [`SigningContext`] configured against the public-good production Sigstore
/// infrastructure.
pub fn production() -> SigstoreResult<Self> {
Ok(Self::new(
FulcioClient::new(
Url::parse(FULCIO_ROOT).expect("constant FULCIO root fails to parse!"),
crate::fulcio::TokenProvider::Oauth(OauthTokenProvider::default()),
),
Default::default(),
))
ctfe_keyring,
})
}

/// Configures and returns a [`SigningSession`] with the held context.
Expand Down
3 changes: 3 additions & 0 deletions src/bundle/verify/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ pub enum CertificateErrorKind {
#[error("certificate expired before time of signing")]
Expired,

#[error("certificate SCT verification failed")]
Sct(#[source] crate::crypto::transparency::SCTError),

#[error("certificate verification failed")]
VerificationFailed(#[source] webpki::Error),
}
Expand Down
Loading