xref: /aosp_15_r20/external/tink/go/jwt/jwt_validator.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
18
19import (
20	"fmt"
21	"time"
22)
23
24const (
25	jwtMaxClockSkewMinutes = 10
26)
27
28// ValidatorOpts define validation options for JWT validators.
29type ValidatorOpts struct {
30	ExpectedTypeHeader *string
31	ExpectedIssuer     *string
32	ExpectedAudience   *string
33
34	IgnoreTypeHeader bool
35	IgnoreAudiences  bool
36	IgnoreIssuer     bool
37
38	AllowMissingExpiration bool
39	ExpectIssuedInThePast  bool
40
41	ClockSkew time.Duration
42	FixedNow  time.Time
43
44	// Deprecated: Use ExpectedAudience instead.
45	ExpectedAudiences *string
46}
47
48// Validator defines how JSON Web Tokens (JWT) should be validated.
49type Validator struct {
50	opts ValidatorOpts
51}
52
53// NewValidator creates a new Validator.
54func NewValidator(opts *ValidatorOpts) (*Validator, error) {
55	if opts == nil {
56		return nil, fmt.Errorf("ValidatorOpts can't be nil")
57	}
58	if opts.ExpectedAudiences != nil {
59		if opts.ExpectedAudience != nil {
60			return nil, fmt.Errorf("ExpectedAudiences and ExpectedAudience can't be set at the same time")
61		}
62		opts.ExpectedAudience = opts.ExpectedAudiences
63		opts.ExpectedAudiences = nil
64	}
65	if opts.ExpectedTypeHeader != nil && opts.IgnoreTypeHeader {
66		return nil, fmt.Errorf("ExpectedTypeHeader and IgnoreTypeHeader cannot be used together")
67	}
68	if opts.ExpectedIssuer != nil && opts.IgnoreIssuer {
69		return nil, fmt.Errorf("ExpectedIssuer and IgnoreIssuer cannot be used together")
70	}
71	if opts.ExpectedAudience != nil && opts.IgnoreAudiences {
72		return nil, fmt.Errorf("ExpectedAudience and IgnoreAudience cannot be used together")
73	}
74	if opts.ClockSkew.Minutes() > jwtMaxClockSkewMinutes {
75		return nil, fmt.Errorf("clock skew too large, max is %d minutes", jwtMaxClockSkewMinutes)
76	}
77	return &Validator{
78		opts: *opts,
79	}, nil
80}
81
82// Validate validates a rawJWT according to the options provided.
83func (v *Validator) Validate(rawJWT *RawJWT) error {
84	if rawJWT == nil {
85		return fmt.Errorf("rawJWT can't be nil")
86	}
87	if err := v.validateTimestamps(rawJWT); err != nil {
88		return err
89	}
90	if err := v.validateTypeHeader(rawJWT); err != nil {
91		return fmt.Errorf("validating type header: %v", err)
92	}
93	if err := v.validateAudiences(rawJWT); err != nil {
94		return fmt.Errorf("validating audience claim: %v", err)
95	}
96	if err := v.validateIssuer(rawJWT); err != nil {
97		return fmt.Errorf("validating issuer claim: %v", err)
98	}
99	return nil
100}
101
102func (v *Validator) validateTimestamps(rawJWT *RawJWT) error {
103	now := time.Now()
104	if !v.opts.FixedNow.IsZero() {
105		now = v.opts.FixedNow
106	}
107
108	if !rawJWT.HasExpiration() && !v.opts.AllowMissingExpiration {
109		return fmt.Errorf("token doesn't have an expiration set")
110	}
111	if rawJWT.HasExpiration() {
112		exp, err := rawJWT.ExpiresAt()
113		if err != nil {
114			return err
115		}
116		if !exp.After(now.Add(-v.opts.ClockSkew)) {
117			return errJwtExpired
118		}
119	}
120	if rawJWT.HasNotBefore() {
121		nbf, err := rawJWT.NotBefore()
122		if err != nil {
123			return err
124		}
125		if nbf.After(now.Add(v.opts.ClockSkew)) {
126			return fmt.Errorf("token cannot be used yet")
127		}
128	}
129	if v.opts.ExpectIssuedInThePast {
130		iat, err := rawJWT.IssuedAt()
131		if err != nil {
132			return err
133		}
134		if iat.After(now.Add(v.opts.ClockSkew)) {
135			return fmt.Errorf("token has an invalid iat claim in the future")
136		}
137	}
138	return nil
139}
140
141func (v *Validator) validateTypeHeader(rawJWT *RawJWT) error {
142	skip, err := validateFieldPresence(v.opts.IgnoreTypeHeader, rawJWT.HasTypeHeader(), v.opts.ExpectedTypeHeader != nil)
143	if err != nil {
144		return err
145	}
146	if skip {
147		return nil
148	}
149	typeHeader, err := rawJWT.TypeHeader()
150	if err != nil {
151		return err
152	}
153	if typeHeader != *v.opts.ExpectedTypeHeader {
154		return fmt.Errorf("wrong 'type header' type")
155	}
156	return nil
157}
158
159func (v *Validator) validateIssuer(rawJWT *RawJWT) error {
160	skip, err := validateFieldPresence(v.opts.IgnoreIssuer, rawJWT.HasIssuer(), v.opts.ExpectedIssuer != nil)
161	if err != nil {
162		return err
163	}
164	if skip {
165		return nil
166	}
167	issuer, err := rawJWT.Issuer()
168	if err != nil {
169		return err
170	}
171	if issuer != *v.opts.ExpectedIssuer {
172		return fmt.Errorf("got %s, want %s", issuer, *v.opts.ExpectedIssuer)
173	}
174	return nil
175}
176
177func (v *Validator) validateAudiences(rawJWT *RawJWT) error {
178	skip, err := validateFieldPresence(v.opts.IgnoreAudiences, rawJWT.HasAudiences(), v.opts.ExpectedAudience != nil)
179	if err != nil {
180		return err
181	}
182	if skip {
183		return nil
184	}
185	audiences, err := rawJWT.Audiences()
186	if err != nil {
187		return err
188	}
189	for i, aud := range audiences {
190		if aud == *v.opts.ExpectedAudience {
191			break
192		}
193		if i == len(audiences)-1 {
194			return fmt.Errorf("%s not found", *v.opts.ExpectedAudience)
195		}
196	}
197	return nil
198}
199
200func validateFieldPresence(ignore bool, isPresent bool, isExpected bool) (bool, error) {
201	if ignore {
202		return true, nil
203	}
204	if !isExpected && !isPresent {
205		return true, nil
206	}
207	if !isExpected && isPresent {
208		return false, fmt.Errorf("token has claim but validator doesn't expect it")
209	}
210	if isExpected && !isPresent {
211		return false, fmt.Errorf("claim was expected but isn't present")
212	}
213	return false, nil
214}
215