- Notifications
You must be signed in to change notification settings - Fork 371
/
Copy pathvalidator.go
338 lines (290 loc) · 10.1 KB
/
validator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
package jwt
import (
"fmt"
"time"
)
// ClaimsValidator is an interface that can be implemented by custom claims who
// wish to execute any additional claims validation based on
// application-specific logic. The Validate function is then executed in
// addition to the regular claims validation and any error returned is appended
// to the final validation result.
//
// type MyCustomClaims struct {
// Foo string `json:"foo"`
// jwt.RegisteredClaims
// }
//
// func (m MyCustomClaims) Validate() error {
// if m.Foo != "bar" {
// return errors.New("must be foobar")
// }
// return nil
// }
typeClaimsValidatorinterface {
Claims
Validate() error
}
// Validator is the core of the new Validation API. It is automatically used by
// a [Parser] during parsing and can be modified with various parser options.
//
// The [NewValidator] function should be used to create an instance of this
// struct.
typeValidatorstruct {
// leeway is an optional leeway that can be provided to account for clock skew.
leeway time.Duration
// timeFunc is used to supply the current time that is needed for
// validation. If unspecified, this defaults to time.Now.
timeFuncfunc() time.Time
// requireExp specifies whether the exp claim is required
requireExpbool
// verifyIat specifies whether the iat (Issued At) claim will be verified.
// According to https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6 this
// only specifies the age of the token, but no validation check is
// necessary. However, if wanted, it can be checked if the iat is
// unrealistic, i.e., in the future.
verifyIatbool
// expectedAud contains the audience this token expects. Supplying an empty
// slice will disable aud checking.
expectedAud []string
// expectAllAud specifies whether all expected audiences must be present in
// the token. If false, only one of the expected audiences must be present.
expectAllAudbool
// expectedIss contains the issuer this token expects. Supplying an empty
// string will disable iss checking.
expectedIssstring
// expectedSub contains the subject this token expects. Supplying an empty
// string will disable sub checking.
expectedSubstring
}
// NewValidator can be used to create a stand-alone validator with the supplied
// options. This validator can then be used to validate already parsed claims.
//
// Note: Under normal circumstances, explicitly creating a validator is not
// needed and can potentially be dangerous; instead functions of the [Parser]
// class should be used.
//
// The [Validator] is only checking the *validity* of the claims, such as its
// expiration time, but it does NOT perform *signature verification* of the
// token.
funcNewValidator(opts...ParserOption) *Validator {
p:=NewParser(opts...)
returnp.validator
}
// Validate validates the given claims. It will also perform any custom
// validation if claims implements the [ClaimsValidator] interface.
//
// Note: It will NOT perform any *signature verification* on the token that
// contains the claims and expects that the [Claim] was already successfully
// verified.
func (v*Validator) Validate(claimsClaims) error {
var (
now time.Time
errs=make([]error, 0, 6)
errerror
)
// Check, if we have a time func
ifv.timeFunc!=nil {
now=v.timeFunc()
} else {
now=time.Now()
}
// We always need to check the expiration time, but usage of the claim
// itself is OPTIONAL by default. requireExp overrides this behavior
// and makes the exp claim mandatory.
iferr=v.verifyExpiresAt(claims, now, v.requireExp); err!=nil {
errs=append(errs, err)
}
// We always need to check not-before, but usage of the claim itself is
// OPTIONAL.
iferr=v.verifyNotBefore(claims, now, false); err!=nil {
errs=append(errs, err)
}
// Check issued-at if the option is enabled
ifv.verifyIat {
iferr=v.verifyIssuedAt(claims, now, false); err!=nil {
errs=append(errs, err)
}
}
// If we have an expected audience, we also require the audience claim
iflen(v.expectedAud) >0 {
iferr=v.verifyAudience(claims, v.expectedAud, v.expectAllAud, true); err!=nil {
errs=append(errs, err)
}
}
// If we have an expected issuer, we also require the issuer claim
ifv.expectedIss!="" {
iferr=v.verifyIssuer(claims, v.expectedIss, true); err!=nil {
errs=append(errs, err)
}
}
// If we have an expected subject, we also require the subject claim
ifv.expectedSub!="" {
iferr=v.verifySubject(claims, v.expectedSub, true); err!=nil {
errs=append(errs, err)
}
}
// Finally, we want to give the claim itself some possibility to do some
// additional custom validation based on a custom Validate function.
cvt, ok:=claims.(ClaimsValidator)
ifok {
iferr:=cvt.Validate(); err!=nil {
errs=append(errs, err)
}
}
iflen(errs) ==0 {
returnnil
}
returnjoinErrors(errs...)
}
// verifyExpiresAt compares the exp claim in claims against cmp. This function
// will succeed if cmp < exp. Additional leeway is taken into account.
//
// If exp is not set, it will succeed if the claim is not required,
// otherwise ErrTokenRequiredClaimMissing will be returned.
//
// Additionally, if any error occurs while retrieving the claim, e.g., when its
// the wrong type, an ErrTokenUnverifiable error will be returned.
func (v*Validator) verifyExpiresAt(claimsClaims, cmp time.Time, requiredbool) error {
exp, err:=claims.GetExpirationTime()
iferr!=nil {
returnerr
}
ifexp==nil {
returnerrorIfRequired(required, "exp")
}
returnerrorIfFalse(cmp.Before((exp.Time).Add(+v.leeway)), ErrTokenExpired)
}
// verifyIssuedAt compares the iat claim in claims against cmp. This function
// will succeed if cmp >= iat. Additional leeway is taken into account.
//
// If iat is not set, it will succeed if the claim is not required,
// otherwise ErrTokenRequiredClaimMissing will be returned.
//
// Additionally, if any error occurs while retrieving the claim, e.g., when its
// the wrong type, an ErrTokenUnverifiable error will be returned.
func (v*Validator) verifyIssuedAt(claimsClaims, cmp time.Time, requiredbool) error {
iat, err:=claims.GetIssuedAt()
iferr!=nil {
returnerr
}
ifiat==nil {
returnerrorIfRequired(required, "iat")
}
returnerrorIfFalse(!cmp.Before(iat.Add(-v.leeway)), ErrTokenUsedBeforeIssued)
}
// verifyNotBefore compares the nbf claim in claims against cmp. This function
// will return true if cmp >= nbf. Additional leeway is taken into account.
//
// If nbf is not set, it will succeed if the claim is not required,
// otherwise ErrTokenRequiredClaimMissing will be returned.
//
// Additionally, if any error occurs while retrieving the claim, e.g., when its
// the wrong type, an ErrTokenUnverifiable error will be returned.
func (v*Validator) verifyNotBefore(claimsClaims, cmp time.Time, requiredbool) error {
nbf, err:=claims.GetNotBefore()
iferr!=nil {
returnerr
}
ifnbf==nil {
returnerrorIfRequired(required, "nbf")
}
returnerrorIfFalse(!cmp.Before(nbf.Add(-v.leeway)), ErrTokenNotValidYet)
}
// verifyAudience compares the aud claim against cmp.
//
// If aud is not set or an empty list, it will succeed if the claim is not required,
// otherwise ErrTokenRequiredClaimMissing will be returned.
//
// Additionally, if any error occurs while retrieving the claim, e.g., when its
// the wrong type, an ErrTokenUnverifiable error will be returned.
func (v*Validator) verifyAudience(claimsClaims, cmp []string, expectAllAudbool, requiredbool) error {
aud, err:=claims.GetAudience()
iferr!=nil {
returnerr
}
iflen(aud) ==0 {
returnerrorIfRequired(required, "aud")
}
// use a var here to keep constant time compare when looping over a number of claims
matching:=make(map[string]bool, 0)
// build a matching hashmap out of the expected aud
for_, expected:=rangecmp {
matching[expected] =false
}
// compare the expected aud with the actual aud in a constant time manner by looping over all actual values
varstringClaimsstring
for_, a:=rangeaud {
a:=a
_, ok:=matching[a]
ifok {
matching[a] =true
}
stringClaims=stringClaims+a
}
// check if all expected auds are present
result:=true
for_, match:=rangematching {
if!expectAllAud&&match {
break
} elseif!match {
result=false
}
}
// case where "" is sent in one or many aud claims
ifstringClaims=="" {
returnerrorIfRequired(required, "aud")
}
returnerrorIfFalse(result, ErrTokenInvalidAudience)
}
// verifyIssuer compares the iss claim in claims against cmp.
//
// If iss is not set, it will succeed if the claim is not required,
// otherwise ErrTokenRequiredClaimMissing will be returned.
//
// Additionally, if any error occurs while retrieving the claim, e.g., when its
// the wrong type, an ErrTokenUnverifiable error will be returned.
func (v*Validator) verifyIssuer(claimsClaims, cmpstring, requiredbool) error {
iss, err:=claims.GetIssuer()
iferr!=nil {
returnerr
}
ifiss=="" {
returnerrorIfRequired(required, "iss")
}
returnerrorIfFalse(iss==cmp, ErrTokenInvalidIssuer)
}
// verifySubject compares the sub claim against cmp.
//
// If sub is not set, it will succeed if the claim is not required,
// otherwise ErrTokenRequiredClaimMissing will be returned.
//
// Additionally, if any error occurs while retrieving the claim, e.g., when its
// the wrong type, an ErrTokenUnverifiable error will be returned.
func (v*Validator) verifySubject(claimsClaims, cmpstring, requiredbool) error {
sub, err:=claims.GetSubject()
iferr!=nil {
returnerr
}
ifsub=="" {
returnerrorIfRequired(required, "sub")
}
returnerrorIfFalse(sub==cmp, ErrTokenInvalidSubject)
}
// errorIfFalse returns the error specified in err, if the value is true.
// Otherwise, nil is returned.
funcerrorIfFalse(valuebool, errerror) error {
ifvalue {
returnnil
} else {
returnerr
}
}
// errorIfRequired returns an ErrTokenRequiredClaimMissing error if required is
// true. Otherwise, nil is returned.
funcerrorIfRequired(requiredbool, claimstring) error {
ifrequired {
returnnewError(fmt.Sprintf("%s claim is required", claim), ErrTokenRequiredClaimMissing)
} else {
returnnil
}
}