Skip to content
Merged
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
257 changes: 208 additions & 49 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion crates/mise-sigstore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ reqwest = { version = "0.13", default-features = false, features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
sha2 = "0.11"
sigstore-verify = { version = "0.11", default-features = false }
sigstore-verify = { version = "0.14", default-features = false }
snap = "1"
thiserror = "2"
tokio = { version = "1", features = ["fs", "io-util", "time"] }
Expand Down
13 changes: 7 additions & 6 deletions crates/mise-sigstore/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@ pub(crate) fn verify_bundle<'a>(
signer_workflow: Option<&str>,
trusted_root: &TrustedRoot,
) -> Result<()> {
let mut policy = VerificationPolicy::default();
let mut policy = VerificationPolicy::any_identity();
// sigstore-verify's default policy *requires* an inclusion proof when
// `verify_tlog` is on. GitHub artifact attestations and TSA-only bundles
// never carry one, so we'd reject them outright. Skip tlog only when the
// bundle has no inclusion proof — public-Sigstore cosign bundles, which do
// ship a Rekor inclusion proof, still get full tlog verification (Rekor
// checkpoint signature, SET, inclusion-proof Merkle path).
if !bundle.has_inclusion_proof() {
policy = policy.skip_tlog();
policy = policy.skip_tlog_unsafe();
}
// GitHub-internal leaf certs don't carry an SCT extension (GitHub's CA
// doesn't log to public CT). `skip_sct` keeps full certificate-chain
Expand All @@ -70,7 +70,10 @@ pub(crate) fn verify_bundle<'a>(
}
let result = sigstore_verify::verify(artifact, bundle, &policy, trusted_root)?;

verify_signer_workflow_identity(result.identity.as_deref(), signer_workflow)?;
verify_signer_workflow_identity(
result.identity().map(|identity| identity.as_str()),
signer_workflow,
)?;

Ok(())
}
Expand Down Expand Up @@ -185,9 +188,7 @@ pub(crate) fn verify_cert_chain(leaf_der: &[u8], trusted_root: &TrustedRoot) ->
.as_secs();
let validation_time = UnixTime::since_unix_epoch(std::time::Duration::from_secs(not_after));

