mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-11-04 07:53:59 +00:00
fix(jwt): bind ECDSA curve to the algorithm and require fixed signature length
ES256, ES384 and ES512 verified against any curve and any even-length signature. RFC 7518 fixes the curve per algorithm and the signature to R||S at the curve's octet length. Verification now enforces both.
This commit is contained in:
@@ -322,6 +322,9 @@ func verifyTimeClaims(claims map[string]interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ecdsaAlgBits maps an ES* algorithm to its curve size (RFC 7518 3.4).
|
||||||
|
var ecdsaAlgBits = map[string]int{"ES256": 256, "ES384": 384, "ES512": 521}
|
||||||
|
|
||||||
// verifySignatureWithKey verifies a JWT signature using an already-parsed
|
// verifySignatureWithKey verifies a JWT signature using an already-parsed
|
||||||
// public key, skipping the PEM-encode/decode round trip. This is the hot
|
// public key, skipping the PEM-encode/decode round trip. This is the hot
|
||||||
// path used by VerifyJWTSignatureAndClaims and verifyLogoutTokenSignature.
|
// path used by VerifyJWTSignatureAndClaims and verifyLogoutTokenSignature.
|
||||||
@@ -369,7 +372,13 @@ func verifySignatureWithKey(tokenString string, pubKey crypto.PublicKey, alg str
|
|||||||
if strings.HasPrefix(alg, "ES") {
|
if strings.HasPrefix(alg, "ES") {
|
||||||
var r, s big.Int
|
var r, s big.Int
|
||||||
sigLen := len(signature)
|
sigLen := len(signature)
|
||||||
if sigLen%2 != 0 {
|
// RFC 7518 3.4: the curve is fixed by the algorithm and the
|
||||||
|
// signature is R||S at exactly the curve's octet length.
|
||||||
|
bits := ecdsaAlgBits[alg]
|
||||||
|
if bits == 0 || pubKey.Curve.Params().BitSize != bits {
|
||||||
|
return fmt.Errorf("ECDSA key curve does not match algorithm %s", alg)
|
||||||
|
}
|
||||||
|
if sigLen != 2*((bits+7)/8) {
|
||||||
return fmt.Errorf("invalid ECDSA signature length")
|
return fmt.Errorf("invalid ECDSA signature length")
|
||||||
}
|
}
|
||||||
r.SetBytes(signature[:sigLen/2])
|
r.SetBytes(signature[:sigLen/2])
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package traefikoidc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
"encoding/base64"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func signES(t *testing.T, key *ecdsa.PrivateKey, signed string, h256 bool, size int) (sigRS []byte) {
|
||||||
|
t.Helper()
|
||||||
|
var digest []byte
|
||||||
|
if h256 {
|
||||||
|
d := sha256.Sum256([]byte(signed))
|
||||||
|
digest = d[:]
|
||||||
|
} else {
|
||||||
|
d := sha512.Sum384([]byte(signed))
|
||||||
|
digest = d[:]
|
||||||
|
}
|
||||||
|
r, s, err := ecdsa.Sign(rand.Reader, key, digest)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
out := make([]byte, 2*size)
|
||||||
|
r.FillBytes(out[:size])
|
||||||
|
s.FillBytes(out[size:])
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestECDSAVerifyIsStrict(t *testing.T) {
|
||||||
|
const signed = "eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiJ1In0"
|
||||||
|
enc := base64.RawURLEncoding.EncodeToString
|
||||||
|
p256, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
|
p384, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
|
|
||||||
|
if err := verifySignatureWithKey(signed+"."+enc(signES(t, p256, signed, true, 32)), &p256.PublicKey, "ES256"); err != nil {
|
||||||
|
t.Fatalf("valid ES256 rejected: %v", err)
|
||||||
|
}
|
||||||
|
if err := verifySignatureWithKey(signed+"."+enc(signES(t, p384, signed, false, 48)), &p384.PublicKey, "ES384"); err != nil {
|
||||||
|
t.Fatalf("valid ES384 rejected: %v", err)
|
||||||
|
}
|
||||||
|
// ES256 token verified against a P-384 key.
|
||||||
|
if err := verifySignatureWithKey(signed+"."+enc(signES(t, p384, signed, true, 48)), &p384.PublicKey, "ES256"); err == nil {
|
||||||
|
t.Error("ES256 accepted with a P-384 key")
|
||||||
|
}
|
||||||
|
// Zero-padded halves: 66 bytes for ES256.
|
||||||
|
if err := verifySignatureWithKey(signed+"."+enc(signES(t, p256, signed, true, 33)), &p256.PublicKey, "ES256"); err == nil {
|
||||||
|
t.Error("ES256 accepted a 66-byte signature")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user