Skip to content

ADR-012: Transparency Log Integration with Rekor

Status

Proposed (January 2026; boundary sync February 2026)

Open-core boundary note (current main): - Open-core delivered signing/verification stops at local-key x-assay-sig flows. - Rekor/Fulcio integration remains part of the enterprise advanced-signing surface.

Context

ADR-011 introduces Sigstore-based tool signing. A critical component is the transparency log (Rekor) which provides:

  1. Immutable record of all signing events
  2. Inclusion proofs to verify signatures were recorded
  3. Consistency proofs to detect log tampering
  4. Identity monitoring to detect account compromises

This ADR details the Rekor integration for verification and monitoring.

Decision

We will integrate with Rekor as part of the enterprise advanced-signing extension described in ADR-011. Open-core verification does not require or query Rekor. Enterprise deployments may use the public Rekor service or a private instance depending policy and operating model.

Verification Flow

┌─────────────────────────────────────────────────────────────────┐
│                    Signature Verification                        │
│                                                                  │
│  Input: tool definition with x-assay-sig                        │
│                                                                  │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 1. Extract rekor_entry UUID from x-assay-sig            │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                   │
│                              ▼                                   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 2. Fetch entry from Rekor API                           │   │
│  │    GET /api/v1/log/entries/{uuid}                       │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                   │
│                              ▼                                   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 3. Verify inclusion proof (Merkle tree)                 │   │
│  │    - Entry hash matches tree leaf                       │   │
│  │    - Proof path to signed tree head                     │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                   │
│                              ▼                                   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 4. Verify signed tree head (STH)                        │   │
│  │    - STH signature from Rekor public key                │   │
│  │    - Timestamp within acceptable window                 │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                   │
│                              ▼                                   │
│  ┌─────────────────────────────────────────────────────────┐   │
│  │ 5. Match entry content to tool signature                │   │
│  │    - Same artifact hash                                 │   │
│  │    - Same signature bytes                               │   │
│  │    - Same certificate                                   │   │
│  └─────────────────────────────────────────────────────────┘   │
│                              │                                   │
│                              ▼                                   │
│                    ✅ Verification Complete                      │
└─────────────────────────────────────────────────────────────────┘

Rekor API Integration

Fetch Entry

pub async fn fetch_rekor_entry(uuid: &str) -> Result<RekorEntry> {
    let url = format!("{}/api/v1/log/entries/{}", REKOR_URL, uuid);
    let response: HashMap<String, LogEntry> = client.get(&url).send().await?.json().await?;

    let (entry_uuid, entry) = response.into_iter().next()
        .ok_or(Error::EntryNotFound)?;

    Ok(RekorEntry {
        uuid: entry_uuid,
        body: entry.body,
        integrated_time: entry.integrated_time,
        log_id: entry.log_id,
        log_index: entry.log_index,
        verification: entry.verification,
    })
}

Verify Inclusion Proof

pub fn verify_inclusion_proof(
    entry: &RekorEntry,
    rekor_public_key: &PublicKey,
) -> Result<()> {
    let proof = &entry.verification.inclusion_proof;

    // 1. Compute leaf hash
    let leaf_hash = sha256(&entry.body);

    // 2. Walk Merkle proof to root
    let computed_root = proof.hashes.iter()
        .fold(leaf_hash, |acc, sibling| {
            if proof.log_index & 1 == 0 {
                sha256(&[acc, sibling.clone()].concat())
            } else {
                sha256(&[sibling.clone(), acc].concat())
            }
        });

    // 3. Verify signed tree head
    let sth = &proof.signed_tree_head;
    verify_ecdsa(
        &sth.signature,
        &format!("{}{}", sth.tree_size, sth.root_hash),
        rekor_public_key,
    )?;

    // 4. Compare roots
    if computed_root != sth.root_hash {
        return Err(Error::InclusionProofFailed);
    }

    Ok(())
}

Offline Verification Bundle

For air-gapped environments, we support offline bundles containing all verification material:

{
  "x-assay-sig": {
    "signature": "...",
    "certificate": "...",
    "rekor_bundle": {
      "entry": { ... },
      "inclusion_proof": {
        "log_index": 12345678,
        "root_hash": "abc123...",
        "tree_size": 50000000,
        "hashes": ["def456...", "ghi789..."],
        "signed_tree_head": {
          "signature": "...",
          "timestamp": "2026-01-28T12:00:00Z"
        }
      }
    }
  }
}

This allows verification without network access to Rekor.

Identity Monitoring

Beyond one-time verification, we support continuous monitoring for security teams:

┌─────────────────────────────────────────────────────────────────┐
│                    Identity Monitoring Service                   │
│                                                                  │
│  Watches Rekor for signing events matching:                     │
│  - Organization email domains                                   │
│  - GitHub repository patterns                                   │
│  - Specific OIDC subjects                                       │
│                                                                  │
│  Alerts on:                                                     │
│  - Unexpected signing events (account compromise)               │
│  - Signing from unknown locations                               │
│  - Signing outside business hours                               │
│  - Revoked certificates used before revocation                  │
└─────────────────────────────────────────────────────────────────┘

Using rekor-monitor

# Monitor for specific identity
rekor-monitor \
  --identity "developer@mycompany.com" \
  --oidc-issuer "https://accounts.google.com" \
  --output-file alerts.json

