sigstore-verify 0.15.0

Sigstore signature verification
Documentation

sigstore-verify

Sigstore signature verification for sigstore-rust.

Overview

This crate provides high-level APIs for verifying Sigstore signatures. It handles the complete verification flow: bundle parsing, certificate chain validation, signature verification, transparency log verification, and identity policy enforcement.

Features

  • Bundle verification: Verify standard Sigstore bundles
  • Certificate validation: X.509 chain validation against Fulcio CA
  • Transparency log verification: Checkpoint signatures, inclusion proofs, SETs
  • Timestamp verification: RFC 3161 timestamp validation
  • Identity policies: Verify signer identity claims (issuer, subject, etc.)

Verification Steps

The policy you pass decides the kind of bundle accepted: a VerificationPolicy for keyless bundles (signed with a Fulcio certificate), or a PublicKeyVerificationPolicy for managed-key bundles (signed with a key you supply). A bundle of the other kind is rejected.

  1. Parse and validate bundle structure
  2. Verify timestamps: RFC 3161 timestamps and, for Rekor v1 entries, the log's integrated time
  3. Keyless only: verify the certificate chain against the trusted root at every verified time, the certificate's SCT, and the identity and issuer the policy requires
  4. Verify the transparency log entry (checkpoint, inclusion proof, or SET)
  5. Verify the signature over the artifact, with the certificate's key or the policy's key
  6. Check that the log entry describes this bundle and artifact

Offline verification and Cargo features

The default sigstore-verify dependency tree (default = ["rustls", "aws-lc-rs"]) has no HTTP client or Tokio runtime: rustls only configures sigstore-trust-root when tuf is also enabled. Verification always uses the supplied trusted root and never fetches artifacts or keys.

  • Default (aws-lc-rs): Uses aws-lc-rs for cryptographic verification. Enable tuf (plus default rustls or native-tls) when the application also needs network trust-root refreshes.
  • Custom CryptoProvider (default-features = false): Omits aws-lc-rs so you can install a custom sigstore_crypto::CryptoProvider via sigstore_crypto::install_default_provider(...) (see crates/sigstore-crypto/tests/boring_provider.rs for a BoringSSL reference implementation). When combining a custom provider with tuf, use rustls-no-provider (and install a matching rustls::crypto::CryptoProvider at startup) or native-tls so rustls does not pull in aws-lc-sys.

Verifier construction

Verifier::new(&root)? prepares Rekor/CT keyrings and Fulcio trust anchors before reading artifacts. Invalid certificates, keys, duplicate log IDs and reversed validity windows are errors at construction. Unsupported configured keys must be removed explicitly rather than silently ignored. Key/authority activation times are still checked when verifying, so long-lived verifiers do not freeze time.

Verification results

VerificationResult is created only by successful verification and exposes read-only accessors. identity() and issuer() are certificate claims; certificate_verified(), sct_verified(), tlog_verified() and identity_policy_checked() describe what was actually checked. verified_timestamps() excludes unsigned time hints. Relaxed policies must not be treated as equivalent to full certificate and log verification.

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

use sigstore_verify::{verify, PublicKeyVerificationPolicy, Verifier, VerificationPolicy};
use sigstore_trust_root::{TrustedRoot, TufConfig};
use sigstore_types::{Artifact, Bundle, DerPublicKey, Sha256Hash};

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

// Actively choose the Sigstore instance and fetch its root through TUF.
let root = TrustedRoot::from_tuf(TufConfig::production()).await?;

// Verify with raw artifact bytes
let artifact_bytes = b"hello world";
let result = verify(artifact_bytes.as_slice(), &bundle, &policy, &root)?;

// Or verify with pre-computed SHA-256 digest (useful for large files)
let digest = Sha256Hash::from_hex("b94d27b9...")?;
let result = verify(digest, &bundle, &policy, &root)?;

// Or use a Verifier directly; it also offers the same inputs
let verifier = Verifier::new(&root)?;
let result = verifier.verify(artifact_bytes.as_slice(), &bundle, &policy)?;

// Stream a large artifact in constant memory
let file = std::fs::File::open("large-artifact.tar.gz")?;
let result = verifier.verify_reader(file, &bundle, &policy)?;

// Runtime-independent futures_io::AsyncRead is also supported
let result = verifier.verify_async_reader(async_reader, &bundle, &policy).await?;

// Managed-key bundles use the same entry points; the key is part of the policy
let key_policy = PublicKeyVerificationPolicy::new(DerPublicKey::from_pem(&key_pem)?);
let result = verifier.verify(artifact_bytes.as_slice(), &bundle, &key_policy)?;

For Tokio readers, enable tokio-util's compat feature and use tokio_util::compat::TokioAsyncReadCompatExt::compat() on the file or stream. Reader verification rejects message-signature schemes that require the original bytes (such as Ed25519) before consuming input; use the byte API for those schemes.

For GitHub artifact attestations, choose GitHub's Sigstore instance explicitly and use the GitHub verification profile:

use sigstore_trust_root::{SigstoreInstance, TrustedRoot};
use sigstore_verify::{verify, VerificationPolicy};
use sigstore_types::{Bundle, Sha256Hash};

let bundle: Bundle = serde_json::from_str(bundle_json)?;
let artifact_digest = Sha256Hash::from_hex("...")?;

// Fetch GitHub's trusted root over TUF (now supported via the `sigstore-tuf`
// 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::any_identity().skip_tlog_unsafe().skip_sct();

let result = verify(artifact_digest, &bundle, &policy, &root)?;

Verification Policies

use sigstore_verify::VerificationPolicy;

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

// Require specific identity and issuer
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::any_identity()
    .skip_tlog_unsafe()
    .skip_certificate_chain();

Managed-key bundles carry no certificate, so the key goes in the policy:

use sigstore_types::DerPublicKey;
use sigstore_verify::PublicKeyVerificationPolicy;

let public_key = DerPublicKey::from_pem(&std::fs::read_to_string("key.pub")?)?;

// Verify the signature, timestamps and transparency log entry
let policy = PublicKeyVerificationPolicy::new(public_key);

// Skip transparency log inclusion (for testing only)
let policy = policy.skip_tlog_unsafe();

All verify functions accept either policy. When the kind of policy is chosen at runtime, pass a &dyn Policy:

use sigstore_verify::{verify, Policy, PublicKeyVerificationPolicy, VerificationPolicy};

let policy: Box<dyn Policy> = match public_key {
    Some(key) => Box::new(PublicKeyVerificationPolicy::new(key)),
    None => Box::new(VerificationPolicy::new(identity, issuer)),
};
let result = verify(artifact_bytes.as_slice(), &bundle, policy.as_ref(), &root)?;

Related Crates

License

Apache-2.0