From bf6927a24ab828d0d99d824cb268ecb4e492f94b Mon Sep 17 00:00:00 2001 From: ilya-korotya Date: Tue, 29 Sep 2026 13:20:23 +0200 Subject: [PATCH 1/2] support external key providers for ecdsa and mldsa --- jwskey.go | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/jwskey.go b/jwskey.go index c1ab7ec..770487b 100644 --- a/jwskey.go +++ b/jwskey.go @@ -1,6 +1,7 @@ package httpsign import ( + "crypto" "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" @@ -14,8 +15,10 @@ import ( // validateJWSKeyAlg checks that key is an appropriate Go type for alg, without using // jws.AlgorithmsForKey (deprecated; not a compatibility API; over-broad for ECDSA/ML-DSA). // When signing is true, asymmetric keys must be private; when false, they must be public. -// HMAC keys are symmetric and accepted for either role. Only raw stdlib key types are -// accepted; crypto.Signer / JWK wrappers are rejected here so callers convert first. +// HMAC keys are symmetric and accepted for either role. Raw stdlib key types are +// accepted, and so is any opaque crypto.Signer (HSM/KMS-backed keys, etc.) whose +// Public() reports the expected key shape — see ecdsaKeyOf/mldsaKeyOf. JWK wrappers +// are still rejected here so callers convert first. func validateJWSKeyAlg(alg jwa.SignatureAlgorithm, key any, signing bool) error { switch alg { case jwa.HS256(), jwa.HS384(), jwa.HS512(): @@ -137,6 +140,17 @@ func ecdsaKeyOf(key any) (curve elliptic.Curve, isPrivate, ok bool) { return k.Curve, false, true case ecdsa.PublicKey: return k.Curve, false, true + case crypto.Signer: + // An opaque signer (HSM, KMS, or any remote/hardware-backed key) + // that implements the standard interface without being one of the + // concrete stdlib types above. Its Public() method determines the + // curve, so the private key material never has to be extracted — + // or even exist locally. + pub, isECDSA := k.Public().(*ecdsa.PublicKey) + if !isECDSA || pub == nil { + return nil, false, false + } + return pub.Curve, true, true default: return nil, false, false } @@ -220,6 +234,17 @@ func mldsaKeyOf(key any) (params mldsa.Parameters, isPrivate, ok bool) { return mldsa.Parameters{}, false, false } return k.Parameters(), false, true + case crypto.Signer: + // An opaque signer (HSM, KMS, or any remote/hardware-backed key) + // that implements the standard interface without being a literal + // *mldsa.PrivateKey. Its Public() method determines the parameter + // set, so the private key material never has to be extracted — or + // even exist locally. + pub, isMLDSA := k.Public().(*mldsa.PublicKey) + if !isMLDSA || pub == nil { + return mldsa.Parameters{}, false, false + } + return pub.Parameters(), true, true default: return mldsa.Parameters{}, false, false } From 79d6a216ca587aef3d02c3af1f590f87e920f7df Mon Sep 17 00:00:00 2001 From: ilya-korotya Date: Tue, 29 Sep 2026 13:22:12 +0200 Subject: [PATCH 2/2] cut comments --- jwskey.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/jwskey.go b/jwskey.go index 770487b..7f81cad 100644 --- a/jwskey.go +++ b/jwskey.go @@ -141,11 +141,6 @@ func ecdsaKeyOf(key any) (curve elliptic.Curve, isPrivate, ok bool) { case ecdsa.PublicKey: return k.Curve, false, true case crypto.Signer: - // An opaque signer (HSM, KMS, or any remote/hardware-backed key) - // that implements the standard interface without being one of the - // concrete stdlib types above. Its Public() method determines the - // curve, so the private key material never has to be extracted — - // or even exist locally. pub, isECDSA := k.Public().(*ecdsa.PublicKey) if !isECDSA || pub == nil { return nil, false, false @@ -235,11 +230,6 @@ func mldsaKeyOf(key any) (params mldsa.Parameters, isPrivate, ok bool) { } return k.Parameters(), false, true case crypto.Signer: - // An opaque signer (HSM, KMS, or any remote/hardware-backed key) - // that implements the standard interface without being a literal - // *mldsa.PrivateKey. Its Public() method determines the parameter - // set, so the private key material never has to be extracted — or - // even exist locally. pub, isMLDSA := k.Public().(*mldsa.PublicKey) if !isMLDSA || pub == nil { return mldsa.Parameters{}, false, false