1package main
2
3import (
4 "crypto/ecdsa"
5 "crypto/sha256"
6 "crypto/x509"
7 "encoding/base64"
8 "encoding/json"
9 "encoding/pem"
10 "errors"
11 "fmt"
12 "math/big"
13 "strings"
14)
15
16func verifyES256(token string, publicKeyPEM []byte) (map[string]any, error) {
17 parts := strings.Split(token, ".")
18 if len(parts) != 3 {
19 return nil, errors.New("not a compact JWS token")
20 }
21
22 headerJSON, err := base64.RawURLEncoding.DecodeString(parts[0])
23 if err != nil {
24 return nil, err
25 }
26 var header struct {
27 Alg string `json:"alg"`
28 }
29 if err := json.Unmarshal(headerJSON, &header); err != nil {
30 return nil, err
31 }
32 if header.Alg != "ES256" {
33 return nil, fmt.Errorf("expected ES256, got %s", header.Alg)
34 }
35
36 block, _ := pem.Decode(publicKeyPEM)
37 pubAny, err := x509.ParsePKIXPublicKey(block.Bytes)
38 if err != nil {
39 return nil, err
40 }
41 pub := pubAny.(*ecdsa.PublicKey)
42
43 sig, err := base64.RawURLEncoding.DecodeString(parts[2])
44 if err != nil || len(sig) != 64 {
45 return nil, errors.New("signature must be 64 raw bytes (r‖s)")
46 }
47 r := new(big.Int).SetBytes(sig[:32])
48 s := new(big.Int).SetBytes(sig[32:])
49
50 digest := sha256.Sum256([]byte(parts[0] + "." + parts[1]))
51 if !ecdsa.Verify(pub, digest[:], r, s) {
52 return nil, errors.New("invalid signature")
53 }
54
55 claims := map[string]any{}
56 payloadJSON, err := base64.RawURLEncoding.DecodeString(parts[1])
57 if err != nil {
58 return nil, err
59 }
60 if err := json.Unmarshal(payloadJSON, &claims); err != nil {
61 return nil, err
62 }
63 return claims, nil // exp / iss / aud checks are still your job
64}