Skip to content

feat: complete and harden Rekor v2 support - #176

Merged
wolfv merged 15 commits into
sigstore:mainfrom
wolfv:harden/rekor-v2-write-path
Aug 27, 2026
Merged

wolfv merged 15 commits into
sigstore:mainfrom
wolfv:harden/rekor-v2-write-path

Conversation

@wolfv

@wolfv wolfv commented Aug 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Complete and harden Rekor v2 support across the client, signer, bundle validator, and verifier.

This deliberately removes the obsolete dsse/0.0.2 API and the lossy v2-to-v1 response conversion. Rekor v2 supports DSSE envelopes through hashedrekord/0.0.2, and its TransparencyLogEntry response is now stored directly in the bundle.

Changes

Rekor client

  • make API version an explicit client property (new_v2, public_v2, staging_v2, and builder selection)
  • reject version-specific operations locally when used with the wrong client
  • apply a 30-second HTTP timeout, satisfying Rekor v2's >=20-second write requirement
  • return protobuf-compatible TransparencyLogEntry values directly from v2 writes
  • strictly reject malformed, non-hashedrekord, nonzero-integrated-time, SET-bearing, proof-less, or checkpoint-less v2 responses
  • add v2 checkpoint, hash-tile, and entry-bundle read APIs
  • parse checkpoints and return coordinate-preserving tile/entry-bundle wrappers
  • support certificate and DER public-key verifiers with typed P-256 key details

Removed code

  • remove DsseEntryV2, DsseRequestV002, and create_dsse_entry_v2
  • remove the duplicate LogEntryV2/InclusionProofV2 models
  • remove duplicated v2 response conversion and every unwrap_or_default() numeric fallback

Signing

  • submit DSSE PAE digests as hashedrekord/0.0.2
  • carry v2 responses directly into bundles instead of round-tripping through v1 hex encodings
  • require an RFC 3161 TSA for Rekor v2 configurations
  • derive the submitted key-details value from the selected signing scheme
  • correct documentation claiming the basic default was v2 (it is v1)

Bundle validation and verification

  • recognize only hashedrekord/0.0.2 as Rekor v2
  • follow the Rekor v2 specification by ignoring unauthenticated duplicate inclusionProof.logIndex, treeSize, and rootHash fields
  • use the top-level log index and signed checkpoint tree size/root for v2 Merkle verification
  • retain v1's duplicate-field consistency checks
  • handle checkpoints containing multiple log/witness signatures without letting an invalid earlier matching signature suppress a later valid log signature

Documentation

  • replace the stale Rekor README with a v1/v2 capability matrix and write/read examples
  • document the absence of v2 search and online-verification APIs
  • document that parsed checkpoints still require signature verification

Test-first validation

The new regressions failed before implementation:

  • cargo test -p sigstore-rekor --test v2_client failed to compile because the versioned/read APIs and typed request constructors did not exist.
  • rekor_v2_ignores_untrusted_duplicate_inclusion_proof_fields failed because structural validation trusted the duplicate proof root.
  • rekor_v2_accepts_a_valid_log_signature_after_an_invalid_matching_signature failed on the first invalid matching signature instead of checking the later valid signature.

Testing

cargo test -p sigstore-rekor -p sigstore-bundle -p sigstore-sign -p sigstore-verify
cargo clippy -p sigstore-rekor -p sigstore-bundle -p sigstore-sign -p sigstore-verify --all-targets --all-features -- -D warnings
cargo test --workspace --all-features --no-run
cargo fmt --all -- --check
git diff --check

All pass locally. Existing live Rust/Cosign v2 interoperability coverage remains in .github/workflows/interop.yml.

Breaking changes

This intentionally removes the never-standardized dsse/0.0.2 submission API and changes create_entry_v2 to return TransparencyLogEntry rather than the v1 LogEntry compatibility type.

Cross-implementation references

The implementation and compatibility tests are cross-checked against pinned versions of the other Sigstore clients:

Pinned upstream sigstore-python message-signature and DSSE Rekor v2 fixtures are now included with provenance under crates/sigstore-verify/test_data/upstream/sigstore-python/; both are verified by the Rust test suite.

Protobuf-spec compatibility

The non-published sigstore-protobuf-compat crate now also checks Rekor v2 directly against sigstore_protobuf_specs 0.5.1:

  • certificate-backed HashedRekordRequestV002 / CreateEntryRequest
  • public-key-backed HashedRekordRequestV002 / CreateEntryRequest
  • both imported sigstore-python Rekor v2 bundles as canonical protobuf Bundle JSON

Each check verifies fixture acceptance by generated pbjson types, acceptance of the Rust reserialization, structural equality after a Rust round trip, and Rust acceptance of canonical JSON emitted by the generated types. This catches misspelled/invented fields, wrong oneof shapes, incorrect bytes encoding, default-elision incompatibility, and dropped protobuf data.

