Conversation
BREAKING CHANGE: 2.0 is not wire-compatible with 1.x in either direction, and the protocol has no version negotiation. Algorithm determination is inverted. The algorithm is taken from the JWK alg member and never derived from kty and crv, which underdetermine it: an RSA key has no crv and leaves both padding and hash free, and an EC curve does not fix the hash. alg must be fully specified per RFC 9864, so the polymorphic EdDSA identifier is rejected in favour of Ed25519 and Ed448. A key whose kty or crv disagrees with its alg is rejected rather than resolved in favour of either reading. RSA is newly supported (PS256/384/512, RS256/384/512). Symmetric material -- oct, HS*, hmac-sha256 -- is rejected outright, since every scheme here distributes a public key. ML-DSA and the AKP key type are recognized and declined as unsupported_algorithm rather than failing as malformed; absence of support is a reason to decline, not a parse error. The hwk scheme emits and requires alg, and rejects kid: the key is inline, so an identifier selects nothing. sigkey is replaced by the Accept-Signature-Scheme and Accept-Signature-Alg header fields, which are Lists of Tokens and so can name more than one value. supported_algorithms is removed from Signature-Error in favour of Accept-Signature-Alg, which works on a challenge as well as an error. The unsupported_scheme code is added. The jwt scheme now validates exp, which it previously ignored entirely. exp is what bounds how long the confirmation key an assertion carries stays acceptable; without it that key is acceptable indefinitely. The strictAAuth option is removed. Covering signature-key is a requirement rather than a profile choice, because an uncovered Signature-Key header can be substituted without invalidating the signature, so it can no longer be disabled. Errors are typed. SignatureVerificationError carries the Signature-Error code directly instead of leaving it to be recovered by matching on message text. The RFC 9421 and Go interop vectors predate this specification and do not cover signature-key, so they can no longer verify; their assertions now check that they are rejected with invalid_input. Restoring cryptographic agreement with the RFC vectors, by testing the signature base directly, is follow-up work. Assertion caching is deliberately not implemented: the draft carries an Editor's Note calling it a straw man. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXExbWHeem2SNb7tZ2LGnc
RFC 9421 Section 1.4 gives three ways to establish the signature algorithm; Section 3.3.7 develops the second for JOSE algorithms and states that "the explicit alg signature parameter is not used at all when using JOSE signing algorithms". This package was already on that path -- fetch() never emitted alg and verify() took the algorithm from the key -- but neither was stated as deliberate, and nothing proved it. State it in both places and cover it with tests. The distinction that matters is between ignoring the parameter and discarding it. alg lives inside @signature-params, which is covered by the signature, so it must still be reproduced verbatim when the base is reconstructed; dropping it would change the base and fail an otherwise valid signature. Ignoring it means only that it does not select the algorithm. The tests sign requests by hand so the covered parameters can include an alg the library would never emit: one declaring ed25519, one declaring rsa-pss-sha512 against an Ed25519 key, and one declaring the symmetric hmac-sha256. All three verify, and the algorithm used is the key's. A verifier that honoured the parameter would attempt the wrong operation on the second and fail. A final test signs with one key and presents another to confirm the suite can still produce a failure, so the others cannot pass vacuously. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXExbWHeem2SNb7tZ2LGnc
-07 was published to the datatracker on 2026-07-05 and was editorial -- a paragraph in the Introduction citing AAuth and Email Verification. Everything this branch implements lands in -08. The hwk alg parameter in particular was forbidden in -06 and remained forbidden in -07, so the break this package straddles is -07 to -08. Every reference saying otherwise was off by one revision. Bumped to 2.0.0-alpha.1 rather than correcting in place, because 2.0.0-alpha.0 is published and its tarball embeds the wrong claim in README.md and MIGRATING-2.0.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXExbWHeem2SNb7tZ2LGnc
A verifier could not say which algorithms it accepts. It implemented eleven and accepted all of them, so a deployment wanting Ed25519 only, or wanting to refuse RSASSA-PKCS1-v1_5 while still implementing it, had no way to express that. An unknown alg happened to produce unsupported_algorithm by falling out of the lookup table rather than by policy, and nothing ever reported what would have worked. verify() now takes supportedAlgorithms, defaulting to everything the library implements, exported as SUPPORTED_ALGORITHMS. A key outside the set is rejected before any signature verification -- there is no point verifying with an algorithm that will be declined either way -- and the set comes back on the result as acceptSignatureAlg for the caller to send in an Accept-Signature-Alg response header. It travels on the result rather than inside SignatureError deliberately: -08 removed the supported_algorithms member from Signature-Error, and what a verifier accepts now belongs in Accept-Signature-Alg. A test asserts it does not appear in the error. Also covered the rule that a verifier resolving a key from a JWKS must select by kid without requiring any other member to be usable. The library already behaved this way -- keys.find() only reads kid -- but nothing proved it, and it is one refactor away from being lost by someone validating inside the predicate. Tests place an unparseable ML-DSA key before and after the usable one, and confirm that selecting the ML-DSA key itself declines cleanly rather than crashing. Each JWKS test uses its own issuer origin. The JWKS cache is module-level and keyed by URL, so sharing one origin serves a previous test's keys. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXExbWHeem2SNb7tZ2LGnc
draft-hardt-httpbis-signature-key-08 requires the metadata document at
{id}/.well-known/{dwk} to carry an issuer member equal to the identity
it was fetched under. This library followed jwks_uri without checking.
Without the check, a document served at that location -- through
misconfigured shared hosting, a subdomain takeover, or any other means
-- can point jwks_uri at keys that do not belong to id, and the verifier
attributes the request to the identity in the header. It is the same
check RFC 8414 Section 3.3 requires of authorization server metadata.
Reject a document with no issuer as issuer_missing, and one whose
issuer differs from id as issuer_mismatch, both new error codes in -08.
The comparison is byte equality as presented, with no normalization, so
a trailing slash is a different identifier and is rejected; a test
pins that.
Two existing jwks_uri fixtures served metadata without an issuer and
now fail, correctly. Added it to them.
This is the only normative change in -08 the library had not tracked.
The rest of the revision was already implemented on this branch before
it merged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LXExbWHeem2SNb7tZ2LGnc
alpha.1 predates supportedAlgorithms, the JWKS tolerance tests, and the discovery issuer verification of -08. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LXExbWHeem2SNb7tZ2LGnc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Starts the 2.x line, tracking
draft-hardt-httpbis-signature-key-08(published to the datatracker 2026-08-05). Version2.0.0-alpha.2, published under thealphadist-tag sonpm install @hellocoop/httpsigkeeps giving1.x.145 tests pass. Full caller-facing detail in MIGRATING-2.0.md.
-08is not wire compatible with-05/-06/-07in either direction, and the protocol has no version negotiation, so both ends of a deployment move together.1.xcontinues on the1.xbranch and keepslatest.Algorithm determination, inverted
The algorithm now comes from the JWK
algmember and is never derived fromkty/crv.algis REQUIRED on every conveyed key and must be fully specified (RFC 9864). PolymorphicEdDSAis rejected in favour ofEd25519/Ed448.ktyorcrvdisagrees with itsalgis rejected rather than resolved in favour of either reading.PS256/384/512,RS256/384/512.oct,HS*) is rejected outright.AKPkey type are declined asunsupported_algorithm, not treated as malformed — absence of support is a reason to decline, not a parse failure.generateKeyPair()stampsalg, which WebCrypto does not.Worth noting
kty+crvgenuinely does determine the algorithm for OKP and EC. The requirement is uniform anyway, for negotiation totality and forward compatibility — see the draft's Design Rationale A.11.Wire changes
sig=hwk;kty="OKP";crv="Ed25519";x="..."sig=hwk;alg="Ed25519";kty="OKP";crv="Ed25519";x="..."kid;sigkey=jktparameterAccept-Signature-Scheme/Accept-Signature-Algheader fieldssupported_algorithmsmemberAccept-Signature-Alginvalid_keyunsupported_schemeEnforcement
issuer. Newissuer_missing/issuer_mismatch. 1.x followedjwks_uriwherever the document pointed, so a subdomain takeover or shared-hosting misconfiguration could get requests attributed to an identity that never signed them. Byte equality, no normalization — a trailing slash is a different identifier, and a test pins that.jwtscheme validatesexp, which it previously ignored entirely.strictAAuthremoved — coveringsignature-keyis a specification requirement, not a profile choice.algsignature parameter is ignored. Signers never emit it; verifiers ignore it but still reproduce it in the signature base, since@signature-paramsis covered. Tests sign by hand with a misleadingalgto prove the key decides.supportedAlgorithmslets a verifier declare what it accepts, reported back asacceptSignatureAlgfor anAccept-Signature-Algresponse header — deliberately not insideSignatureError, whosesupported_algorithmsmember-08removed.SignatureVerificationErrorcarries the code instead of leaving it to message matching.Known gaps
jwks,self-jwt,x509not implemented. Additive, not breaking.signature-key. They now assert rejection, which is correct but weaker. Replacement coverage tracked in Publish test vectors for each Signature-Key scheme dickhardt/signature-key#25.Follow-ups
email-verificationpins^1.7.0and will not resolve 2.x — email-verification: conformance work needed for signature-key -07 / httpsig 2.0 #66swift-httpsigneeds the same algorithm-determination change — Track signature-key -07: algorithm determination, hwk alg, and negotiation headers swift-httpsig#1