let all_certs = trusted_root.fulcio_certs().map_err(|e| {
AttestationError::Verification(format!("failed to load CA certs from trust root: {e}"))
})?;
let all_certs = trusted_root.fulcio_certs();
if all_certs.is_empty() {
return Err(AttestationError::Verification(
"trust root contains no CA certificates".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion crates/mise-sigstore/src/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub(crate) fn bundle_source_repository(bundle: &Bundle) -> Option<String> {
let sigstore_verify::types::SignatureContent::DsseEnvelope(envelope) = &bundle.content else {
return None;
};
release_statement_repository(&envelope.decode_payload())
release_statement_repository(envelope.payload.as_bytes())
}

pub(crate) fn certificate_source_repository(cert: &x509_cert::Certificate) -> Option<String> {
Expand Down
52 changes: 28 additions & 24 deletions crates/mise-sigstore/src/cosign.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,13 @@ pub async fn verify_cosign_signature_with_key(
// Bundle path: needs the trust root for tlog (Rekor) verification.
let trusted_root = production_trusted_root().await?;
let artifact = tokio::fs::read(artifact_path).await?;
sigstore_verify::verify_with_key(artifact.as_slice(), &bundle, &public_key, &trusted_root)?;
sigstore_verify::verify_with_key(
artifact.as_slice(),
&bundle,
&public_key,
&sigstore_verify::PublicKeyVerificationPolicy::default(),
&trusted_root,
)?;
return Ok(true);
}

Expand All @@ -91,22 +97,19 @@ pub(crate) fn verify_public_key_bundle(
) -> Result<()> {
use sigstore_verify::bundle::{ValidationOptions, validate_bundle_with_options};
use sigstore_verify::crypto::{
KeyType, SigningScheme, detect_key_type, verify_signature, verify_signature_prehashed,
KeyAlgorithm, SigningScheme, verify_signature, verify_signature_prehashed,
};

validate_bundle_with_options(
bundle,
&ValidationOptions {
require_inclusion_proof: true,
require_timestamp: false,
},
&ValidationOptions::new().with_require_inclusion_proof(true),
)
.map_err(|e| AttestationError::Verification(format!("bundle validation failed: {e}")))?;

let scheme = match detect_key_type(public_key) {
KeyType::Ed25519 => SigningScheme::Ed25519,
KeyType::EcdsaP256 => SigningScheme::EcdsaP256Sha256,
KeyType::Unknown => {
let scheme = match KeyAlgorithm::from_spki(public_key) {
Ok(KeyAlgorithm::Ed25519) => SigningScheme::Ed25519,
Ok(KeyAlgorithm::EcdsaP256) => SigningScheme::EcdsaP256Sha256,
_ => {
return Err(AttestationError::Verification(
"unsupported or unrecognized public key type".to_string(),
));
Expand All @@ -115,7 +118,7 @@ pub(crate) fn verify_public_key_bundle(

match &bundle.content {
SignatureContent::MessageSignature(msg_sig) => {
let artifact_hash = Sha256Hash::try_from_slice(&Sha256::digest(artifact))?;
let artifact_hash = Sha256Hash::try_from(Sha256::digest(artifact).as_slice())?;
if let Some(digest) = &msg_sig.message_digest {
if digest.algorithm != HashAlgorithm::Sha2256 {
return Err(AttestationError::Verification(format!(
Expand Down Expand Up @@ -145,18 +148,19 @@ pub(crate) fn verify_public_key_bundle(
})?;
}
SignatureContent::DsseEnvelope(envelope) => {
let payload = envelope.decode_payload();
let pae = sigstore_verify::types::pae(&envelope.payload_type, &payload);
if !envelope
.signatures
.iter()
.any(|sig| verify_signature(public_key, &pae, &sig.sig, scheme).is_ok())
{
let payload = envelope.payload.as_bytes();
let pae = sigstore_verify::types::pae(&envelope.payload_type, payload);
if verify_signature(public_key, &pae, &envelope.signature.sig, scheme).is_err() {
return Err(AttestationError::Verification(
"DSSE signature verification failed: no valid signatures found".to_string(),
));
}
verify_dsse_artifact_subject(&payload, artifact)?;
verify_dsse_artifact_subject(payload, artifact)?;
}
_ => {
return Err(AttestationError::UnsupportedFormat(
"unsupported bundle signature content".to_string(),
));
}
}

Expand Down Expand Up @@ -260,12 +264,12 @@ pub(crate) fn verify_raw_signature(
signature: &[u8],
public_key: &DerPublicKey,
) -> Result<()> {
use sigstore_verify::crypto::{KeyType, SigningScheme, detect_key_type, verify_signature};
use sigstore_verify::crypto::{KeyAlgorithm, SigningScheme, verify_signature};

let scheme = match detect_key_type(public_key) {
KeyType::Ed25519 => SigningScheme::Ed25519,
KeyType::EcdsaP256 => SigningScheme::EcdsaP256Sha256,
KeyType::Unknown => {
let scheme = match KeyAlgorithm::from_spki(public_key) {
Ok(KeyAlgorithm::Ed25519) => SigningScheme::Ed25519,
Ok(KeyAlgorithm::EcdsaP256) => SigningScheme::EcdsaP256Sha256,
_ => {
return Err(AttestationError::Verification(
"unsupported or unrecognized public key type".to_string(),
));
Expand Down
6 changes: 4 additions & 2 deletions crates/mise-sigstore/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ use reqwest::header::{AUTHORIZATION, HeaderMap, HeaderValue, USER_AGENT};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use sigstore_verify::VerificationPolicy;
pub use sigstore_verify::trust_root::DEFAULT_TUF_URL;
use sigstore_verify::trust_root::{PRODUCTION_TUF_ROOT, SigstoreInstance, TrustedRoot, TufConfig};
use sigstore_verify::trust_root::{SigstoreInstance, TrustedRoot, TufBootstrap, TufConfig};
use sigstore_verify::types::bundle::VerificationMaterialContent;
use sigstore_verify::types::{
Artifact, Bundle, DerCertificate, DerPublicKey, HashAlgorithm, Sha256Hash, SignatureBytes,
Expand All @@ -33,6 +32,9 @@ mod retry;
mod slsa;
mod trust;

/// The Sigstore public-good TUF repository URL.
pub const DEFAULT_TUF_URL: &str = "https://tuf-repo-cdn.sigstore.dev";

pub use client::{
Attestation, AttestationClient, AttestationClientBuilder, FetchParams, GitHubSource, sources,
};
Expand Down
4 changes: 2 additions & 2 deletions crates/mise-sigstore/src/slsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,13 +365,13 @@ pub(crate) fn verify_bundle_slsa_subjects(
) -> Result<()> {
let payload = match &bundle.content {
sigstore_verify::types::SignatureContent::DsseEnvelope(envelope) => {
envelope.decode_payload()
envelope.payload.as_bytes()
}
_ => {
return Err(AttestationError::UnsupportedFormat(
"SLSA provenance must be a DSSE envelope".to_string(),
));
}
};
verify_intoto_payload_subjects(&payload, artifacts, min_level)
verify_intoto_payload_subjects(payload, artifacts, min_level)
}
9 changes: 4 additions & 5 deletions crates/mise-sigstore/src/tests/certificate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ use super::*;
#[test]
fn select_tuf_config_default_uses_production_url() {
// No override → canonical Sigstore public-good TUF URL (default behavior).
assert_eq!(select_tuf_config(None).url, DEFAULT_TUF_URL);
assert_eq!(DEFAULT_TUF_URL, SigstoreInstance::PublicGood.tuf_url());
assert!(format!("{:?}", select_tuf_config(None)).contains(DEFAULT_TUF_URL));
}

#[test]
fn select_tuf_config_override_uses_mirror_url() {
// Override → the mirror URL, while still pinning PRODUCTION_TUF_ROOT
// (the latter is enforced by TufConfig::custom, covered by the
// sigstore-trust-root crate's own tests).
// Override → the mirror URL, while still pinning the production root.
let mirror = "https://tuf-mirror.example.com/".to_string();
assert_eq!(select_tuf_config(Some(mirror.clone())).url, mirror);
assert!(format!("{:?}", select_tuf_config(Some(mirror.clone()))).contains(&mirror));
}

#[test]
Expand Down
9 changes: 6 additions & 3 deletions crates/mise-sigstore/src/trust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use super::*;

/// Override the Sigstore public-good TUF URL (e.g. a mirror derived from mise's
/// `settings.url_replacements`). Passing a mirror URL still bootstraps from the
/// embedded production root ([`PRODUCTION_TUF_ROOT`]), so a mirror cannot forge
/// embedded production root ([`SigstoreInstance::tuf_root`]), so a mirror cannot forge
/// the chain of trust — TUF verifies all fetched metadata against that pinned
/// root. Passing `None` restores the default behavior.
pub fn set_tuf_url(url: Option<String>) {
Expand All @@ -19,9 +19,12 @@ pub(crate) fn select_tuf_config(override_url: Option<String>) -> TufConfig {
match override_url {
// SECURITY: pin the embedded production root even when fetching from a
// mirror. A custom URL has no embedded-root fallback, and the mirror
// serves identical TUF content; bootstrapping with PRODUCTION_TUF_ROOT
// serves identical TUF content; bootstrapping with the production root
// means every metadata file is verified against the canonical root.
Some(url) => TufConfig::custom(url).with_root(PRODUCTION_TUF_ROOT),
Some(url) => TufConfig::custom(
url,
TufBootstrap::trusted(SigstoreInstance::PublicGood.tuf_root()),
),
// Equivalent to `TrustedRoot::production()` (which is itself
// `from_tuf(TufConfig::production())`) — the default path is unchanged.
None => TufConfig::production(),
Expand Down
Loading