supply chain security

Implementing Sigstore for Software Signing

Implements Sigstore-based software signing and verification using Cosign keyless signing, Rekor transparency log verification, and Fulcio certificate authority integration to establish cryptographic provenance for container images, binaries, and software artifacts. The practitioner configures OIDC-based identity binding, verifies signing events against the Rekor transparency log, and integrates signing workflows into CI/CD pipelines. Activates for requests involving software supply chain signing, keyless container signing, Sigstore deployment, or artifact provenance verification.

cosignfulciokeyless-signingoidcrekorsigstoresoftware-signingsupply-chain
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

When to Use

  • Signing container images and software artifacts without managing long-lived cryptographic keys
  • Establishing verifiable provenance for build outputs in CI/CD pipelines using OIDC identity binding
  • Querying the Rekor transparency log to audit when and by whom an artifact was signed
  • Verifying that container images pulled from registries were signed by authorized identities and issuers
  • Integrating Sigstore verification into Kubernetes admission controllers to enforce signed-image policies

Do not use for signing artifacts that require air-gapped or offline signing workflows where OIDC authentication is unavailable, for environments that cannot reach the public Sigstore infrastructure (Fulcio, Rekor) and have no private instance deployed, or as a replacement for traditional PGP/GPG signing where regulatory compliance mandates specific key management procedures.

Prerequisites

  • Cosign CLI v2.4+ installed (go install github.com/sigstore/cosign/v2/cmd/cosign@latest or binary release)
  • Access to an OIDC identity provider supported by Fulcio (Google, GitHub, Microsoft, or a custom OIDC issuer)
  • Container registry credentials (for signing container images) with push access to store signature objects
  • Python 3.9+ with sigstore, requests, and cryptography packages for the automation agent
  • Network access to fulcio.sigstore.dev, rekor.sigstore.dev, and tuf-repo-cdn.sigstore.dev (or private Sigstore instance URLs)

Workflow

Step 1: Install and Configure Cosign

Install Cosign and verify it can reach the Sigstore infrastructure:

  • Install from binary release: Download the appropriate binary from the Cosign GitHub releases page and verify its checksum. On Linux: curl -LO https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64 && chmod +x cosign-linux-amd64 && sudo mv cosign-linux-amd64 /usr/local/bin/cosign
  • Verify installation: Run cosign version to confirm the version and check connectivity to Sigstore services with cosign initialize which fetches the TUF root of trust
  • Configure custom infrastructure (optional): If running a private Sigstore stack, set --fulcio-url, --rekor-url, and --oidc-issuer flags or use environment variables COSIGN_REKOR_URL and COSIGN_FULCIO_URL

Step 2: Keyless Signing with Cosign and Fulcio

