I used help from AI to create this issue, but I humanly read it and reviewed/corrected it before submitting it
Why did I create this issue
The concrete case that surfaced this: GitHub artifact attestations produced for a private repository. Those are signed by GitHub's own (non–public-good) Sigstore instance, which does not write to a public transparency log — the bundle carries a TSA timestamp instead. gh attestation verify and cosign (with --use-signed-timestamps --insecure-ignore-tlog) both verify these bundles; sigstore-java cannot.
Summary
KeylessVerifier.verify(...) unconditionally requires at least one Rekor transparency-log entry in the bundle. Sigstore bundles whose verification material contains only an RFC 3161 signed timestamp timestampVerificationData) and no tlogEntries are valid per the bundle spec, but verification fails before the timestamp or signature is ever checked.
Related issues (this is not a duplicate)
Steps to reproduce
- Obtain a bundle with
timestampVerificationData and no tlogEntries — e.g. an actions/attest-build-provenance attestation from a private GitHub repo (gh attestation download …), plus the matching trust root (gh attestation trusted-root).
sigstore-cli verify <artifact> --bundle <bundle> --trusted-root <root> --certificate-identity <san> --certificate-oidc-issuer https://token.actions.githubusercontent.com
Actual behavior
Verification throws while fetching the (absent) Rekor entry — bundle.getEntries().get(0) on an empty list — so it never reaches signature/timestamp verification.
Root cause
In sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java, the Rekor entry is treated as mandatory:
// findHashAlgorithm(...)
var rekorEntry = bundle.getEntries().get(0);
...
// verify(...)
var rekorEntry = bundle.getEntries().get(0);
...
rekorVerifier.verifyEntry(rekorEntry); // "Transparency log entry could not be verified"
verifyTimestamps(...) already knows how to verify RFC 3161 timestamps and even guards if (timestamps.isEmpty() && entryTime == null), but it is only reached after the
mandatory Rekor entry has been obtained and verified — so a TSA-only bundle can never get there.
Expected behavior
When a bundle has no tlogEntries but does carry a valid timestampVerificationData (RFC 3161 timestamp from a TSA in the trusted root), KeylessVerifier should verify the Fulcio chain + DSSE/message signature and establish signing time from the TSA timestamp — i.e. treat the transparency-log entry as optional when signed timestamps are present, rather than requiring one. This matches the Sigstore bundle spec, whose verification material permits tlog entries and/or signed timestamps.
Suggested fix
- Make
findHashAlgorithm / verify tolerate an empty bundle.getEntries():
- derive the hash algorithm without a Rekor entry when none is present (e.g. from the DSSE payload / message-signature algorithm), and
- skip
rekorVerifier.verifyEntry(...) when there is no entry, relying on verifyTimestamps(...) (TSA) for the trusted signing time.
- Keep requiring at least one source of trusted time (tlog SET or RFC 3161 timestamp), which
verifyTimestamps already enforces.
References / prior art
- Sigstore bundle spec: verification material may be a transparency-log entry, a signed timestamp, or both.
cosign verify-blob-attestation verifies the same bundles with --use-signed-timestamps --insecure-ignore-tlog (+ --trusted-root); gh attestation verify verifies them natively.
I used help from AI to create this issue, but I humanly read it and reviewed/corrected it before submitting it
Why did I create this issue
The concrete case that surfaced this: GitHub artifact attestations produced for a private repository. Those are signed by GitHub's own (non–public-good) Sigstore instance, which does not write to a public transparency log — the bundle carries a TSA timestamp instead.
gh attestation verifyandcosign(with--use-signed-timestamps --insecure-ignore-tlog) both verify these bundles;sigstore-javacannot.Summary
KeylessVerifier.verify(...)unconditionally requires at least one Rekor transparency-log entry in the bundle. Sigstore bundles whose verification material contains only an RFC 3161 signed timestamptimestampVerificationData) and notlogEntriesare valid per the bundle spec, but verification fails before the timestamp or signature is ever checked.Related issues (this is not a duplicate)
KeylessVerifiercallsbundle.getEntries().get(0)).This issue is about verifying bundles that carry no
tlogEntriesat all — only a TSA timestamp.have zero tlog entries.
Steps to reproduce
timestampVerificationDataand notlogEntries— e.g. anactions/attest-build-provenanceattestation from a private GitHub repo (gh attestation download …), plus the matching trust root (gh attestation trusted-root).sigstore-cli verify <artifact> --bundle <bundle> --trusted-root <root> --certificate-identity <san> --certificate-oidc-issuer https://token.actions.githubusercontent.comActual behavior
Verification throws while fetching the (absent) Rekor entry —
bundle.getEntries().get(0)on an empty list — so it never reaches signature/timestamp verification.Root cause
In
sigstore-java/src/main/java/dev/sigstore/KeylessVerifier.java, the Rekor entry is treated as mandatory:verifyTimestamps(...)already knows how to verify RFC 3161 timestamps and even guardsif (timestamps.isEmpty() && entryTime == null), but it is only reached after themandatory Rekor entry has been obtained and verified — so a TSA-only bundle can never get there.
Expected behavior
When a bundle has no
tlogEntriesbut does carry a validtimestampVerificationData(RFC 3161 timestamp from a TSA in the trusted root),KeylessVerifiershould verify the Fulcio chain + DSSE/message signature and establish signing time from the TSA timestamp — i.e. treat the transparency-log entry as optional when signed timestamps are present, rather than requiring one. This matches the Sigstore bundle spec, whose verification material permits tlog entries and/or signed timestamps.Suggested fix
findHashAlgorithm/verifytolerate an emptybundle.getEntries():rekorVerifier.verifyEntry(...)when there is no entry, relying onverifyTimestamps(...)(TSA) for the trusted signing time.verifyTimestampsalready enforces.References / prior art
cosign verify-blob-attestationverifies the same bundles with--use-signed-timestamps --insecure-ignore-tlog(+--trusted-root);gh attestation verifyverifies them natively.