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