Skip to content
Open
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
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ let verifier = Verifier::new(&root);
let bundle: sigstore_types::Bundle = serde_json::from_str(&bundle_json)?;

// Authorize the expected signer, not just any valid Sigstore identity.
let policy = VerificationPolicy::default()
.require_issuer("https://token.actions.githubusercontent.com")
.require_identity("https://github.com/myorg/myrepo/.github/workflows/release.yml@refs/tags/v1.0.0");
let policy = VerificationPolicy::new(
"https://github.com/myorg/myrepo/.github/workflows/release.yml@refs/tags/v1.0.0",
"https://token.actions.githubusercontent.com",
);
verifier.verify(artifact_bytes, &bundle, &policy)?;
```

Expand Down Expand Up @@ -88,7 +89,7 @@ let bundle_json = serde_json::to_string_pretty(&bundle)?;
cargo run -p sigstore-sign --features browser --example sign_blob -- README.md -o README.md.sigstore.json

# Verify with our tool
cargo run -p sigstore-verify --example verify_bundle -- README.md README.md.sigstore.json
cargo run -p sigstore-verify --example verify_bundle -- --identity "$INSERT_YOUR_EMAIL" --issuer https://github.com/login/oauth README.md README.md.sigstore.json

# You can also verify with cosign
cosign verify-blob --bundle README.md.sigstore.json \
Expand All @@ -108,7 +109,7 @@ curl -LO https://github.com/sigstore/cosign/releases/download/v3.0.2/cosign_chec

# 2. Verify the bundle (cryptographic verification without identity policy)
cargo run -p sigstore-verify --example verify_bundle -- \
cosign_checksums.txt cosign_checksums.txt.sigstore.json
--allow-any-identity cosign_checksums.txt cosign_checksums.txt.sigstore.json

# 3. Or verify with identity policy (this release was signed with Google's keyless signer)
cargo run -p sigstore-verify --example verify_bundle -- \
Expand Down
2 changes: 1 addition & 1 deletion crates/sigstore-conformance/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ fn verify_bundle(args: &[String]) -> Result<(), Box<dyn std::error::Error>> {
let certificate_oidc_issuer = certificate_oidc_issuer.unwrap();

// Create verification policy
let policy = VerificationPolicy::default()
let policy = VerificationPolicy::any_identity()
.require_identity(certificate_identity)
.require_issuer(certificate_oidc_issuer);

Expand Down
17 changes: 12 additions & 5 deletions crates/sigstore-verify/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ This crate provides high-level APIs for verifying Sigstore signatures. It handle
5. Verify timestamps if present
6. Check identity against policy (optional)

## Authorization

`VerificationPolicy` has no default. Prefer `VerificationPolicy::new(identity, issuer)`
to authorize the expected signer. `any_identity()` is an explicit opt-in to
cryptographic verification without signer authorization. The example CLI likewise
requires an identity/issuer restriction or `--allow-any-identity`.

## Usage

```rust
Expand All @@ -31,7 +38,7 @@ use sigstore_trust_root::{TrustedRoot, TufConfig};
use sigstore_types::{Artifact, Bundle, Sha256Hash};

let bundle: Bundle = serde_json::from_str(bundle_json)?;
let policy = VerificationPolicy::default();
let policy = VerificationPolicy::any_identity();

// Actively choose the Sigstore instance and fetch its root through TUF.
let root = TrustedRoot::from_tuf(TufConfig::production()).await?;
Expand Down Expand Up @@ -80,7 +87,7 @@ let artifact_digest = Sha256Hash::from_hex("...")?;
// client), or use the embedded copy below for an offline path.
// let root = TrustedRoot::from_tuf(sigstore_trust_root::TufConfig::github()).await?;
let root = TrustedRoot::from_embedded(SigstoreInstance::GitHub)?;
let policy = VerificationPolicy::default().skip_tlog_unsafe().skip_sct();
let policy = VerificationPolicy::any_identity().skip_tlog_unsafe().skip_sct();

