package authjwt import ( "crypto/ecdsa" "crypto/x509" "encoding/pem" "fmt" "github.com/golang-jwt/jwt/v5" ) var _ Signer = (*ecSigner)(nil) type ecSigner struct { private *ecdsa.PrivateKey public *ecdsa.PublicKey } // NewECSigner returns a Signer backed by ECDSA. // The signing algorithm is auto-detected from the key's curve: // P-256→ES256, P-384→ES384, P-521→ES512. func NewECSigner(privateKey *ecdsa.PrivateKey) Signer { return &ecSigner{private: privateKey, public: &privateKey.PublicKey} } // NewECSignerFromPEM parses a PKCS#8 PEM-encoded ECDSA private key. func NewECSignerFromPEM(pemKey []byte) (Signer, error) { block, _ := pem.Decode(pemKey) if block == nil { return nil, fmt.Errorf("no PEM block found") } key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { return nil, fmt.Errorf("parse EC private key: %w", err) } ecKey, ok := key.(*ecdsa.PrivateKey) if !ok { return nil, fmt.Errorf("PEM key is not an EC private key") } return NewECSigner(ecKey), nil } func (s *ecSigner) Sign(claims jwt.Claims) (string, error) { return jwt.NewWithClaims(ecAlg(s.private), claims).SignedString(s.private) } func (s *ecSigner) Verify(tokenString string) (*jwt.Token, error) { alg := ecAlg(s.private) return jwt.Parse(tokenString, func(t *jwt.Token) (any, error) { if t.Method.Alg() != alg.Alg() { return nil, fmt.Errorf("unexpected signing method %q", t.Header["alg"]) } return s.public, nil }, jwt.WithJSONNumber()) } func ecAlg(key *ecdsa.PrivateKey) *jwt.SigningMethodECDSA { switch key.Curve.Params().Name { case "P-384": return jwt.SigningMethodES384 case "P-521": return jwt.SigningMethodES512 default: return jwt.SigningMethodES256 } }