@wolfv
wolfv force-pushed the harden/rekor-v2-write-path branch from fd25a06 to cfe3f68 Compare August 4, 2026 11:44
@wolfv
wolfv force-pushed the harden/rekor-v2-write-path branch 2 times, most recently from a105bce to f1e604c Compare August 13, 2026 11:51
@wolfv

wolfv commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

Adversarial follow-up pushed: v2 write responses are now rejected unless the logged digest, signature, and verifier match the submitted request; managed-key hashedrekord verifiers must match the caller-supplied key; canonical legacy intoto support and cooperative hashing are preserved. This branch currently contains #169 and #175 so it remains mergeable before they land; merge #169 and #175 first, then this PR.

@wolfv wolfv mentioned this pull request Aug 13, 2026
@wolfv
wolfv force-pushed the harden/rekor-v2-write-path branch from f1e604c to 5c658c4 Compare August 14, 2026 15:35
@wolfv

wolfv commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Status update: rebased onto current main and resolved the post-#159 checkpoint-keyring conflict. The branch is currently conflict-free and CI is green. It still carries #175 plus the prehashed-signing foundation now tracked in #183; merge #175 and #183 before #176, then #176 can be restacked to remove those duplicate commits.

@wolfv
wolfv force-pushed the harden/rekor-v2-write-path branch 2 times, most recently from ae83a1d to f89f3d6 Compare August 18, 2026 10:30
@wolfv
wolfv requested a review from tnytown August 18, 2026 14:57
@wolfv
wolfv force-pushed the harden/rekor-v2-write-path branch from 2dfe9ed to f89f3d6 Compare August 18, 2026 15:02

@tnytown tnytown left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a few code quality nits that caught my eye; looks like this behaves correctly

if is_v2 {
let leaf_index = entry
.log_index
.as_u64()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i wonder if it's possible to deserialize and store the expected representation instead of having getters on types containing the original format. this pattern appears a lot in the codebase where we're effectively performing deserialization outside of the deserialization layer

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed on deserializing into semantic types as early as possible. Commit 52026a1 now makes log indices and proof tree sizes validated non-negative types at deserialization, removing the downstream conversions. A fully protocol-specific normalized v1/v2 entry representation would require a broader public bundle-model change, so I have left that part for a focused follow-up.

Comment thread crates/sigstore-verify/src/verify_impl/tlog.rs
Comment thread crates/sigstore-rekor/src/client.rs Outdated
Comment thread crates/sigstore-rekor/src/client.rs Outdated
Comment thread crates/sigstore-verify/src/verify_impl/hashedrekord.rs Outdated
wolfv added 13 commits August 19, 2026 11:44
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Pass the caller-supplied public key into Rekor consistency checks so canonical intoto entries remain safely verifiable for managed-key bundles.

Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
tile_path left the most-significant base-1000 group unpadded, producing
paths like x1/x234/067 instead of the spec-mandated x001/x234/067 for
any index whose digit count is not a multiple of three. Encode via
base-1000 decomposition with each group zero-padded to three digits,
and cover the group-boundary shapes in unit and wire-path tests.

Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
The two Rekor protocols share no endpoints: v1 is a JSON REST API,
v2 is a write endpoint plus C2SP tile reads with different request and
response types. Model that as two structs instead of a runtime tag.

RekorClient keeps its name and the v1 surface (including the cache,
which only ever applied to v1 endpoints). The new RekorV2Client carries
create_entry, get_checkpoint, get_tile, and get_entry_bundle, so
calling an endpoint of the wrong version is now a compile error. This
removes the require_version guards, the UnsupportedOperation error
variant, and the builder's with_api_version. RekorApiVersion remains as
signing configuration.

Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Reject Rekor v2 write responses for a different submission and require logged public-key verifiers to match the caller-supplied managed key.

Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>
@wolfv
wolfv force-pushed the harden/rekor-v2-write-path branch from 52026a1 to e40fc0a Compare August 19, 2026 09:45
wolfv added 2 commits August 19, 2026 17:04
…-path

# Conflicts:
#	crates/sigstore-rekor/src/body.rs
#	crates/sigstore-verify/src/verify_impl/helpers.rs
#	crates/sigstore-verify/src/verify_impl/rekor.rs
Signed-off-by: Wolf Vollprecht <w.vollprecht@gmail.com>

@jku jku left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this looks really good. I can't find real problems and it contains multiple major improvements. Left one comment on the "default URLs", but I don't think that's a blocker as it matches the rest of the low level API.

Reviewing PRs this large is kind of painful even with the separate commits. If the pr-stacks worked for you maybe that would work for this sort of thing in future

Comment on lines +386 to +388
pub fn public() -> Self {
Self::new(RekorApiVersion::V2.default_url())
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish we didn't have these methods especially for v2 -- leaving URLs visible in the examples would make it clearer that the caller is responsible for the URL and that V2.default_url() is unlikely to point to a usable log 100% of the time.

@wolfv
wolfv merged commit 875b876 into sigstore:main Aug 27, 2026
17 checks passed
@wolfv wolfv mentioned this pull request Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants