Skip to content

Add Rekor v2 support, DSSE/in-toto signing, and managed-key verification (v0.3.0)#308

Open
segiddins wants to merge 11 commits into
mainfrom
segiddins/rekor-v2-dsse-managed-keys
Open

Add Rekor v2 support, DSSE/in-toto signing, and managed-key verification (v0.3.0)#308
segiddins wants to merge 11 commits into
mainfrom
segiddins/rekor-v2-dsse-managed-keys

Conversation

@segiddins

Copy link
Copy Markdown
Member

This stack brings sigstore-ruby up to current sigstore protocol support and prepares a 0.3.0 release.

What's included

  • Rekor v2 (tiled transparency log) verification and signing using the hashedrekord 0.0.2 entry type. v2 entries carry no integrated time and no online retrieval API, so the binding to the log rests entirely on the Merkle inclusion proof and its checkpoint signature, which are now verified for every checkpoint-bearing entry. Ed25519 log ids are derived as C2SP note key hashes from the trusted root's declared logId.
  • DSSE / in-toto attestation signing (Signer#sign_dsse), using the in-toto attestation protos.
  • Managed-key (bring-your-own-key) verification via Verifier#verify(key:), for bundles that carry a public-key hint instead of a Fulcio certificate. The certificate path validation, SCT, and identity-policy steps are skipped only for such bundles (gated on the bundle's structural key_based?), and a mutual-exclusion guard rejects key-without-cert-bundle and cert-bundle-with-key.
  • Timestamping Authority (TSA / RFC 3161) signing, and defaulting the signing configuration from TUF.
  • Update to sigstore protobuf-specs v0.5.1 / protobug 0.2.0, adding the protobug_in_toto_attestation_protos dependency and widening protobug_sigstore_protos to ~> 0.2.0.
  • Update conformance suites to sigstore-conformance v0.0.29 and tuf-conformance v2.4.0, reducing the xfail set to the single conditional TSA-timestamp case (pending Set time directly on the x509 store ruby/openssl#770).

Security hardening

  • Enforce that an RFC 3161 timestamp's message imprint covers the bundle signature, and that the timestamp's gen_time falls within the Timestamping Authority's validity window. Verification fails closed when no trusted signing time (TSA response or log integrated time) is available.
  • A managed-key DSSE bundle carried as a Rekor v1 entry now fails with a clear error instead of raising NoMethodError.
  • An Ed25519 (v2) tlog key with no declared log id now raises rather than registering an unusable key id.
  • The EXACT signing-service selector now accepts configs that list more services than requested, matching the spec / sigstore-python.

Notes

  • Each commit is self-contained; the final commit bumps the version to 0.3.0 and backfills the changelog (including the previously unrecorded 0.2.x releases).
  • Full unit suite (197 tests) and rubocop pass locally.

Marked draft pending CI (including the conformance jobs against prod and staging).

@segiddins segiddins requested a review from Hayden-IO June 20, 2026 21:08
@segiddins segiddins force-pushed the segiddins/rekor-v2-dsse-managed-keys branch 2 times, most recently from 0a1814d to 5b99bcd Compare June 21, 2026 03:01
segiddins added 10 commits June 21, 2026 00:50
…tion

Bump sigstore-conformance to v0.0.29 and tuf-conformance to v2.4.0.

Fix issues surfaced by the newer suite:
- Treat the OIDC nbf claim as optional (RFC 7519), which was rejecting
  beacon tokens that omit it and breaking all signing.
- Reject message-signature bundles whose messageDigest hint does not
  match the artifact being verified.

Add verification support for Rekor v2 (tiled) transparency log bundles:
- Parse Ed25519 (PKIX_ED25519) log keys, and key the Rekor keyring by the
  trusted root's declared log id, since a Rekor v2 log id is a C2SP
  signed-note key hash over the log name rather than a digest of the key.
- Verify hashedrekord 0.0.2 entries (including the hashedrekord-over-DSSE
  encoding) directly against the bundle's artifact, signature, and cert.
- Rely on the Timestamping Service for signing time, as Rekor v2 entries
  carry no integrated time; require at least one trusted timestamp.
- Ignore unknown witness cosignatures on checkpoints, requiring one valid
  signature from the log key.
- Verify the RFC 3161 message imprint against the bundle signature and
  enforce the timestamp authority's validity window from the trusted root.

Repoint the conformance unit tests to the reorganized bundle-verify/
asset layout, verifying offline against the vendored production trusted
root.

Mark only the remaining unsupported conformance cases as xfail:
managed-key verification and signing to a Rekor v2 instance.

Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
Support verifying bundles whose verificationMaterial is a public key hint
rather than a Fulcio certificate. The verifying key is supplied out-of-band
via a new `--key` flag; certificate-path validation, SCT verification, and
identity-policy checks are skipped, while the signature and its binding to the
Rekor entry are still verified against the supplied key.

Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
Bump the protobug_sigstore_protos dependency from ~> 0.1.0 to ~> 0.2.0,
which pulls in protobuf-specs v0.5.1.

Take advantage of two additions the new specs ship:

- SigningConfig v0.2 is now a compiled message, so signing_config.rb
  decodes the document through Sigstore::TrustRoot::V1::SigningConfig
  instead of parsing the JSON by hand. Only the service-selection
  algorithm (per-operator collapse + ANY/EXACT/ALL selector) remains in
  Ruby. An unset EXACT count now defaults to the proto zero value, which
  the selector logic rejects as invalid.

- Artifact gained a typed artifact_digest oneof variant. VerificationInput
  now accepts it alongside the raw-bytes and sha256: URI forms, extracted
  into a testable VerificationInput.hashed_input_for.

Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
Implement signing an in-toto statement as a DSSE envelope, the last
conformance signing case sigstore-ruby did not support. `sign-bundle
--in-toto` now signs the file's DSSE Pre-Authentication Encoding, wraps it
in an io.intoto.Envelope, and submits the transparency-log entry as a
Rekor v1 `dsse` 0.0.1 entry (or, against a Rekor v2 instance, a
hashedrekord over the PAE digest, matching the verification side). The
bundle carries the envelope rather than a message signature, and is
self-verified against the statement's first subject digest.

Drop the managed-key xfails from the Rakefile and ci.yml: managed-key
verification has been supported since the previous commit, so those cases
now pass and were failing CI as strict XPASS.

The full conformance suite now passes with no xfails beyond the
conditional TSA timestamp case.

Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
Add the protobug_in_toto_attestation_protos dependency and decode the
in-toto statement through InTotoAttestation::V1::Statement in the DSSE
self-verification path, instead of parsing the JSON by hand.

Default the signing config from TUF when none is given. SigningConfig
gains production/staging/from_tuf (mirroring TrustedRoot) backed by a new
TrustUpdater#signing_config_path that fetches the signing_config.v0.2.json
target, returning nil when the repository publishes none or when offline.
The CLI's sign command now falls back to this published config instead of
nil, so signing targets whatever Rekor the instance's config selects --
Rekor v2 where the config lists a valid v2 service -- and picks up the
instance's TSA. With no published config (or offline) the Signer still
uses the legacy v1 flow from the trusted root.

Both TrustedRoot and SigningConfig also gain a from_tuf_updater entry
point that builds from an already-refreshed updater, and the CLI shares
one TrustUpdater between them so signing refreshes the repository once
rather than twice.

Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
Backfill CHANGELOG entries for 0.2.0-0.2.3 and add a 0.3.0 entry covering
Rekor v2, DSSE/in-toto signing, managed-key verification, TSA signing, and
the protobuf-specs v0.5.1 upgrade.

Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
The extremely-dangerous-public-oidc-beacon action is deprecated and already fails
to publish a usable token ("Current token expires too early"), breaking the CI
smoketest. Fetch sigstore-conformance's longer-lived public test token directly
instead; it is issued by a Google service account, so make the smoketest's expected
certificate OIDC issuer configurable (defaulting to GitHub Actions for the ambient
identity used when releasing).

Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
Three fixes surfaced by the ruby-head and jruby-head CI legs:

- Pass +verifier_pem+ to Bundle#expected_tlog_entry positionally instead of as a
  keyword argument. JRuby 4.0 (jruby-head) mis-binds the keyword through the
  bundle's DelegateClass and raises ArgumentError; the helpers it forwards to are
  already positional, so this is also more consistent.
- On JRuby, compute the precertificate TBS via the manual ASN.1 path rather than
  OpenSSL::X509::Certificate#tbs_bytes. jruby-openssl's tbs_bytes does not honor
  removing the SCT extension, producing the wrong TBS and breaking SCT verification.
- Bump the bundled Bundler to 4.0.14. 2.6.9 crashes during 'bundle install' on
  Ruby 4.1 (ruby-head) with 'uninitialized constant Pathname::SEPARATOR_PAT';
  4.0.14 requires Ruby >= 3.2 and installs the existing lockfile unchanged.

Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
@segiddins segiddins force-pushed the segiddins/rekor-v2-dsse-managed-keys branch from daea6da to cf6c27c Compare June 21, 2026 05:51
@segiddins segiddins marked this pull request as ready for review June 21, 2026 06:55
The jruby-head TUF conformance leg failed with XPASS(strict) on
test_keytype_and_scheme[rsa/rsassa-pss-sha256]. The xfail set keyed the
RSASSA-PSS case on RUBY_ENGINE == "jruby", but the library actually gates PSS
verification on OpenSSL::PKey::RSA#verify_pss (internal/key.rb). Stable jruby's
jruby-openssl lacks the method so the case fails as expected, while jruby-head's
newer jruby-openssl provides it, so verification succeeds and pytest's strict
xfail turns the unexpected pass into a job failure.

Gate the xfail on the same capability instead of the engine name: emit it only
when verify_pss is undefined. This drops the jruby special-case entirely and
self-corrects whenever stable jruby gains the method.

Signed-off-by: Samuel Giddins <segiddins@segiddins.me>
# precertificate SCT extension (it re-encodes the original extensions), yielding
# the wrong TBS and breaking SCT verification. Fall through to the manual ASN.1
# path on JRuby, which manipulates the DER directly and is engine-independent.
if openssl.respond_to?(:tbs_bytes) && RUBY_ENGINE != "jruby"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

JRuby has pushed jruby-openssl 0.16.1 with a fix; should this be re-evauluated?

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.

2 participants