let result = verify(artifact_digest, &bundle, &policy, &root)?;
```
Expand All @@ -91,15 +98,15 @@ let result = verify(artifact_digest, &bundle, &policy, &root)?;
use sigstore_verify::VerificationPolicy;

// Default policy (verify tlog, timestamps, and certificate chain)
let policy = VerificationPolicy::default();
let policy = VerificationPolicy::any_identity();

// Require specific identity and issuer
let policy = VerificationPolicy::default()
let policy = VerificationPolicy::any_identity()
.require_identity("user@example.com")
.require_issuer("https://accounts.google.com");

// Skip certain verifications (for testing only)
let policy = VerificationPolicy::default()
let policy = VerificationPolicy::any_identity()
.skip_tlog_unsafe()
.skip_certificate_chain();
```
Expand Down
21 changes: 18 additions & 3 deletions crates/sigstore-verify/examples/verify_bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//!
//! Verify a local bundle:
//! ```sh
//! cargo run -p sigstore-verify --example verify_bundle -- artifact.txt artifact.sigstore.json
//! cargo run -p sigstore-verify --example verify_bundle -- --allow-any-identity artifact.txt artifact.sigstore.json
//! ```
//!
//! Verify with identity requirements:
Expand Down Expand Up @@ -65,6 +65,7 @@ async fn main() {
let mut trusted_root_path: Option<String> = None;
let mut tuf_root_path: Option<String> = None;
let mut staging = false;
let mut allow_any_identity = false;
let mut positional: Vec<String> = Vec::new();

let mut i = 1;
Expand Down Expand Up @@ -115,6 +116,7 @@ async fn main() {
tuf_root_path = Some(value);
}
}
"--allow-any-identity" => allow_any_identity = true,
"--staging" => {
staging = true;
}
Expand Down Expand Up @@ -152,6 +154,13 @@ async fn main() {
process::exit(2);
}

if identity.is_none() && identity_regexp.is_none() && issuer.is_none() && !allow_any_identity {
eprintln!(
"Error: specify an identity/issuer requirement or explicitly pass --allow-any-identity"
);
process::exit(2);
}

let artifact_or_digest = &positional[0];
let bundle_path = &positional[1];

Expand Down Expand Up @@ -223,7 +232,7 @@ async fn main() {
};

