1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package auth
16
17 import (
18 "context"
19 "fmt"
20 "testing"
21 "time"
22
23 "github.com/golang-jwt/jwt/v4"
24 "github.com/stretchr/testify/require"
25 "go.uber.org/zap"
26 )
27
28 const (
29 jwtRSAPubKey = "../../tests/fixtures/server.crt"
30 jwtRSAPrivKey = "../../tests/fixtures/server.key.insecure"
31
32 jwtECPubKey = "../../tests/fixtures/server-ecdsa.crt"
33 jwtECPrivKey = "../../tests/fixtures/server-ecdsa.key.insecure"
34 )
35
36 func TestJWTInfo(t *testing.T) {
37 optsMap := map[string]map[string]string{
38 "RSA-priv": {
39 "priv-key": jwtRSAPrivKey,
40 "sign-method": "RS256",
41 "ttl": "1h",
42 },
43 "RSA": {
44 "pub-key": jwtRSAPubKey,
45 "priv-key": jwtRSAPrivKey,
46 "sign-method": "RS256",
47 },
48 "RSAPSS-priv": {
49 "priv-key": jwtRSAPrivKey,
50 "sign-method": "PS256",
51 },
52 "RSAPSS": {
53 "pub-key": jwtRSAPubKey,
54 "priv-key": jwtRSAPrivKey,
55 "sign-method": "PS256",
56 },
57 "ECDSA-priv": {
58 "priv-key": jwtECPrivKey,
59 "sign-method": "ES256",
60 },
61 "ECDSA": {
62 "pub-key": jwtECPubKey,
63 "priv-key": jwtECPrivKey,
64 "sign-method": "ES256",
65 },
66 "HMAC": {
67 "priv-key": jwtECPrivKey,
68 "sign-method": "HS256",
69 },
70 }
71
72 for k, opts := range optsMap {
73 t.Run(k, func(tt *testing.T) {
74 testJWTInfo(tt, opts)
75 })
76 }
77 }
78
79 func testJWTInfo(t *testing.T, opts map[string]string) {
80 lg := zap.NewNop()
81 jwt, err := newTokenProviderJWT(lg, opts)
82 if err != nil {
83 t.Fatal(err)
84 }
85
86 ctx := context.TODO()
87
88 token, aerr := jwt.assign(ctx, "abc", 123)
89 if aerr != nil {
90 t.Fatalf("%#v", aerr)
91 }
92 ai, ok := jwt.info(ctx, token, 123)
93 if !ok {
94 t.Fatalf("failed to authenticate with token %s", token)
95 }
96 if ai.Revision != 123 {
97 t.Fatalf("expected revision 123, got %d", ai.Revision)
98 }
99 ai, ok = jwt.info(ctx, "aaa", 120)
100 if ok || ai != nil {
101 t.Fatalf("expected aaa to fail to authenticate, got %+v", ai)
102 }
103
104
105 if opts["pub-key"] != "" && opts["priv-key"] != "" {
106 t.Run("verify-only", func(t *testing.T) {
107 newOpts := make(map[string]string, len(opts))
108 for k, v := range opts {
109 newOpts[k] = v
110 }
111 delete(newOpts, "priv-key")
112 verify, err := newTokenProviderJWT(lg, newOpts)
113 if err != nil {
114 t.Fatal(err)
115 }
116
117 ai, ok := verify.info(ctx, token, 123)
118 if !ok {
119 t.Fatalf("failed to authenticate with token %s", token)
120 }
121 if ai.Revision != 123 {
122 t.Fatalf("expected revision 123, got %d", ai.Revision)
123 }
124 ai, ok = verify.info(ctx, "aaa", 120)
125 if ok || ai != nil {
126 t.Fatalf("expected aaa to fail to authenticate, got %+v", ai)
127 }
128
129 _, aerr := verify.assign(ctx, "abc", 123)
130 if aerr != ErrVerifyOnly {
131 t.Fatalf("unexpected error when attempting to sign with public key: %v", aerr)
132 }
133
134 })
135 }
136 }
137
138 func TestJWTBad(t *testing.T) {
139
140 var badCases = map[string]map[string]string{
141 "no options": {},
142 "invalid method": {
143 "sign-method": "invalid",
144 },
145 "rsa no key": {
146 "sign-method": "RS256",
147 },
148 "invalid ttl": {
149 "sign-method": "RS256",
150 "ttl": "forever",
151 },
152 "rsa invalid public key": {
153 "sign-method": "RS256",
154 "pub-key": jwtRSAPrivKey,
155 "priv-key": jwtRSAPrivKey,
156 },
157 "rsa invalid private key": {
158 "sign-method": "RS256",
159 "pub-key": jwtRSAPubKey,
160 "priv-key": jwtRSAPubKey,
161 },
162 "hmac no key": {
163 "sign-method": "HS256",
164 },
165 "hmac pub key": {
166 "sign-method": "HS256",
167 "pub-key": jwtRSAPubKey,
168 },
169 "missing public key file": {
170 "sign-method": "HS256",
171 "pub-key": "missing-file",
172 },
173 "missing private key file": {
174 "sign-method": "HS256",
175 "priv-key": "missing-file",
176 },
177 "ecdsa no key": {
178 "sign-method": "ES256",
179 },
180 "ecdsa invalid public key": {
181 "sign-method": "ES256",
182 "pub-key": jwtECPrivKey,
183 "priv-key": jwtECPrivKey,
184 },
185 "ecdsa invalid private key": {
186 "sign-method": "ES256",
187 "pub-key": jwtECPubKey,
188 "priv-key": jwtECPubKey,
189 },
190 }
191
192 lg := zap.NewNop()
193
194 for k, v := range badCases {
195 t.Run(k, func(t *testing.T) {
196 _, err := newTokenProviderJWT(lg, v)
197 if err == nil {
198 t.Errorf("expected error for options %v", v)
199 }
200 })
201 }
202 }
203
204
205 func testJWTOpts() string {
206 return fmt.Sprintf("%s,pub-key=%s,priv-key=%s,sign-method=RS256", tokenTypeJWT, jwtRSAPubKey, jwtRSAPrivKey)
207 }
208
209 func TestJWTTokenWithMissingFields(t *testing.T) {
210 testCases := []struct {
211 name string
212 username string
213 revision uint64
214 expectValid bool
215 }{
216 {
217 name: "valid token",
218 username: "hello",
219 revision: 100,
220 expectValid: true,
221 },
222 {
223 name: "no username",
224 username: "",
225 revision: 100,
226 expectValid: false,
227 },
228 {
229 name: "no revision",
230 username: "hello",
231 revision: 0,
232 expectValid: false,
233 },
234 }
235
236 for _, tc := range testCases {
237 tc := tc
238 optsMap := map[string]string{
239 "priv-key": jwtRSAPrivKey,
240 "sign-method": "RS256",
241 "ttl": "1h",
242 }
243
244 t.Run(tc.name, func(t *testing.T) {
245
246 claims := jwt.MapClaims{
247 "exp": time.Now().Add(time.Hour).Unix(),
248 }
249 if tc.username != "" {
250 claims["username"] = tc.username
251 }
252 if tc.revision != 0 {
253 claims["revision"] = tc.revision
254 }
255
256
257 var opts jwtOptions
258 err := opts.ParseWithDefaults(optsMap)
259 require.NoError(t, err)
260 key, err := opts.Key()
261 require.NoError(t, err)
262
263 tk := jwt.NewWithClaims(opts.SignMethod, claims)
264 token, err := tk.SignedString(key)
265 require.NoError(t, err)
266
267
268 jwtProvider, err := newTokenProviderJWT(zap.NewNop(), optsMap)
269 require.NoError(t, err)
270 ai, ok := jwtProvider.info(context.TODO(), token, 123)
271
272 require.Equal(t, tc.expectValid, ok)
273 if ok {
274 require.Equal(t, tc.username, ai.Username)
275 require.Equal(t, tc.revision, ai.Revision)
276 }
277 })
278 }
279 }
280
View as plain text