Skip to content

WriteAttestationsReferrer drops per-layer annotations (certificate, chain, RFC3161 timestamp) #4707

@rdsharma

Description

@rdsharma

Summary

WriteAttestationsReferrer in pkg/oci/remote/write.go silently drops per-layer OCI descriptor annotations when pushing DSSE attestations via the OCI 1.1 Referrers API. This makes referrer attestation manifests unverifiable — cosign, Kyverno, and any policy engine reading attestation referrers cannot find the signing certificate, chain, or RFC3161 timestamp.

Root Cause

WriteAttestationsReferrer (line 421) calls atts.Layers() which returns []v1.Layer — raw layer content with no descriptor metadata. These layers are passed to WriteReferrer, which rebuilds descriptors (lines 357-361) using only MediaType, Digest, and Size, omitting the Annotations field entirely.

Cosign stores verification materials as per-layer descriptor annotations (not in layer content):

  • dev.sigstore.cosign/certificate
  • dev.sigstore.cosign/chain
  • dev.sigstore.cosign/rfc3161timestamp
  • dev.cosignproject.cosign/signature
  • predicateType

All of these are silently lost.

The sibling function WriteSignaturesExperimentalOCI() does not have this bug because it calls sigs.Get() (which returns []oci.Signature carrying annotations) rather than sigs.Layers().

Steps to Reproduce

This function is not called by cosign's CLI directly, but is exported public API in pkg/oci/remote/. We hit this as library consumers calling WriteAttestationsReferrer from our own signing tool.

  1. Build an oci.SignedEntity with DSSE attestations that have per-layer descriptor annotations (certificate, chain, RFC3161 timestamp, predicateType):
    // Annotations are set via mutate.AppendSignatures → mutate.Addendum{Annotations: ...}
    // Verified present via atts.Get() → sig.Annotations() before push
  2. Push via the Referrers API:
    d, err := ociremote.ResolveDigest(ref, remoteOpts...)
    ociremote.WriteAttestationsReferrer(d, signedEntity, remoteOpts...)
  3. Fetch the resulting referrer manifest and inspect layer descriptors - annotations are missing:
    "layers": [{
      "mediaType": "application/vnd.in-toto+json",
      "size": 1757,
      "digest": "sha256:..."
      // No "annotations" field - certificate, chain, timestamp all dropped
    }]
  4. Verification fails - e.g. cosign verify-attestation returns x509: certificate signed by unknown authority because the cert/chain annotations are gone.

For comparison, pushing the same SignedEntity via WriteAttestations (tag-based) preserves all per-layer annotations correctly,

Expected Behavior

Layer descriptors in the referrer manifest should include all annotations from the original attestation layers (certificate, chain, timestamp, predicateType).

Proposed Fix

  1. In WriteAttestationsReferrer: use atts.Get() instead of atts.Layers() to retrieve []oci.Signature objects that carry annotations, then convert to []v1.Layer.
  2. In WriteReferrer: after building each layer descriptor, check if the layer implements an Annotations() method (via structural interface assertion) and populate the descriptor's Annotations field.

I have a fix and test ready — will submit a PR shortly.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions