xref: /aosp_15_r20/external/tink/go/jwt/jwt_validator_test.go (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15////////////////////////////////////////////////////////////////////////////////
16
17package jwt_test
18
19import (
20	"testing"
21	"time"
22
23	"github.com/google/tink/go/jwt"
24)
25
26type validationTestCase struct {
27	tag           string
28	tokenOpts     *jwt.RawJWTOptions
29	validatorOpts *jwt.ValidatorOpts
30}
31
32func TestNewValidatorFailure(t *testing.T) {
33	for _, tc := range []validationTestCase{
34		{
35			tag: "combining ExpectedTypeHeader and IgnoreTypeHeader",
36			validatorOpts: &jwt.ValidatorOpts{
37				ExpectedTypeHeader: refString("should fail"),
38				IgnoreTypeHeader:   true,
39			},
40		},
41		{
42			tag: "combining ExpectedIssuer and IgnoreIssuer",
43			validatorOpts: &jwt.ValidatorOpts{
44				ExpectedIssuer: refString("should fail"),
45				IgnoreIssuer:   true,
46			},
47		},
48		{
49			tag: "combining ExpectedAudience and IgnoreAudiences",
50			validatorOpts: &jwt.ValidatorOpts{
51				ExpectedAudience: refString("should fail"),
52				IgnoreAudiences:  true,
53			},
54		},
55		{
56			tag: "combining ExpectedAudiences and IgnoreAudiences",
57			validatorOpts: &jwt.ValidatorOpts{
58				ExpectedAudiences: refString("should fail"),
59				IgnoreAudiences:   true,
60			},
61		},
62		{
63			tag: "both ExpectedAudience and ExpectedAudiences are set",
64			validatorOpts: &jwt.ValidatorOpts{
65				ExpectedAudience:  refString("aud"),
66				ExpectedAudiences: refString("aud"),
67			},
68		},
69		{
70			tag: "invalid clock skew",
71			validatorOpts: &jwt.ValidatorOpts{
72				ClockSkew: time.Minute * 11,
73			},
74		},
75		{
76			tag: "validator opts can't be nil",
77		},
78	} {
79		t.Run(tc.tag, func(t *testing.T) {
80			if _, err := jwt.NewValidator(tc.validatorOpts); err == nil {
81				t.Errorf("NewValidator(%v) err = nil, want error", tc.validatorOpts)
82			}
83		})
84	}
85}
86
87func TestValidationFailures(t *testing.T) {
88	for _, tc := range []validationTestCase{
89		{
90			tag: "expired token",
91			tokenOpts: &jwt.RawJWTOptions{
92				ExpiresAt: refTime(100),
93			},
94			validatorOpts: &jwt.ValidatorOpts{
95				FixedNow: time.Unix(500, 0),
96			},
97		},
98		{
99			tag: "no expiration and AllowMissingExpiration = false",
100			tokenOpts: &jwt.RawJWTOptions{
101				WithoutExpiration: true,
102			},
103			validatorOpts: &jwt.ValidatorOpts{},
104		},
105		{
106			tag: "token expiry equals current time",
107			tokenOpts: &jwt.RawJWTOptions{
108				ExpiresAt: refTime(123),
109			},
110			validatorOpts: &jwt.ValidatorOpts{
111				FixedNow: time.Unix(123, 0),
112			},
113		},
114		{
115			tag: "not before in the future",
116			tokenOpts: &jwt.RawJWTOptions{
117				WithoutExpiration: true,
118				NotBefore:         refTime(1500),
119			},
120			validatorOpts: &jwt.ValidatorOpts{
121				AllowMissingExpiration: true,
122				FixedNow:               time.Unix(1000, 0),
123			},
124		},
125		{
126			tag: "issued in the future with ExpectIssuedInThePast = true",
127			tokenOpts: &jwt.RawJWTOptions{
128				WithoutExpiration: true,
129				IssuedAt:          refTime(5000),
130			},
131			validatorOpts: &jwt.ValidatorOpts{
132				AllowMissingExpiration: true,
133				FixedNow:               time.Unix(1000, 0),
134				ExpectIssuedInThePast:  true,
135			},
136		},
137		{
138			tag: "without issued at with ExpectIssuedInThePast = true",
139			tokenOpts: &jwt.RawJWTOptions{
140				WithoutExpiration: true,
141			},
142			validatorOpts: &jwt.ValidatorOpts{
143				AllowMissingExpiration: true,
144				ExpectIssuedInThePast:  true,
145			},
146		},
147		{
148			tag: "no type header and RequiresTypeHeader = true",
149			tokenOpts: &jwt.RawJWTOptions{
150				WithoutExpiration: true,
151			},
152			validatorOpts: &jwt.ValidatorOpts{
153				AllowMissingExpiration: true,
154				ExpectedTypeHeader:     refString("typeHeader"),
155			},
156		},
157		{
158			tag: "invalid type header",
159			tokenOpts: &jwt.RawJWTOptions{
160				WithoutExpiration: true,
161				TypeHeader:        refString("typeHeader"),
162			},
163			validatorOpts: &jwt.ValidatorOpts{
164				AllowMissingExpiration: true,
165				ExpectedTypeHeader:     refString("different"),
166			},
167		},
168		{
169			tag: "type header in token but no type header in validator",
170			tokenOpts: &jwt.RawJWTOptions{
171				WithoutExpiration: true,
172				TypeHeader:        refString("typeHeader"),
173			},
174			validatorOpts: &jwt.ValidatorOpts{
175				AllowMissingExpiration: true,
176			},
177		},
178		{
179			tag: "issuer required but not specified",
180			tokenOpts: &jwt.RawJWTOptions{
181				WithoutExpiration: true,
182			},
183			validatorOpts: &jwt.ValidatorOpts{
184				AllowMissingExpiration: true,
185				ExpectedIssuer:         refString("tink-issuer"),
186			},
187		},
188		{
189			tag: "invalid issuer",
190			tokenOpts: &jwt.RawJWTOptions{
191				WithoutExpiration: true,
192				Issuer:            refString("tink-issuer"),
193			},
194			validatorOpts: &jwt.ValidatorOpts{
195				AllowMissingExpiration: true,
196				ExpectedIssuer:         refString("different"),
197			},
198		},
199		{
200			tag: "issuer in token but not in validator",
201			tokenOpts: &jwt.RawJWTOptions{
202				WithoutExpiration: true,
203				Issuer:            refString("issuer"),
204			},
205			validatorOpts: &jwt.ValidatorOpts{
206				AllowMissingExpiration: true,
207			},
208		},
209		{
210			tag: "audience required but no specified",
211			tokenOpts: &jwt.RawJWTOptions{
212				WithoutExpiration: true,
213			},
214			validatorOpts: &jwt.ValidatorOpts{
215				AllowMissingExpiration: true,
216				ExpectedAudience:       refString("tink-audience"),
217			},
218		},
219		{
220			tag: "audience required but no specified, deprecated",
221			tokenOpts: &jwt.RawJWTOptions{
222				WithoutExpiration: true,
223			},
224			validatorOpts: &jwt.ValidatorOpts{
225				AllowMissingExpiration: true,
226				ExpectedAudiences:      refString("tink-audience"),
227			},
228		},
229		{
230			tag: "invalid audience",
231			tokenOpts: &jwt.RawJWTOptions{
232				WithoutExpiration: true,
233				Audience:          refString("tink-audience"),
234			},
235			validatorOpts: &jwt.ValidatorOpts{
236				AllowMissingExpiration: true,
237				ExpectedAudience:       refString("audience"),
238			},
239		},
240		{
241			tag: "invalid audience, deprecated",
242			tokenOpts: &jwt.RawJWTOptions{
243				WithoutExpiration: true,
244				Audience:          refString("tink-audience"),
245			},
246			validatorOpts: &jwt.ValidatorOpts{
247				AllowMissingExpiration: true,
248				ExpectedAudiences:      refString("audience"),
249			},
250		},
251		{
252			tag: "audience in token but not in validator",
253			tokenOpts: &jwt.RawJWTOptions{
254				WithoutExpiration: true,
255				Audience:          refString("audience"),
256			},
257			validatorOpts: &jwt.ValidatorOpts{
258				AllowMissingExpiration: true,
259			},
260		},
261	} {
262
263		t.Run(tc.tag, func(t *testing.T) {
264			token, err := jwt.NewRawJWT(tc.tokenOpts)
265			if err != nil {
266				t.Fatalf("jwt.NewRawJWT(%v) err = %v, want nil", tc.tokenOpts, err)
267			}
268			validator, err := jwt.NewValidator(tc.validatorOpts)
269			if err != nil {
270				t.Fatalf("jwt.NewValidator(%v) err = %v, want nil", tc.validatorOpts, err)
271			}
272			if err := validator.Validate(token); err == nil {
273				t.Errorf("validator.Validate(%v) err = nil, want error", token)
274			}
275		})
276	}
277}
278
279func TestExpiredTokenValidationReturnsExpiredErr(t *testing.T) {
280	tokenOpts := &jwt.RawJWTOptions{
281		ExpiresAt: refTime(100),
282	}
283	expiredToken, err := jwt.NewRawJWT(tokenOpts)
284	if err != nil {
285		t.Fatalf("jwt.NewRawJWT(tokenOpts) err = %v, want nil", err)
286	}
287	validatorOpts := &jwt.ValidatorOpts{
288		FixedNow: time.Unix(500, 0),
289	}
290	validator, err := jwt.NewValidator(validatorOpts)
291	if err != nil {
292		t.Fatalf("jwt.NewValidator(validatorOpts) err = %v, want nil", err)
293	}
294
295	validationErr := validator.Validate(expiredToken)
296	if validationErr == nil {
297		t.Errorf("validator.Validate(expiredToken) err = nil, want error")
298	}
299	if !jwt.IsExpirationErr(validationErr) {
300		t.Errorf("jwt.IsExpirationErr(validationErr) = false, want true")
301	}
302}
303
304func TestExpirationGetsValidatedFirst(t *testing.T) {
305	tokenOpts := &jwt.RawJWTOptions{
306		ExpiresAt: refTime(100),
307		Audience:  refString("invalidAudience"),
308	}
309	expiredTokenWithInvalidAudience, err := jwt.NewRawJWT(tokenOpts)
310	if err != nil {
311		t.Fatalf("jwt.NewRawJWT(tokenOpts) err = %v, want nil", err)
312	}
313	validatorOpts := &jwt.ValidatorOpts{
314		ExpectedAudiences: refString("audience"),
315		FixedNow:          time.Unix(500, 0),
316	}
317	validator, err := jwt.NewValidator(validatorOpts)
318	if err != nil {
319		t.Fatalf("jwt.NewValidator(validatorOpts) err = %v, want nil", err)
320	}
321
322	validationErr := validator.Validate(expiredTokenWithInvalidAudience)
323	if validationErr == nil {
324		t.Errorf("validator.Validate(expiredTokenWithInvalidAudience) err = nil, want error")
325	}
326	if !jwt.IsExpirationErr(validationErr) {
327		t.Errorf("jwt.IsExpirationErr(validationErr) = false, want true")
328	}
329}
330
331func TestValidationSuccess(t *testing.T) {
332	for _, tc := range []validationTestCase{
333		{
334			tag: "unexpired token",
335			tokenOpts: &jwt.RawJWTOptions{
336				ExpiresAt: refTime(1000),
337			},
338			validatorOpts: &jwt.ValidatorOpts{
339				FixedNow: time.Unix(100, 0),
340			},
341		},
342		{
343			tag: "expired with clock slew",
344			tokenOpts: &jwt.RawJWTOptions{
345				ExpiresAt: refTime(400),
346			},
347			validatorOpts: &jwt.ValidatorOpts{
348				FixedNow:  time.Unix(500, 0),
349				ClockSkew: time.Second * 200,
350			},
351		},
352		{
353			tag: "not before in the past",
354			tokenOpts: &jwt.RawJWTOptions{
355				WithoutExpiration: true,
356				NotBefore:         refTime(500),
357			},
358			validatorOpts: &jwt.ValidatorOpts{
359				AllowMissingExpiration: true,
360				FixedNow:               time.Unix(1000, 0),
361			},
362		},
363		{
364			tag: "not before equals now",
365			tokenOpts: &jwt.RawJWTOptions{
366				WithoutExpiration: true,
367				NotBefore:         refTime(500),
368			},
369			validatorOpts: &jwt.ValidatorOpts{
370				AllowMissingExpiration: true,
371				FixedNow:               time.Unix(500, 0),
372			},
373		},
374		{
375			tag: "not before in near future with clock skew",
376			tokenOpts: &jwt.RawJWTOptions{
377				WithoutExpiration: true,
378				NotBefore:         refTime(600),
379			},
380			validatorOpts: &jwt.ValidatorOpts{
381				AllowMissingExpiration: true,
382				FixedNow:               time.Unix(500, 0),
383				ClockSkew:              time.Second * 200,
384			},
385		},
386		{
387			tag: "issued in the past",
388			tokenOpts: &jwt.RawJWTOptions{
389				WithoutExpiration: true,
390				IssuedAt:          refTime(500),
391			},
392			validatorOpts: &jwt.ValidatorOpts{
393				AllowMissingExpiration: true,
394				FixedNow:               time.Unix(1000, 0),
395			},
396		},
397		{
398			tag: "issued in the future",
399			tokenOpts: &jwt.RawJWTOptions{
400				WithoutExpiration: true,
401				IssuedAt:          refTime(5000),
402			},
403			validatorOpts: &jwt.ValidatorOpts{
404				AllowMissingExpiration: true,
405				FixedNow:               time.Unix(1000, 0),
406			},
407		},
408		{
409			tag: "without issued at",
410			tokenOpts: &jwt.RawJWTOptions{
411				WithoutExpiration: true,
412			},
413			validatorOpts: &jwt.ValidatorOpts{
414				AllowMissingExpiration: true,
415			},
416		},
417		{
418			tag: "issued in the past with ExpectIssuedInThePast",
419			tokenOpts: &jwt.RawJWTOptions{
420				WithoutExpiration: true,
421				IssuedAt:          refTime(500),
422			},
423			validatorOpts: &jwt.ValidatorOpts{
424				AllowMissingExpiration: true,
425				ExpectIssuedInThePast:  true,
426				FixedNow:               time.Unix(1000, 0),
427			},
428		},
429		{
430			tag: "issued in the past with ExpectIssuedInThePast and clock skew",
431			tokenOpts: &jwt.RawJWTOptions{
432				WithoutExpiration: true,
433				IssuedAt:          refTime(1100),
434			},
435			validatorOpts: &jwt.ValidatorOpts{
436				AllowMissingExpiration: true,
437				ExpectIssuedInThePast:  true,
438				FixedNow:               time.Unix(1000, 0),
439				ClockSkew:              time.Second * 200,
440			},
441		},
442		{
443
444			tag: "expected type header",
445			tokenOpts: &jwt.RawJWTOptions{
446				WithoutExpiration: true,
447				TypeHeader:        refString("typeHeader"),
448			},
449			validatorOpts: &jwt.ValidatorOpts{
450				AllowMissingExpiration: true,
451				ExpectedTypeHeader:     refString("typeHeader"),
452			},
453		},
454		{
455			tag: "ignore type header",
456			tokenOpts: &jwt.RawJWTOptions{
457				WithoutExpiration: true,
458				TypeHeader:        refString("typeHeader"),
459			},
460			validatorOpts: &jwt.ValidatorOpts{
461				AllowMissingExpiration: true,
462				IgnoreTypeHeader:       true,
463			},
464		},
465		{
466			tag: "expected issuer",
467			tokenOpts: &jwt.RawJWTOptions{
468				WithoutExpiration: true,
469				Issuer:            refString("issuer"),
470			},
471			validatorOpts: &jwt.ValidatorOpts{
472				AllowMissingExpiration: true,
473				ExpectedIssuer:         refString("issuer"),
474			},
475		},
476		{
477			tag: "ignore issuer",
478			tokenOpts: &jwt.RawJWTOptions{
479				WithoutExpiration: true,
480				Issuer:            refString("issuer"),
481			},
482			validatorOpts: &jwt.ValidatorOpts{
483				AllowMissingExpiration: true,
484				IgnoreIssuer:           true,
485			},
486		},
487		{
488			tag: "expected audience",
489			tokenOpts: &jwt.RawJWTOptions{
490				WithoutExpiration: true,
491				Audience:          refString("audience"),
492			},
493			validatorOpts: &jwt.ValidatorOpts{
494				AllowMissingExpiration: true,
495				ExpectedAudience:       refString("audience"),
496			},
497		},
498		{
499			tag: "deprecated expected audiences",
500			tokenOpts: &jwt.RawJWTOptions{
501				WithoutExpiration: true,
502				Audience:          refString("audience"),
503			},
504			validatorOpts: &jwt.ValidatorOpts{
505				AllowMissingExpiration: true,
506				ExpectedAudiences:      refString("audience"),
507			},
508		},
509		{
510			tag: "ignore audience",
511			tokenOpts: &jwt.RawJWTOptions{
512				WithoutExpiration: true,
513				Audience:          refString("audience"),
514			},
515			validatorOpts: &jwt.ValidatorOpts{
516				AllowMissingExpiration: true,
517				IgnoreAudiences:        true,
518			},
519		},
520	} {
521		t.Run(tc.tag, func(t *testing.T) {
522			token, err := jwt.NewRawJWT(tc.tokenOpts)
523			if err != nil {
524				t.Fatalf("NewRawJWT(%v) err = %v, want nil", tc.tokenOpts, err)
525			}
526			validator, err := jwt.NewValidator(tc.validatorOpts)
527			if err != nil {
528				t.Fatalf("NewValidator(%v) err = %v, want nil", tc.validatorOpts, err)
529			}
530			if err := validator.Validate(token); err != nil {
531				t.Errorf("validator.Validate(%v) err = %v, want nil", token, err)
532			}
533		})
534	}
535}
536