Skip to content

fix: make Ed25519 keys usable again for sign-blob, attest-blob and verify --key - #5074

Open
hamodywe wants to merge 1 commit into
sigstore:mainfrom
hamodywe:fix/ed25519-scheme-agreement
Open

fix: make Ed25519 keys usable again for sign-blob, attest-blob and verify --key#5074
hamodywe wants to merge 1 commit into
sigstore:mainfrom
hamodywe:fix/ed25519-scheme-agreement

Conversation

@hamodywe

Copy link
Copy Markdown

Summary

With the v3 defaults, an Ed25519 key that cosign import-key-pair accepts cannot sign, attest, or verify anything. Three places in cosign disagree about which Ed25519 scheme is in use; this makes them agree with sigstore-go's convention and with each other.

Reproduction (on main, 58aae9e)

$ openssl genpkey -algorithm ed25519 -out ed25519.pem
$ cosign import-key-pair --key ed25519.pem --output-key-prefix ed -y
Public key written to ed.pub

$ cosign sign-blob --key ed.key -y --bundle blob.sigstore.json blob.txt
error during command execution: signing blob.txt: signing bundle: error signing bundle: failed to verify signature: could not verify message: failed to verify signature

$ cosign attest-blob --key ed.key -y --predicate p.json --type custom --bundle att.sigstore.json blob.txt
error during command execution: creating bundle: signing bundle: error signing bundle: could not verify envelope: accepted signatures do not match threshold, Found: 0, Expected 1

Every other key type in the same matrix — RSA PKCS#1 and PKCS#8, EC P-256 SEC1 and PKCS#8, P-384 — signs and verifies. Only Ed25519 fails, and it fails on every run, not on an edge case.

Why

Ed25519 has two schemes that share a key and are not interchangeable: pure Ed25519 and Ed25519ph. sigstore-go's convention (compatSignatureVerifier) is Ed25519ph for message signatures — the only scheme a hashedrekord entry takes — and pure Ed25519 for DSSE envelopes. cosign's GetDefaultLoadOptions loads a key as Ed25519ph, which is right for message signatures, but three other places did not follow:

where was now
pkg/cosign/bundle/sign.go — the verifier the bundle is checked against right after signing LoadDefaultVerifier(pubKey) → pure Ed25519, so an Ed25519ph signature failed the bundle's own post-signing verification loaded to match keypair.GetSigningAlgorithm()
signcommon.NewAttestationBundle — attestations key loaded with the defaults → signs the DSSE envelope as Ed25519ph; sigstore-go verifies envelopes as pure Ed25519 keys are loaded as pure Ed25519 for attestations
pkg/cosign/verify.go — a verifier loaded from --key LoadDefaultVerifier → pure Ed25519 only, so even a correct bundle could not be verified with the public key accepts both schemes, trying the message-signature one first — the same compatibility approach sigstore-go takes for certificates

The first is the one the e2e comment in TestSignBlobNewBundleNonDefaultAlgorithm already describes ("By default, we sign using the prehash variant for a ed25519 key. Rekor supports ed25519ph for a hashedrekord"); the verifier just was not loaded the same way.

ECDSA and RSA are untouched: verifierForKeypair adds no option for them, ed25519CompatibleVerifier returns a non-Ed25519 verifier unchanged, and the attestation change only affects the load options of an Ed25519 key. Image signing and the legacy bundle format go through the same SignData and are unaffected. Also replaces the log.Fatal calls in that signing path with returned errors, so a library caller gets an error rather than an exit.

After

Same key, same commands, default configuration (signatures uploaded to the public log):

$ cosign sign-blob --key ed.key -y --bundle blob.sigstore.json blob.txt
Wrote bundle to file blob.sigstore.json
$ cosign verify-blob --bundle blob.sigstore.json --key ed.pub blob.txt
Verified OK
$ cosign verify-blob --bundle blob.sigstore.json --key ed.pub tampered.txt
error during command execution: failed to verify signature

$ cosign attest-blob --key ed.key -y --predicate p.json --type custom --bundle att.sigstore.json blob.txt
Wrote bundle to file att.sigstore.json
$ cosign verify-blob-attestation --bundle att.sigstore.json --key ed.pub --type custom blob.txt
Verified OK

The bundle carries a SHA2_512 message digest and a Rekor entry, as expected for Ed25519ph. Controls run in the same session: EC P-256, P-384 and RSA sign-blob/verify-blob, and an ECDSA attest-blob/verify-blob-attestation, all Verified OK before and after.

Tests

  • pkg/cosign/bundle: verifierForKeypair accepts the signature of an ephemeral PKIX_ED25519_PH, PKIX_ED25519 and PKIX_ECDSA_P256_SHA_256 keypair; a second test pins that LoadDefaultVerifier rejects an Ed25519ph signature — the mistake the helper exists to avoid.
  • pkg/cosign: ed25519CompatibleVerifier accepts an Ed25519ph and a pure signature over the same message, rejects a signature over other content, reports the same public key, and returns a non-Ed25519 verifier unchanged.
  • go test for pkg/cosign/..., cmd/cosign/cli/{sign,signcommon,verify,attest}/... and internal/key/... passes.

Found by round-tripping sign-blobverify-blob and attest-blobverify-blob-attestation over every key type import-key-pair accepts.


Disclosure: I used Claude (Opus 5) while investigating and writing this. Every command above I ran and checked myself, and review comments will be answered by me.

@hamodywe
hamodywe requested a review from a team as a code owner August 26, 2026 11:30
…rify --key

With the v3 defaults, an Ed25519 key that `cosign import-key-pair` accepts
cannot sign, attest, or verify anything:

    $ cosign sign-blob --key ed25519.key --bundle blob.sigstore.json blob
    error signing bundle: failed to verify signature: could not verify
    message: failed to verify signature

    $ cosign attest-blob --key ed25519.key --predicate p.json --type custom \
        --bundle att.sigstore.json blob
    error signing bundle: could not verify envelope: accepted signatures do
    not match threshold, Found: 0, Expected 1

Ed25519 has two schemes that share a key and are not interchangeable, and
cosign's own pieces disagreed about which one is in use:

  * A key loaded for the transparency log signs as Ed25519ph (the only
    scheme a hashedrekord entry takes). The verifier the bundle is checked
    against right after signing was loaded with LoadDefaultVerifier, which
    is pure Ed25519, so every keyed signing run failed its own post-signing
    verification. The verifier is now loaded to match the keypair's
    signing algorithm.

  * sigstore-go verifies a DSSE envelope with pure Ed25519 and reserves
    Ed25519ph for message signatures, but attestations loaded the key with
    the default options and signed the envelope as Ed25519ph. Attestations
    now load an Ed25519 key as pure Ed25519.

  * A verifier loaded from --key was always pure Ed25519, so even a
    correctly written bundle could not be verified. It now accepts both
    schemes, trying the message-signature one first, the same compatibility
    approach sigstore-go takes for certificates.

None of this touches ECDSA or RSA, image signing, or the legacy bundle
format; those paths still load and verify exactly as before. Also replaces
the log.Fatal calls in the bundle signing path with returned errors, so a
library caller gets an error rather than an exit.

Signed-off-by: hamodywe <iosapk.org@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hamodywe
hamodywe force-pushed the fix/ed25519-scheme-agreement branch from 3145e07 to 900669a Compare August 26, 2026 11:31
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.

1 participant