Perform identity-based signing where Fulcio issues a short-lived certificate bound to your OIDC identity:

  • Sign a container image: Run cosign sign <IMAGE_DIGEST> which triggers an OIDC authentication flow. Cosign generates an ephemeral key pair, obtains a short-lived certificate from Fulcio binding the public key to the OIDC identity, signs the image digest, and records the signing event in Rekor. The private key is destroyed immediately after signing.
  • Sign a blob (file): Run cosign sign-blob <file> --bundle artifact.sigstore.json to sign arbitrary files. The bundle contains the signature, certificate, timestamp, and Rekor inclusion proof.
  • Non-interactive signing in CI: Set SIGSTORE_ID_TOKEN environment variable with a valid OIDC token (e.g., from GitHub Actions OIDC or GCP workload identity) to skip the browser-based authentication flow:
    export SIGSTORE_ID_TOKEN=$(curl -sH "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
      "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sigstore" | jq -r '.value')
    cosign sign $IMAGE_DIGEST
  • Supported OIDC providers: Google (https://accounts.google.com), GitHub (https://github.com/login/oauth), Microsoft (https://login.microsoftonline.com), GitLab (https://gitlab.com), and custom providers registered with a private Fulcio instance

Step 3: Verify Signed Artifacts

Verify that artifacts were signed by expected identities from expected OIDC issuers:

  • Verify a container image: Run cosign verify <IMAGE_URI> --certificate-identity=name@example.com --certificate-oidc-issuer=https://accounts.google.com to confirm the image was signed by the specified identity. Cosign validates the certificate chain, checks the Rekor inclusion proof, and verifies the signature matches the current image digest.
  • Verify a signed blob: Run cosign verify-blob <file> --bundle artifact.sigstore.json --certificate-identity=name@example.com --certificate-oidc-issuer=https://accounts.google.com
  • Regex matching for CI identities: Use --certificate-identity-regexp to match CI workflow identities:
    cosign verify $IMAGE --certificate-identity-regexp="https://github.com/myorg/myrepo/.*" \
      --certificate-oidc-issuer=https://token.actions.githubusercontent.com
  • Verification failure modes: Cosign returns a non-zero exit code on failure. Common failures include certificate identity mismatch, expired certificates without a valid Rekor timestamp, missing Rekor entry, and image digest mismatch (image was modified after signing).

Step 4: Query the Rekor Transparency Log

Search and verify entries in the Rekor transparency log to audit signing events:

  • Search by email identity: Use rekor-cli search --email user@example.com to find all signing events for an identity
  • Search by artifact hash: Use rekor-cli search --sha sha256:<hash> to find signing events for a specific artifact
  • Retrieve and verify an entry: Use rekor-cli get --uuid <entry_uuid> to retrieve full entry details including the certificate, signature, and artifact hash
  • Verify log inclusion: Use rekor-cli verify --entry-uuid <uuid> to verify the entry's inclusion proof against the signed tree head, confirming the entry exists in the append-only log and has not been tampered with
  • REST API queries: Query https://rekor.sigstore.dev/api/v1/index/retrieve with POST body {"hash": "sha256:<hash>"} to retrieve entry UUIDs, then fetch full entries from /api/v1/log/entries/<uuid>
  • Monitor for consistency: Use the rekor-monitor tool or Omniwitness to continuously verify the log remains append-only and entries are never mutated or removed

Step 5: Integrate into CI/CD Pipelines

Embed signing and verification into build and deployment pipelines:

  • GitHub Actions: Use sigstore/cosign-installer action to install Cosign, then sign images using the GitHub OIDC token as the identity. The signing identity will be the workflow URL (e.g., https://github.com/org/repo/.github/workflows/build.yml@refs/heads/main).
  • Kubernetes admission enforcement: Deploy Sigstore Policy Controller or Kyverno with Cosign verification policies to reject unsigned or incorrectly signed images at admission time
  • Supply chain metadata: Use cosign attest to attach in-toto attestations (SLSA provenance, SBOM, vulnerability scan results) to images, signed with the same keyless flow, enabling consumers to verify both the artifact and its build metadata

Key Concepts

Term Definition
Keyless Signing Identity-based signing that uses short-lived certificates from Fulcio bound to OIDC identities instead of long-lived cryptographic keys, eliminating key management overhead
Fulcio Sigstore's certificate authority that issues short-lived X.509 certificates after verifying OIDC tokens, binding an ephemeral public key to a verified identity
Rekor Sigstore's immutable, append-only transparency log that records signing events with timestamps, enabling auditors to verify when and by whom an artifact was signed
Cosign The primary CLI tool for signing and verifying container images and blobs using the Sigstore infrastructure (Fulcio + Rekor)
TUF Root of Trust The Update Framework distribution mechanism for Sigstore's root CA certificate and Rekor public key, ensuring clients trust the correct Sigstore infrastructure
OIDC Identity Binding The process where Fulcio verifies a user's identity through an OpenID Connect token and binds it to a short-lived signing certificate
Inclusion Proof A cryptographic proof from Rekor demonstrating that a signing event entry exists within the transparency log's Merkle tree

Tools & Systems

  • Cosign: CLI tool for signing containers and blobs, verifying signatures, and attaching attestations using Sigstore keyless signing or traditional key-based signing
  • Fulcio: Free root certificate authority for code signing certificates issued based on OIDC identity verification with a validity period of approximately 10 minutes
  • Rekor: Transparency log server providing tamper-evident storage of signing metadata, searchable by identity, artifact hash, or public key
  • Sigstore Policy Controller: Kubernetes admission webhook that enforces image signing policies by verifying Cosign signatures and attestations before allowing pod creation
  • rekor-cli: Command-line client for querying, uploading, and verifying entries in the Rekor transparency log

Common Scenarios

Scenario: Securing a Container Image Build Pipeline with Keyless Signing

Context: A DevOps team builds container images in GitHub Actions and deploys to a Kubernetes cluster. They need to ensure only images built by their CI pipeline can be deployed, preventing supply chain attacks from compromised registries or unauthorized pushes.

Approach:

  1. Add sigstore/cosign-installer@v3 to the GitHub Actions workflow and enable OIDC token permissions with id-token: write
  2. After building and pushing the image, sign it with cosign sign $IMAGE_DIGEST using the GitHub Actions OIDC identity automatically
  3. Deploy Sigstore Policy Controller to the Kubernetes cluster with a ClusterImagePolicy requiring signatures from --certificate-identity-regexp=https://github.com/myorg/myrepo/.* and --certificate-oidc-issuer=https://token.actions.githubusercontent.com
  4. Verify the signing entry appears in Rekor by querying with the image digest hash to confirm the transparency log recorded the event
  5. Test the admission controller by attempting to deploy an unsigned image and confirming it is rejected with a policy violation error

Pitfalls:

  • Signing the image tag instead of the digest (cosign sign myimage:latest vs cosign sign myimage@sha256:abc...) means verification breaks when the tag is updated to point to a different digest
  • Not pinning the --certificate-oidc-issuer during verification allows signatures from any OIDC provider to pass, defeating the purpose of identity binding
  • Forgetting to set id-token: write permission in GitHub Actions results in OIDC token retrieval failure and signing errors
  • Using --certificate-identity-regexp=.* in production verification policies effectively disables identity verification

Output Format

## Sigstore Signing Verification Report
 
**Artifact**: ghcr.io/myorg/myapp@sha256:a1b2c3d4...
**Verification Status**: PASSED
 
**Certificate Details**:
  Subject: https://github.com/myorg/myapp/.github/workflows/build.yml@refs/heads/main
  Issuer: https://token.actions.githubusercontent.com
  Valid From: 2026-03-19T10:00:00Z
  Valid To: 2026-03-19T10:10:00Z
 
**Rekor Entry**:
  UUID: 24296fb24b8ad77a8d52...
  Log Index: 89234567
  Integrated Time: 2026-03-19T10:00:05Z
  Inclusion Proof: VERIFIED (tree size: 92000000, root hash: e4f5a6...)
 
**Policy Check**: Image signed by authorized CI workflow identity
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 1

api-reference.md5.1 KB

API Reference: Sigstore Software Signing Agent

Overview

Automates Sigstore-based software signing and verification using Cosign keyless signing, Rekor transparency log queries, and Fulcio certificate authority integration. Wraps the Cosign CLI and Rekor REST API to sign artifacts, verify signatures against expected OIDC identities, search the transparency log, and audit signing events end-to-end.

Dependencies

Package Version Purpose
requests >=2.28 HTTP requests to Rekor REST API
cosign >=2.4 (CLI) Signing and verification of blobs and container images
rekor-cli >=1.3 (CLI, optional) Direct Rekor entry verification with inclusion proofs

CLI Usage

# Check cosign installation and Rekor connectivity
python agent.py check
 
# Sign a file blob (triggers OIDC auth flow)
python agent.py sign-blob myfile.tar.gz --bundle myfile.sigstore.json
 
# Verify a signed blob
python agent.py verify-blob myfile.tar.gz --bundle myfile.sigstore.json \
  --cert-identity user@example.com \
  --cert-oidc-issuer https://accounts.google.com
 
# Sign a container image (use digest, not tag)
python agent.py sign-container registry.io/myimage@sha256:abc123...
 
# Verify a container image
python agent.py verify-container registry.io/myimage@sha256:abc123... \
  --cert-identity user@example.com \
  --cert-oidc-issuer https://accounts.google.com
 
# Search Rekor by artifact hash
python agent.py search-rekor --hash <sha256-hash>
 
# Search Rekor by signer email
python agent.py search-rekor --email user@example.com
 
# Search Rekor by file (computes hash automatically)
python agent.py search-rekor --file myfile.tar.gz
 
# Retrieve a specific Rekor entry
python agent.py get-rekor-entry <uuid>
 
# Get Rekor transparency log state
python agent.py log-info
 
# Full audit of a signing event
python agent.py audit --file myfile.tar.gz \
  --cert-identity user@example.com \
  --cert-oidc-issuer https://accounts.google.com
 
# All commands support custom output path
python agent.py sign-blob myfile.tar.gz --output custom_report.json

Arguments

Argument Required Description
command Yes Subcommand: check, sign-blob, verify-blob, sign-container, verify-container, search-rekor, get-rekor-entry, log-info, audit
--bundle Varies Path to sigstore bundle file (required for verify-blob, optional for sign-blob)
--cert-identity For verify Expected signer identity (email or workflow URL)
--cert-oidc-issuer For verify Expected OIDC issuer URL (e.g., https://accounts.google.com)
--rekor-url No Custom Rekor server URL (default: https://rekor.sigstore.dev)
--output No Output report path (default: sigstore_report.json)

Key Functions

sign_blob_keyless(filepath, bundle_path)

Signs a file using Cosign keyless signing. Triggers OIDC authentication, obtains a Fulcio certificate, records the event in Rekor, and outputs a sigstore bundle containing the signature, certificate, and inclusion proof.

verify_blob_keyless(filepath, bundle_path, cert_identity, cert_oidc_issuer)

Verifies a signed blob against the expected certificate identity and OIDC issuer. Validates the certificate chain, Rekor inclusion proof, and signature integrity.

sign_container_keyless(image_uri)

Signs a container image by digest using keyless signing. The signature is stored as an OCI artifact attached to the image in the registry.

verify_container_keyless(image_uri, cert_identity, cert_oidc_issuer)

Verifies container image signatures and returns parsed verification details including all matching signatures.

search_rekor_by_hash(artifact_hash, rekor_url)

Queries the Rekor REST API POST /api/v1/index/retrieve with a SHA-256 hash to find all log entries for an artifact.

search_rekor_by_email(email, rekor_url)

Queries Rekor for all signing events associated with an email identity.

get_rekor_entry(uuid, rekor_url)

Retrieves a specific Rekor log entry by UUID from GET /api/v1/log/entries/<uuid>, parsing log index, integrated time, inclusion proof presence, and signed entry timestamp.

get_rekor_log_info(rekor_url)

Retrieves the current Rekor log state from GET /api/v1/log, including tree size, root hash, and signed tree head.

audit_signing_event(filepath, image_uri, cert_identity, cert_oidc_issuer, rekor_url)

Performs a comprehensive audit combining artifact hash computation, Rekor log search, entry detail retrieval, inclusion proof verification, and signature verification into a single pass/fail report.

Rekor REST API Endpoints Used

Endpoint Method Purpose
/api/v1/log GET Retrieve current log state (tree size, root hash)
/api/v1/index/retrieve POST Search entries by hash or email
/api/v1/log/entries/<uuid> GET Retrieve a specific log entry

Common OIDC Issuers

Provider Issuer URL
Google https://accounts.google.com
GitHub Actions https://token.actions.githubusercontent.com
Microsoft https://login.microsoftonline.com
GitLab https://gitlab.com

Scripts 1

agent.py17.7 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""Sigstore Software Signing Agent - Automates cosign keyless signing, Rekor
transparency log verification, and Fulcio certificate inspection for container
images and software artifacts."""

import json
import logging
import argparse
import subprocess
import hashlib
import sys
from datetime import datetime, timezone
from pathlib import Path

import requests

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

REKOR_PUBLIC_URL = "https://rekor.sigstore.dev"
FULCIO_PUBLIC_URL = "https://fulcio.sigstore.dev"


def compute_sha256(filepath):
    """Compute SHA-256 hash of a file."""
    sha256 = hashlib.sha256()
    with open(filepath, "rb") as f:
        for chunk in iter(lambda: f.read(8192), b""):
            sha256.update(chunk)
    return sha256.hexdigest()


def run_cosign(args, capture=True):
    """Execute a cosign CLI command and return the result."""
    cmd = ["cosign"] + args
    logger.info("Running: %s", " ".join(cmd))
    result = subprocess.run(cmd, capture_output=capture, text=True, timeout=120)
    if result.returncode != 0:
        logger.error("cosign failed (exit %d): %s", result.returncode, result.stderr)
    return result


def check_cosign_installed():
    """Verify cosign CLI is available and return version info."""
    result = run_cosign(["version"])
    if result.returncode != 0:
        logger.error("cosign is not installed or not in PATH")
        return None
    version_line = ""
    for line in result.stdout.splitlines():
        if "cosign" in line.lower() or "GitVersion" in line:
            version_line = line.strip()
            break
    return version_line or result.stdout.strip()


def sign_blob_keyless(filepath, bundle_path=None):
    """Sign a file blob using cosign keyless signing with Fulcio and Rekor.

    This triggers an OIDC authentication flow. In CI, set SIGSTORE_ID_TOKEN
    environment variable to provide the identity token non-interactively.
    """
    filepath = Path(filepath)
    if not filepath.exists():
        return {"error": f"File not found: {filepath}", "signed": False}

    if bundle_path is None:
        bundle_path = str(filepath) + ".sigstore.json"

    args = ["sign-blob", str(filepath), "--bundle", bundle_path, "--yes"]
    result = run_cosign(args)

    if result.returncode == 0:
        logger.info("Blob signed successfully: %s", filepath)
        bundle_data = {}
        try:
            with open(bundle_path, "r") as f:
                bundle_data = json.load(f)
        except (json.JSONDecodeError, FileNotFoundError):
            pass
        return {
            "signed": True,
            "file": str(filepath),
            "bundle": bundle_path,
            "sha256": compute_sha256(filepath),
            "has_rekor_entry": "rekorBundle" in bundle_data
            or "verificationMaterial" in bundle_data,
        }
    return {"signed": False, "file": str(filepath), "error": result.stderr.strip()}


def verify_blob_keyless(filepath, bundle_path, cert_identity, cert_oidc_issuer):
    """Verify a signed blob against expected identity and OIDC issuer."""
    filepath = Path(filepath)
    if not filepath.exists():
        return {"error": f"File not found: {filepath}", "verified": False}

    args = [
        "verify-blob",
        str(filepath),
        "--bundle",
        bundle_path,
        "--certificate-identity",
        cert_identity,
        "--certificate-oidc-issuer",
        cert_oidc_issuer,
    ]
    result = run_cosign(args)

    return {
        "verified": result.returncode == 0,
        "file": str(filepath),
        "certificate_identity": cert_identity,
        "certificate_oidc_issuer": cert_oidc_issuer,
        "output": result.stdout.strip() if result.returncode == 0 else result.stderr.strip(),
    }


def sign_container_keyless(image_uri):
    """Sign a container image using cosign keyless signing.

    The image_uri should include the digest (e.g., registry/image@sha256:abc...).
    Signing by tag instead of digest is unreliable because tags are mutable.
    """
    args = ["sign", image_uri, "--yes"]
    result = run_cosign(args)

    return {
        "signed": result.returncode == 0,
        "image": image_uri,
        "output": result.stdout.strip() if result.returncode == 0 else result.stderr.strip(),
    }


def verify_container_keyless(image_uri, cert_identity, cert_oidc_issuer):
    """Verify a container image signature against expected identity and issuer."""
    args = [
        "verify",
        image_uri,
        "--certificate-identity",
        cert_identity,
        "--certificate-oidc-issuer",
        cert_oidc_issuer,
    ]
    result = run_cosign(args)

    verification_details = []
    if result.returncode == 0:
        try:
            verification_details = json.loads(result.stdout)
        except json.JSONDecodeError:
            verification_details = [{"raw_output": result.stdout.strip()}]

    return {
        "verified": result.returncode == 0,
        "image": image_uri,
        "certificate_identity": cert_identity,
        "certificate_oidc_issuer": cert_oidc_issuer,
        "signatures": verification_details,
    }


def search_rekor_by_hash(artifact_hash, rekor_url=None):
    """Search the Rekor transparency log for entries matching an artifact hash.

    Queries the Rekor REST API /api/v1/index/retrieve endpoint.
    """
    base = rekor_url or REKOR_PUBLIC_URL
    url = f"{base}/api/v1/index/retrieve"
    payload = {"hash": f"sha256:{artifact_hash}"}

    try:
        resp = requests.post(url, json=payload, timeout=30)
        resp.raise_for_status()
        uuids = resp.json()
        logger.info("Found %d Rekor entries for hash %s", len(uuids), artifact_hash[:16])
        return {"hash": artifact_hash, "entry_uuids": uuids, "count": len(uuids)}
    except requests.RequestException as e:
        logger.error("Rekor search failed: %s", e)
        return {"hash": artifact_hash, "entry_uuids": [], "error": str(e)}


def search_rekor_by_email(email, rekor_url=None):
    """Search the Rekor transparency log for entries matching an email identity."""
    base = rekor_url or REKOR_PUBLIC_URL
    url = f"{base}/api/v1/index/retrieve"
    payload = {"email": email}

    try:
        resp = requests.post(url, json=payload, timeout=30)
        resp.raise_for_status()
        uuids = resp.json()
        logger.info("Found %d Rekor entries for email %s", len(uuids), email)
        return {"email": email, "entry_uuids": uuids, "count": len(uuids)}
    except requests.RequestException as e:
        logger.error("Rekor search failed: %s", e)
        return {"email": email, "entry_uuids": [], "error": str(e)}


def get_rekor_entry(uuid, rekor_url=None):
    """Retrieve a specific entry from the Rekor transparency log by UUID."""
    base = rekor_url or REKOR_PUBLIC_URL
    url = f"{base}/api/v1/log/entries/{uuid}"

    try:
        resp = requests.get(url, timeout=30)
        resp.raise_for_status()
        entry_data = resp.json()

        parsed = {"uuid": uuid, "raw": entry_data}
        for entry_uuid, entry_body in entry_data.items():
            parsed["log_index"] = entry_body.get("logIndex")
            parsed["integrated_time"] = entry_body.get("integratedTime")
            if parsed["integrated_time"]:
                parsed["integrated_time_iso"] = datetime.fromtimestamp(
                    parsed["integrated_time"], tz=timezone.utc
                ).isoformat()
            verification = entry_body.get("verification", {})
            parsed["has_inclusion_proof"] = "inclusionProof" in verification
            parsed["has_signed_entry_timestamp"] = "signedEntryTimestamp" in verification
            break

        return parsed
    except requests.RequestException as e:
        logger.error("Failed to retrieve Rekor entry %s: %s", uuid, e)
        return {"uuid": uuid, "error": str(e)}


def verify_rekor_entry(uuid, rekor_url=None):
    """Verify a Rekor entry's inclusion proof using the rekor-cli."""
    result = run_cosign(["env"])  # Check if rekor-cli is better
    rekor_result = subprocess.run(
        ["rekor-cli", "verify", "--rekor_server", rekor_url or REKOR_PUBLIC_URL,
         "--entry-uuid", uuid],
        capture_output=True, text=True, timeout=60,
    )
    return {
        "uuid": uuid,
        "inclusion_verified": rekor_result.returncode == 0,
        "output": rekor_result.stdout.strip() if rekor_result.returncode == 0
        else rekor_result.stderr.strip(),
    }


def get_rekor_log_info(rekor_url=None):
    """Retrieve the current Rekor transparency log state (tree size, root hash)."""
    base = rekor_url or REKOR_PUBLIC_URL
    url = f"{base}/api/v1/log"

    try:
        resp = requests.get(url, timeout=30)
        resp.raise_for_status()
        log_info = resp.json()
        return {
            "tree_size": log_info.get("treeSize"),
            "root_hash": log_info.get("rootHash"),
            "signed_tree_head": log_info.get("signedTreeHead"),
            "tree_id": log_info.get("treeID"),
        }
    except requests.RequestException as e:
        logger.error("Failed to get Rekor log info: %s", e)
        return {"error": str(e)}


def audit_signing_event(filepath=None, image_uri=None, cert_identity=None,
                        cert_oidc_issuer=None, rekor_url=None):
    """Perform a complete audit of a signing event: verify the artifact and
    cross-reference against the Rekor transparency log."""
    report = {
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "artifact": filepath or image_uri,
        "checks": [],
    }

    # Get Rekor log state
    log_info = get_rekor_log_info(rekor_url)
    report["rekor_log_state"] = log_info

    if filepath:
        artifact_hash = compute_sha256(filepath)
        report["artifact_sha256"] = artifact_hash

        # Search Rekor for this artifact
        rekor_search = search_rekor_by_hash(artifact_hash, rekor_url)
        report["rekor_entries"] = rekor_search
        report["checks"].append({
            "check": "rekor_entry_exists",
            "passed": rekor_search.get("count", 0) > 0,
            "detail": f"Found {rekor_search.get('count', 0)} Rekor entries",
        })

        # Retrieve entry details if found
        if rekor_search.get("entry_uuids"):
            first_uuid = rekor_search["entry_uuids"][0]
            entry_detail = get_rekor_entry(first_uuid, rekor_url)
            report["rekor_entry_detail"] = entry_detail
            report["checks"].append({
                "check": "inclusion_proof_present",
                "passed": entry_detail.get("has_inclusion_proof", False),
                "detail": "Inclusion proof found in Rekor entry"
                if entry_detail.get("has_inclusion_proof")
                else "No inclusion proof in Rekor entry",
            })

        # Verify blob if bundle and identity provided
        bundle_path = str(filepath) + ".sigstore.json"
        if Path(bundle_path).exists() and cert_identity and cert_oidc_issuer:
            verify_result = verify_blob_keyless(
                filepath, bundle_path, cert_identity, cert_oidc_issuer
            )
            report["verification"] = verify_result
            report["checks"].append({
                "check": "signature_verification",
                "passed": verify_result.get("verified", False),
                "detail": "Signature verified against identity and issuer"
                if verify_result.get("verified")
                else verify_result.get("output", "Verification failed"),
            })

    elif image_uri and cert_identity and cert_oidc_issuer:
        verify_result = verify_container_keyless(
            image_uri, cert_identity, cert_oidc_issuer
        )
        report["verification"] = verify_result
        report["checks"].append({
            "check": "container_signature_verification",
            "passed": verify_result.get("verified", False),
            "detail": f"Found {len(verify_result.get('signatures', []))} valid signatures"
            if verify_result.get("verified")
            else "Container signature verification failed",
        })

    # Summary
    passed = sum(1 for c in report["checks"] if c["passed"])
    total = len(report["checks"])
    report["summary"] = {
        "checks_passed": passed,
        "checks_total": total,
        "overall_status": "PASSED" if passed == total and total > 0 else "FAILED",
    }

    return report


def generate_report(results, output_path):
    """Write audit results to a JSON report file."""
    with open(output_path, "w") as f:
        json.dump(results, f, indent=2, default=str)
    logger.info("Report written to %s", output_path)


def main():
    parser = argparse.ArgumentParser(
        description="Sigstore Software Signing Agent - Keyless signing, "
        "Rekor verification, and Fulcio certificate inspection"
    )
    sub = parser.add_subparsers(dest="command", required=True)

    # sign-blob
    sign_blob_p = sub.add_parser("sign-blob", help="Sign a file blob with keyless signing")
    sign_blob_p.add_argument("file", help="Path to file to sign")
    sign_blob_p.add_argument("--bundle", help="Output bundle path (default: <file>.sigstore.json)")

    # verify-blob
    verify_blob_p = sub.add_parser("verify-blob", help="Verify a signed blob")
    verify_blob_p.add_argument("file", help="Path to signed file")
    verify_blob_p.add_argument("--bundle", required=True, help="Path to sigstore bundle")
    verify_blob_p.add_argument("--cert-identity", required=True, help="Expected certificate identity")
    verify_blob_p.add_argument("--cert-oidc-issuer", required=True, help="Expected OIDC issuer URL")

    # sign-container
    sign_cont_p = sub.add_parser("sign-container", help="Sign a container image")
    sign_cont_p.add_argument("image", help="Container image URI (use digest, not tag)")

    # verify-container
    verify_cont_p = sub.add_parser("verify-container", help="Verify a container image signature")
    verify_cont_p.add_argument("image", help="Container image URI")
    verify_cont_p.add_argument("--cert-identity", required=True, help="Expected certificate identity")
    verify_cont_p.add_argument("--cert-oidc-issuer", required=True, help="Expected OIDC issuer URL")

    # search-rekor
    search_p = sub.add_parser("search-rekor", help="Search Rekor transparency log")
    search_group = search_p.add_mutually_exclusive_group(required=True)
    search_group.add_argument("--hash", help="SHA-256 hash of artifact to search")
    search_group.add_argument("--email", help="Email identity to search")
    search_group.add_argument("--file", help="File to compute hash and search")
    search_p.add_argument("--rekor-url", help="Custom Rekor server URL")

    # get-rekor-entry
    entry_p = sub.add_parser("get-rekor-entry", help="Retrieve a Rekor log entry")
    entry_p.add_argument("uuid", help="Rekor entry UUID")
    entry_p.add_argument("--rekor-url", help="Custom Rekor server URL")

    # log-info
    log_p = sub.add_parser("log-info", help="Get Rekor transparency log state")
    log_p.add_argument("--rekor-url", help="Custom Rekor server URL")

    # audit
    audit_p = sub.add_parser("audit", help="Full audit of a signing event")
    audit_group = audit_p.add_mutually_exclusive_group(required=True)
    audit_group.add_argument("--file", help="Path to signed file")
    audit_group.add_argument("--image", help="Container image URI")
    audit_p.add_argument("--cert-identity", help="Expected certificate identity")
    audit_p.add_argument("--cert-oidc-issuer", help="Expected OIDC issuer URL")
    audit_p.add_argument("--rekor-url", help="Custom Rekor server URL")

    # check
    sub.add_parser("check", help="Verify cosign is installed and reachable")

    parser.add_argument("--output", default="sigstore_report.json", help="Output report path")
    args = parser.parse_args()

    result = {}

    if args.command == "check":
        version = check_cosign_installed()
        log_info = get_rekor_log_info()
        result = {
            "cosign_installed": version is not None,
            "cosign_version": version,
            "rekor_reachable": "error" not in log_info,
            "rekor_tree_size": log_info.get("tree_size"),
        }

    elif args.command == "sign-blob":
        result = sign_blob_keyless(args.file, args.bundle)

    elif args.command == "verify-blob":
        result = verify_blob_keyless(
            args.file, args.bundle, args.cert_identity, args.cert_oidc_issuer
        )

    elif args.command == "sign-container":
        result = sign_container_keyless(args.image)

    elif args.command == "verify-container":
        result = verify_container_keyless(
            args.image, args.cert_identity, args.cert_oidc_issuer
        )

    elif args.command == "search-rekor":
        rekor_url = getattr(args, "rekor_url", None)
        if args.hash:
            result = search_rekor_by_hash(args.hash, rekor_url)
        elif args.email:
            result = search_rekor_by_email(args.email, rekor_url)
        elif args.file:
            file_hash = compute_sha256(args.file)
            result = search_rekor_by_hash(file_hash, rekor_url)
            result["file"] = args.file
            result["computed_hash"] = file_hash

    elif args.command == "get-rekor-entry":
        result = get_rekor_entry(args.uuid, getattr(args, "rekor_url", None))

    elif args.command == "log-info":
        result = get_rekor_log_info(getattr(args, "rekor_url", None))

    elif args.command == "audit":
        result = audit_signing_event(
            filepath=getattr(args, "file", None),
            image_uri=getattr(args, "image", None),
            cert_identity=getattr(args, "cert_identity", None),
            cert_oidc_issuer=getattr(args, "cert_oidc_issuer", None),
            rekor_url=getattr(args, "rekor_url", None),
        )

    print(json.dumps(result, indent=2, default=str))
    generate_report(result, args.output)


if __name__ == "__main__":
    main()
Keep exploring