// Build verification policy
let mut policy = VerificationPolicy::default();
let mut policy = VerificationPolicy::any_identity();
if let Some(id) = &identity {
policy = policy.require_identity(id);
}
Expand Down Expand Up @@ -330,6 +339,9 @@ fn print_usage(program: &str) {
eprintln!(" <BUNDLE> Path to the Sigstore bundle (.sigstore.json)");
eprintln!();
eprintln!("Options:");
eprintln!(
" --allow-any-identity Verify cryptography without authorizing a signer"
);
eprintln!(" --certificate-identity <ID> Required certificate identity (exact match)");
eprintln!(" --certificate-identity-regexp <RE> Required certificate identity (regex)");
eprintln!(" --certificate-oidc-issuer <ISSUER> Required OIDC issuer");
Expand All @@ -344,7 +356,10 @@ fn print_usage(program: &str) {
eprintln!();
eprintln!("Examples:");
eprintln!(" # Verify a bundle");
eprintln!(" {} artifact.txt artifact.sigstore.json", program);
eprintln!(
" {} --allow-any-identity artifact.txt artifact.sigstore.json",
program
);
eprintln!();
eprintln!(" # Verify with identity regex (cosign-compatible)");
eprintln!(" {} --certificate-identity-regexp \".*\" \\", program);
Expand Down
4 changes: 2 additions & 2 deletions crates/sigstore-verify/examples/verify_conda_attestation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ async fn main() {

// Build verification policy - for GitHub Actions attestations, we expect
// the identity to be the workflow file path and issuer to be GitHub
let policy =
VerificationPolicy::default().require_issuer("https://token.actions.githubusercontent.com");
let policy = VerificationPolicy::any_identity()
.require_issuer("https://token.actions.githubusercontent.com");

// Verify
println!();
Expand Down
2 changes: 1 addition & 1 deletion crates/sigstore-verify/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
//! let bundle = Bundle::from_json(&bundle_json)?;
//! let artifact = std::fs::read("artifact.txt")?;
//!
//! let policy = VerificationPolicy::default()
//! let policy = VerificationPolicy::any_identity()
//! .require_identity("user@example.com")
//! .require_issuer("https://accounts.google.com");
//!
Expand Down
42 changes: 30 additions & 12 deletions crates/sigstore-verify/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,15 @@ impl PublicKeyVerificationPolicy {
}

/// Policy for verifying certificate-based signatures.
///
/// Choose [`Self::new`] to authorize an identity and issuer, or explicitly opt
/// into [`Self::any_identity`] for cryptographic verification without signer
/// authorization. There is deliberately no `Default` policy.
///
/// ```compile_fail
/// use sigstore_verify::VerificationPolicy;
/// let _: VerificationPolicy = Default::default();
/// ```
#[derive(Debug, Clone)]
pub struct VerificationPolicy {
/// Expected identity (email or URI)
Expand All @@ -78,31 +87,40 @@ pub struct VerificationPolicy {
pub certificate: CertificatePolicy,
}

impl Default for VerificationPolicy {
fn default() -> Self {
impl VerificationPolicy {
/// Require both an exact certificate identity and its OIDC issuer.
pub fn new(identity: impl Into<String>, issuer: impl Into<String>) -> Self {
Self::any_identity()
.require_identity(identity)
.require_issuer(issuer)
}

/// Verify cryptography without authorizing a particular signer.
///
/// Any identity accepted by the configured certificate authorities may
/// verify. Applications must authorize the result separately before use.
pub fn any_identity() -> Self {
Self {
identity: None,
issuer: None,
verify_tlog: true,
certificate: CertificatePolicy::Verify { verify_sct: true },
}
}
}

impl VerificationPolicy {
/// Create a policy that requires a specific identity
pub fn with_identity(identity: impl Into<String>) -> Self {
Self {
identity: Some(identity.into()),
..Default::default()
..Self::any_identity()
}
}

/// Create a policy that requires a specific issuer
pub fn with_issuer(issuer: impl Into<String>) -> Self {
Self {
issuer: Some(issuer.into()),
..Default::default()
..Self::any_identity()
}
}

Expand Down Expand Up @@ -237,7 +255,7 @@ impl Verifier {
/// let trusted_root = TrustedRoot::from_json(SIGSTORE_PRODUCTION_TRUSTED_ROOT)?;
/// let verifier = Verifier::new(&trusted_root);
/// let bundle: Bundle = todo!();
/// let policy = VerificationPolicy::default();
/// let policy = VerificationPolicy::any_identity();
///
/// // Option 1: Verify with raw bytes
/// let artifact_bytes = b"hello world";
Expand Down Expand Up @@ -826,7 +844,7 @@ fn verify_message_signature_crypto(
/// let bundle = Bundle::from_json(&bundle_json)?;
/// let artifact = std::fs::read("artifact.txt")?;
///
/// verify(&artifact, &bundle, &sigstore_verify::VerificationPolicy::default(), &trusted_root)?;
/// verify(&artifact, &bundle, &sigstore_verify::VerificationPolicy::any_identity(), &trusted_root)?;
/// # Ok(())
/// # }
/// ```
Expand Down Expand Up @@ -899,7 +917,7 @@ mod tests {

#[test]
fn test_verification_policy_default() {
let policy = VerificationPolicy::default();
let policy = VerificationPolicy::any_identity();
assert!(policy.verify_tlog);
assert_eq!(
policy.certificate,
Expand All @@ -909,7 +927,7 @@ mod tests {

#[test]
fn test_verification_policy_builder() {
let policy = VerificationPolicy::default()
let policy = VerificationPolicy::any_identity()
.require_identity("test@example.com")
.require_issuer("https://accounts.google.com")
.skip_tlog_unsafe();
Expand All @@ -924,7 +942,7 @@ mod tests {

#[test]
fn test_skip_sct_keeps_certificate_chain_verification() {
let policy = VerificationPolicy::default().skip_sct();
let policy = VerificationPolicy::any_identity().skip_sct();

assert_eq!(
policy.certificate,
Expand All @@ -934,7 +952,7 @@ mod tests {

#[test]
fn test_skip_certificate_chain_preserves_legacy_sct_skip() {
let policy = VerificationPolicy::default().skip_certificate_chain();
let policy = VerificationPolicy::any_identity().skip_certificate_chain();

assert_eq!(policy.certificate, CertificatePolicy::Skip);
}
Expand Down
Loading
Loading