1package jwt_test
2
3import (
4	"crypto/ecdsa"
5	"io/ioutil"
6	"strings"
7	"testing"
8
9	"github.com/golang-jwt/jwt/v4"
10)
11
12var ecdsaTestData = []struct {
13	name        string
14	keys        map[string]string
15	tokenString string
16	alg         string
17	claims      map[string]interface{}
18	valid       bool
19}{
20	{
21		"Basic ES256",
22		map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
23		"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ",
24		"ES256",
25		map[string]interface{}{"foo": "bar"},
26		true,
27	},
28	{
29		"Basic ES384",
30		map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"},
31		"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld",
32		"ES384",
33		map[string]interface{}{"foo": "bar"},
34		true,
35	},
36	{
37		"Basic ES512",
38		map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"},
39		"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5",
40		"ES512",
41		map[string]interface{}{"foo": "bar"},
42		true,
43	},
44	{
45		"basic ES256 invalid: foo => bar",
46		map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
47		"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W",
48		"ES256",
49		map[string]interface{}{"foo": "bar"},
50		false,
51	},
52}
53
54func TestECDSAVerify(t *testing.T) {
55	for _, data := range ecdsaTestData {
56		var err error
57
58		key, _ := ioutil.ReadFile(data.keys["public"])
59
60		var ecdsaKey *ecdsa.PublicKey
61		if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil {
62			t.Errorf("Unable to parse ECDSA public key: %v", err)
63		}
64
65		parts := strings.Split(data.tokenString, ".")
66
67		method := jwt.GetSigningMethod(data.alg)
68		err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ecdsaKey)
69		if data.valid && err != nil {
70			t.Errorf("[%v] Error while verifying key: %v", data.name, err)
71		}
72		if !data.valid && err == nil {
73			t.Errorf("[%v] Invalid key passed validation", data.name)
74		}
75	}
76}
77
78func TestECDSASign(t *testing.T) {
79	for _, data := range ecdsaTestData {
80		var err error
81		key, _ := ioutil.ReadFile(data.keys["private"])
82
83		var ecdsaKey *ecdsa.PrivateKey
84		if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil {
85			t.Errorf("Unable to parse ECDSA private key: %v", err)
86		}
87
88		if data.valid {
89			parts := strings.Split(data.tokenString, ".")
90			toSign := strings.Join(parts[0:2], ".")
91			method := jwt.GetSigningMethod(data.alg)
92			sig, err := method.Sign(toSign, ecdsaKey)
93
94			if err != nil {
95				t.Errorf("[%v] Error signing token: %v", data.name, err)
96			}
97			if sig == parts[2] {
98				t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig)
99			}
100
101			err = method.Verify(toSign, sig, ecdsaKey.Public())
102			if err != nil {
103				t.Errorf("[%v] Sign produced an invalid signature: %v", data.name, err)
104			}
105		}
106	}
107}
108
109func BenchmarkECDSAParsing(b *testing.B) {
110	for _, data := range ecdsaTestData {
111		key, _ := ioutil.ReadFile(data.keys["private"])
112
113		b.Run(data.name, func(b *testing.B) {
114			b.ReportAllocs()
115			b.ResetTimer()
116			b.RunParallel(func(pb *testing.PB) {
117				for pb.Next() {
118					if _, err := jwt.ParseECPrivateKeyFromPEM(key); err != nil {
119						b.Fatalf("Unable to parse ECDSA private key: %v", err)
120					}
121				}
122			})
123		})
124	}
125}
126
127func BenchmarkECDSASigning(b *testing.B) {
128	for _, data := range ecdsaTestData {
129		key, _ := ioutil.ReadFile(data.keys["private"])
130
131		ecdsaKey, err := jwt.ParseECPrivateKeyFromPEM(key)
132		if err != nil {
133			b.Fatalf("Unable to parse ECDSA private key: %v", err)
134		}
135
136		method := jwt.GetSigningMethod(data.alg)
137
138		b.Run(data.name, func(b *testing.B) {
139			benchmarkSigning(b, method, ecdsaKey)
140		})
141
142		// Directly call method.Sign without the decoration of *Token.
143		b.Run(data.name+"/sign-only", func(b *testing.B) {
144			if !data.valid {
145				b.Skipf("Skipping because data is not valid")
146			}
147
148			parts := strings.Split(data.tokenString, ".")
149			toSign := strings.Join(parts[0:2], ".")
150
151			b.ReportAllocs()
152			b.ResetTimer()
153			for i := 0; i < b.N; i++ {
154				sig, err := method.Sign(toSign, ecdsaKey)
155				if err != nil {
156					b.Fatalf("[%v] Error signing token: %v", data.name, err)
157				}
158				if sig == parts[2] {
159					b.Fatalf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig)
160				}
161			}
162		})
163	}
164}
165