# Monitor GitHub Actions workflow
rekor-monitor \
  --identity "repo:myorg/mcp-tools:ref:refs/heads/main" \
  --oidc-issuer "https://token.actions.githubusercontent.com"

Consistency Verification

To detect log tampering, we periodically verify consistency proofs:

pub async fn verify_consistency(
    prev_checkpoint: &Checkpoint,
    rekor_public_key: &PublicKey,
) -> Result<Checkpoint> {
    // 1. Fetch current log info
    let log_info = fetch_log_info().await?;

    // 2. Get consistency proof between old and new tree sizes
    let proof = fetch_consistency_proof(
        prev_checkpoint.tree_size,
        log_info.tree_size,
    ).await?;

    // 3. Verify proof
    verify_consistency_proof(
        &prev_checkpoint.root_hash,
        &log_info.root_hash,
        prev_checkpoint.tree_size,
        log_info.tree_size,
        &proof.hashes,
    )?;

    // 4. Verify signed tree head
    verify_ecdsa(
        &log_info.signed_tree_head.signature,
        &format!("{}{}", log_info.tree_size, log_info.root_hash),
        rekor_public_key,
    )?;

    Ok(Checkpoint {
        tree_size: log_info.tree_size,
        root_hash: log_info.root_hash,
        timestamp: Utc::now(),
    })
}

TUF Root Distribution

Rekor's public key and Fulcio's root certificate are distributed via The Update Framework (TUF):

pub fn initialize_sigstore_roots() -> Result<TrustRoot> {
    // TUF repository: tuf-repo.sigstore.dev
    let tuf_client = TufClient::new("https://tuf-repo.sigstore.dev")?;

    // Fetch trusted root
    let root = tuf_client.get_target("trusted_root.json")?;

    Ok(TrustRoot {
        fulcio_root: root.certificate_authorities[0].clone(),
        rekor_keys: root.transparency_logs
            .iter()
            .map(|tl| tl.public_key.clone())
            .collect(),
        valid_from: root.valid_from,
    })
}

Caching Strategy

To minimize latency and API calls:

Data Cache TTL Location
TUF root 24 hours Disk (XDG_CACHE)
Rekor entries Indefinite Disk (content-addressed)
Signed tree heads 5 minutes Memory
Inclusion proofs Indefinite Disk (with entry)
pub struct RekorCache {
    entries: DiskCache<String, RekorEntry>,
    sth_cache: RwLock<Option<(Instant, SignedTreeHead)>>,
}

impl RekorCache {
    pub async fn get_entry(&self, uuid: &str) -> Result<RekorEntry> {
        // Check disk cache first
        if let Some(entry) = self.entries.get(uuid)? {
            return Ok(entry);
        }

        // Fetch from API
        let entry = fetch_rekor_entry(uuid).await?;

        // Cache to disk (entries are immutable)
        self.entries.put(uuid, &entry)?;

        Ok(entry)
    }
}

Alternatives Considered

1. No Transparency Log (Signature Only)

Pros: - Simpler implementation - No external dependency

Cons: - No proof of signing time - No detection of compromised keys - Cannot revoke signatures

Decision: Transparency is essential for supply chain security.

2. Private-Only Rekor

Pros: - Full control - No public disclosure

Cons: - Operational burden - No cross-organization trust

Decision: Rekor remains enterprise-only; enterprise deployments may choose public Rekor or a private instance.

3. Alternative Transparency Logs

Options: - Google Trillian - Certificate Transparency (CT) logs - AWS QLDB

Decision: Rekor is purpose-built for Sigstore and has the best ecosystem integration.

CLI Flags

# Require Rekor transparency proof for verification (enterprise advanced signing)
assay tool verify tool.json --rekor-required

# Verify evidence bundle with Rekor check
assay evidence verify bundle.tar.gz --rekor-required

# Skip Rekor check (offline mode)
assay tool verify tool.json --offline
assay evidence verify bundle.tar.gz --offline

Policy-based configuration:

# assay.yaml
tool_verification:
  require_transparency: true  # Enterprise advanced signing; equivalent to --rekor-required

evidence_verification:
  require_transparency: true

Implementation Plan

Phase 1: Enterprise Verification (Week 1)

  • Rekor API client
  • Entry fetching
  • Inclusion proof verification
  • TUF root initialization

Phase 2: Offline Bundles (Week 2)

  • Bundle format specification
  • Bundle generation during signing
  • Offline verification path

Phase 3: Caching (Week 2)

  • Disk cache for entries
  • Memory cache for STH
  • Cache invalidation logic

Phase 4: Monitoring (Week 3-4)

  • rekor-monitor wrapper
  • Identity pattern matching
  • Alert generation

Acceptance Criteria

  • Inclusion proof verification passes for valid entries
  • Tampered entries fail verification
  • Offline bundles verify without network
  • Cache hit rate > 90% for repeated verifications
  • Verification latency < 200ms (cached), < 500ms (uncached)

Consequences

Positive

  • Cryptographic proof of signing time for the enterprise signing surface
  • Detection of compromised enterprise identities
  • Append-only audit trail when the enterprise extension is enabled
  • No trust in single entity (distributed witnesses)

Negative

  • Dependency on Rekor availability (99.5% SLA)
  • Network latency for uncached verifications
  • Storage for cached entries

Neutral

  • Must update TUF roots periodically
  • Rekor v2 migration planned for 2026

References