Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ in the [API reference](https://pkg.go.dev/github.com/yaronf/httpsign).

**v0.6.1** breaks foreign-JWS **verify** again: prefer `NewJWSVerifier(allowed, key, …)` (infer alg); use `NewJWSVerifierWithAlg` when needed; pass a `JWSAlgAllowlist` (`nil` skips policy). `NewJWSSigner` defaults to / requires `SignAlg(false)`; use `NewJWSSignerFromJWK` when the private key is a JWK.

**v0.6.2** restores opaque `crypto.Signer` (HSM/KMS) for foreign JWS RSA/ECDSA/Ed25519 and ML-DSA on sign and verify. See [internal-docs/RELEASE-v0.6.2.md](internal-docs/RELEASE-v0.6.2.md).

| Caller | Change in v0.6.1 |
|--------|------------------|
| Native algorithms only | None. |
Expand All @@ -47,7 +49,11 @@ Full notes: [internal-docs/RELEASE-v0.6.1.md](internal-docs/RELEASE-v0.6.1.md).

Optional algorithms beyond the native set use [`lestrrat-go/jwx/v4`](https://github.com/lestrrat-go/jwx) (≥ v4.5.0) via `NewJWSSigner` / `NewJWSSignerFromJWK` / `NewJWSVerifier`.

**ML-DSA (FIPS 204)** works through the same path with `crypto/mldsa` keys. Prefer inferring the alg from the public key:
For **RSA / ECDSA / Ed25519 / ML-DSA**, `NewJWSSigner` and `NewJWSVerifierWithAlg` accept either raw stdlib keys or an opaque [`crypto.Signer`](https://pkg.go.dev/crypto#Signer) (HSM/KMS) whose `Public()` matches the algorithm. Verify via `crypto.Signer` uses `Public()` locally; you can also pass the exported public key. Opaque Signers must use `NewJWSVerifierWithAlg` — the preferred infer path (`NewJWSVerifier`) does not recognize them.

Default jwx ML-DSA signing still requires `*mldsa.PrivateKey`. For an opaque ML-DSA Signer (e.g. KMS), register a custom `jws.Signer` with [`jws.RegisterSigner`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v4/jws#RegisterSigner) that calls `crypto.Signer.Sign` (prefer also handling raw `*mldsa.PrivateKey` if other code in the process needs it). ML-DSA remains foreign-JWS only — not a native RFC 9421 algorithm.

**ML-DSA (FIPS 204)** with raw keys — prefer inferring the alg from the public key:

```go
priv, _ := mldsa.GenerateKey(mldsa.MLDSA65())
Expand Down
6 changes: 5 additions & 1 deletion crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ func NewEd25519SignerFromSeed(seed []byte, config *SignConfig, fields Fields) (*
// NewJWSSigner creates a generic signer for JWS algorithms via github.com/lestrrat-go/jwx/v4.
// The particular key type for each algorithm is documented in that package (including
// crypto/mldsa keys for ML-DSA on Go 1.27+). HMAC keys must be []byte (not string).
// Classical RSA/ECDSA/Ed25519 and ML-DSA also accept an opaque crypto.Signer (HSM/KMS) whose
// Public() matches the algorithm. Default jwx ML-DSA signing still needs *mldsa.PrivateKey;
// opaque ML-DSA Signers require a custom jws.RegisterSigner (see README).
// Do not pass jwk.Key here — use NewJWSSignerFromJWK so alg can be taken from the JWK.
// Config may be nil (defaults with SignAlg(false)). A non-nil config that would emit
// HTTP Signature-Input "alg" is rejected — foreign JWS has no RFC 9421 algorithm id.
Expand Down Expand Up @@ -368,7 +371,8 @@ func NewJWSVerifier(allowed *JWSAlgAllowlist, key any, config *VerifyConfig, fie

// NewJWSVerifierWithAlg creates a foreign-JWS verifier for an explicit JWS algorithm.
// Use when the algorithm cannot be inferred (raw RSA/HMAC) or the store already chose alg.
// Do not pass jwk.Key here — use NewJWSVerifier so alg can be taken from the JWK.
// Classical RSA/ECDSA/Ed25519 and ML-DSA accept raw public keys or an opaque crypto.Signer (local verify
// via Public()). Do not pass jwk.Key here — use NewJWSVerifier so alg can be taken from the JWK.
// allowed may be nil to skip alg policy.
func NewJWSVerifierWithAlg(allowed *JWSAlgAllowlist, alg jwa.SignatureAlgorithm, key any, config *VerifyConfig, fields Fields) (*Verifier, error) {
if key == nil {
Expand Down
5 changes: 4 additions & 1 deletion internal-docs/JWS-ALG-POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,14 @@ NewJWSSignerFromJWK(key jwk.Key, config *SignConfig, fields Fields) (*Signer, er
2. Resolve alg via jwx registry (`LookupSignatureAlgorithm`); reject empty / unregistered / `none`. Nil key rejected.
3. Existing **`validateJWSKeyAlg`**; default “unsupported” → fall through to **`jws.VerifierFor(alg)`** (jwx).
4. Reject raw `jwk.Key` here — use preferred `NewJWSVerifier` (or convert).
5. Classical RSA/ECDSA/Ed25519 and ML-DSA: raw keys **or** opaque `crypto.Signer` (local verify via `Public()`). Default jwx ML-DSA Sign still needs `*mldsa.PrivateKey` unless the caller `jws.RegisterSigner`s a custom signer.

**`NewJWSSigner`:** hardening + passthrough; **no** allowlist; force `SignAlg(false)`. Reject raw `jwk.Key` — use `NewJWSSignerFromJWK`.
**`NewJWSSigner`:** hardening + passthrough; **no** allowlist; force `SignAlg(false)`. Reject raw `jwk.Key` — use `NewJWSSignerFromJWK`. Classical RSA/ECDSA/Ed25519 and ML-DSA accept raw keys **or** opaque `crypto.Signer` (HSM/KMS).

**`NewJWSSignerFromJWK`:** require a **private** JWK (symmetric `oct` OK); resolve alg with the same table / EdDSA↔Ed25519 rules as verify; export raw **private** material; then `NewJWSSigner`.

**`crypto.Signer` note:** same trade-off as jwx — external Signers make alg/key-type misuse harder to detect than in-process stdlib keys. `Public()` panics (e.g. malformed ed25519) are recovered at construction as validation errors. Opaque ML-DSA Signers additionally need `jws.RegisterSigner` for Sign under stock jwx.

Do **not** put the JWS allowlist on `VerifyConfig`.

**Honesty about crypto quality:** hardening ≠ interop. No closed “supported algorithms” docs matrix.
Expand Down
33 changes: 33 additions & 0 deletions internal-docs/RELEASE-v0.6.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Release notes: httpsign v0.6.2 (draft)

Maintainer draft for the next patch after **v0.6.1**. Tag only when ready.

---

## Summary

**Additive compatibility restore** for foreign JWS (native signers unchanged). Builds on **v0.6.1**.

### Highlights

- **`crypto.Signer` (HSM/KMS)** accepted again for foreign JWS **RSA / ECDSA / Ed25519 / ML-DSA** on both `NewJWSSigner` and `NewJWSVerifierWithAlg` (and verify paths that share `validateJWSKeyAlg`).
- Validation uses `Public()` shape + existing curve / ML-DSA parameter-set checks; panics from `Public()` are recovered as construction errors.
- **ML-DSA note:** default jwx still signs only with `*mldsa.PrivateKey`. Opaque ML-DSA Signers need a custom [`jws.RegisterSigner`](https://pkg.go.dev/github.com/lestrrat-go/jwx/v4/jws#RegisterSigner). ML-DSA stays foreign-JWS only (not a native RFC 9421 algorithm).
- **JWK** still rejected in raw constructors (`NewJWSSignerFromJWK` / preferred `NewJWSVerifier`).

### Upgrade from v0.6.1

| You use | Action |
|---------|--------|
| **Native only / raw stdlib JWS keys** | No change. |
| **Opaque `crypto.Signer` for RS\*/PS\*/ES\*/EdDSA** | Works again (was rejected at construction in v0.6.0–v0.6.1). |
| **Opaque `crypto.Signer` for ML-DSA** | Construction accepted; register a custom jwx ML-DSA signer for Sign, or keep using raw `*mldsa.PrivateKey`. |
| **JWK in `NewJWSSigner`** | Unchanged — use `NewJWSSignerFromJWK`. |

### Backward compatibility

This partially rolls back v0.6’s “raw stdlib only” foreign-JWS key policy. Callers already using raw keys are unaffected. Same caveat as jwx: external `crypto.Signer` makes alg/key misuse harder to catch than in-process stdlib types.

Fixes [#34](https://github.com/yaronf/httpsign/issues/34).

Thanks to [@ilya-korotya](https://github.com/ilya-korotya) for reporting the regression and for the draft fix in [#35](https://github.com/yaronf/httpsign/pull/35).
Loading
Loading