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