1// Copyright 2021 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/go-cmp/cmp" 24 "github.com/google/go-cmp/cmp/cmpopts" 25 "github.com/google/tink/go/jwt" 26) 27 28const ( 29 invalidUTF8 = "\xF4\x7F\xBF\xBF" 30 validExpiration = 1640043004 31) 32 33type testCase struct { 34 tag string 35 opts *jwt.RawJWTOptions 36 json string 37 token *jwt.RawJWT 38} 39 40func refString(a string) *string { 41 return &a 42} 43 44func refTime(ts int64) *time.Time { 45 t := time.Unix(ts, 0) 46 return &t 47} 48 49func TestCreatingRawJWTWithAllClaims(t *testing.T) { 50 json := `{ 51 "sub": "tink-test-subject", 52 "iss": "tink-test-issuer", 53 "jti": "tink-jwt-id", 54 "aud": ["aud-1", "aud-2"], 55 "exp": 457888, 56 "nbf": 450888, 57 "iat": 400888, 58 "cc-num": 1.67, 59 "cc-bool": true, 60 "cc-null": null, 61 "cc-array": [1,2,3], 62 "cc-string": "cc-val", 63 "cc-object": {"nested-cc-num": 5.5} 64 }` 65 66 opts := &jwt.RawJWTOptions{ 67 TypeHeader: refString("typeHeader"), 68 Subject: refString("tink-test-subject"), 69 Issuer: refString("tink-test-issuer"), 70 JWTID: refString("tink-jwt-id"), 71 Audiences: []string{"aud-1", "aud-2"}, 72 ExpiresAt: refTime(457888), 73 NotBefore: refTime(450888), 74 IssuedAt: refTime(400888), 75 CustomClaims: map[string]interface{}{ 76 "cc-num": 1.67, 77 "cc-bool": true, 78 "cc-null": nil, 79 "cc-string": "cc-val", 80 "cc-array": []interface{}{1.0, 2.0, 3.0}, 81 "cc-object": map[string]interface{}{"nested-cc-num": 5.5}, 82 }, 83 } 84 fromJSON, err := jwt.NewRawJWTFromJSON(refString("typeHeader"), []byte(json)) 85 if err != nil { 86 t.Fatalf("jwt.NewRawJWTFromJSON(%q): %v", json, err) 87 } 88 fromOpts, err := jwt.NewRawJWT(opts) 89 if err != nil { 90 t.Fatalf("jwt.NewRawJWT(%v): %v", opts, err) 91 } 92 for _, tc := range []testCase{ 93 { 94 tag: "jwt.NewRawJWTFromJSON", 95 token: fromJSON, 96 }, 97 { 98 tag: "NewRawJWT", 99 token: fromOpts, 100 }, 101 } { 102 if !tc.token.HasTypeHeader() { 103 t.Errorf("tc.token.HasTypeHeader() = false, want true") 104 } 105 if !tc.token.HasAudiences() { 106 t.Errorf("tc.token.HasAudiences() = false, want true") 107 } 108 if !tc.token.HasSubject() { 109 t.Errorf("tc.token.HasSubject() = false, want true") 110 } 111 if !tc.token.HasIssuer() { 112 t.Errorf("tc.token.HasIssuer() = false, want true") 113 } 114 if !tc.token.HasJWTID() { 115 t.Errorf("tc.token.HasJWTID() = false, want true") 116 } 117 if !tc.token.HasExpiration() { 118 t.Errorf("tc.token.HasExpiration() = false, want true") 119 } 120 if !tc.token.HasNotBefore() { 121 t.Errorf("tc.token.HasNotBefore() = false, want true") 122 } 123 if !tc.token.HasIssuedAt() { 124 t.Errorf("tc.token.HasIssuedAt() = false, want true") 125 } 126 127 typeHeader, err := tc.token.TypeHeader() 128 if err != nil { 129 t.Errorf("tc.token.TypeHeader() err = %v, want nil", err) 130 } 131 if !cmp.Equal(typeHeader, *opts.TypeHeader) { 132 t.Errorf("tc.token.TypeHeader() = %q, want %q", typeHeader, *opts.TypeHeader) 133 } 134 audiences, err := tc.token.Audiences() 135 if err != nil { 136 t.Errorf("tc.token.Audiences() err = %v, want nil", err) 137 } 138 if !cmp.Equal(audiences, opts.Audiences) { 139 t.Errorf("tc.token.Audiences() = %q, want %q", audiences, opts.Audiences) 140 } 141 subject, err := tc.token.Subject() 142 if err != nil { 143 t.Errorf("tc.token.Subject() err = %v, want nil", err) 144 } 145 if !cmp.Equal(subject, *opts.Subject) { 146 t.Errorf("tc.token.Subject() = %q, want %q", subject, *opts.Subject) 147 } 148 issuer, err := tc.token.Issuer() 149 if err != nil { 150 t.Errorf("tc.token.Issuer() err = %v, want nil", err) 151 } 152 if !cmp.Equal(issuer, *opts.Issuer) { 153 t.Errorf("tc.token.Issuer() = %q, want %q", issuer, *opts.Issuer) 154 } 155 jwtID, err := tc.token.JWTID() 156 if err != nil { 157 t.Errorf("tc.token.JWTID() err = %v, want nil", err) 158 } 159 if !cmp.Equal(jwtID, *opts.JWTID) { 160 t.Errorf("tc.token.JWTID() = %q, want %q", jwtID, *opts.JWTID) 161 } 162 expiresAt, err := tc.token.ExpiresAt() 163 if err != nil { 164 t.Errorf("tc.token.ExpiresAt() err = %v, want nil", err) 165 } 166 if !cmp.Equal(expiresAt, *opts.ExpiresAt) { 167 t.Errorf("tc.token.ExpiresAt() = %q, want %q", expiresAt, *opts.ExpiresAt) 168 } 169 issuedAt, err := tc.token.IssuedAt() 170 if err != nil { 171 t.Errorf("tc.token.IssuedAt() err = %v, want nil", err) 172 } 173 if !cmp.Equal(issuedAt, *opts.IssuedAt) { 174 t.Errorf("tc.token.IssuedAt() = %q, want %q", issuedAt, *opts.IssuedAt) 175 } 176 notBefore, err := tc.token.NotBefore() 177 if err != nil { 178 t.Errorf("tc.token.NotBefore() err = %v, want nil", err) 179 } 180 if !cmp.Equal(notBefore, *opts.NotBefore) { 181 t.Errorf("tc.token.NotBefore() = %q, want %q", notBefore, *opts.NotBefore) 182 } 183 wantCustomClaims := []string{"cc-num", "cc-bool", "cc-null", "cc-string", "cc-array", "cc-object"} 184 if !cmp.Equal(tc.token.CustomClaimNames(), wantCustomClaims, cmpopts.SortSlices(func(a, b string) bool { return a < b })) { 185 t.Errorf("tc.token.CustomClaimNames() = %q, want %q", tc.token.CustomClaimNames(), wantCustomClaims) 186 } 187 if !tc.token.HasNumberClaim("cc-num") { 188 t.Errorf("tc.token.HasNumberClaim('cc-num') = false, want true") 189 } 190 if !tc.token.HasBooleanClaim("cc-bool") { 191 t.Errorf("tc.token.HasBooleanClaim('cc-bool') = false, want true") 192 } 193 if !tc.token.HasNullClaim("cc-null") { 194 t.Errorf("tc.token.HasNullClaim('cc-null') = false, want true") 195 } 196 if !tc.token.HasStringClaim("cc-string") { 197 t.Errorf("tc.token.HasStringClaim('cc-string') = false, want true") 198 } 199 if !tc.token.HasArrayClaim("cc-array") { 200 t.Errorf("tc.token.HasArrayClaim('cc-array') = false, want true") 201 } 202 if !tc.token.HasObjectClaim("cc-object") { 203 t.Errorf("tc.token.HasObjectClaim('cc-object') = false, want true") 204 } 205 206 number, err := tc.token.NumberClaim("cc-num") 207 if err != nil { 208 t.Errorf("tc.token.NumberClaim('cc-num') err = %v, want nil", err) 209 } 210 if !cmp.Equal(number, opts.CustomClaims["cc-num"]) { 211 t.Errorf("tc.token.NumberClaim('cc-num') = %f, want %f", number, opts.CustomClaims["cc-num"]) 212 } 213 boolean, err := tc.token.BooleanClaim("cc-bool") 214 if err != nil { 215 t.Errorf("tc.token.BooleanClaim('cc-bool') err = %v, want nil", err) 216 } 217 if !cmp.Equal(boolean, opts.CustomClaims["cc-bool"]) { 218 t.Errorf("tc.token.BooleanClaim('cc-bool') = %v, want %v", boolean, opts.CustomClaims["cc-bool"]) 219 } 220 str, err := tc.token.StringClaim("cc-string") 221 if err != nil { 222 t.Errorf("tc.token.StringClaim('cc-string') err = %v, want nil", err) 223 } 224 if !cmp.Equal(str, opts.CustomClaims["cc-string"]) { 225 t.Errorf("tc.token.StringClaim('cc-string') = %q, want %q", str, opts.CustomClaims["cc-string"]) 226 } 227 array, err := tc.token.ArrayClaim("cc-array") 228 if err != nil { 229 t.Errorf("tc.token.ArrayClaim('cc-array') err = %v, want nil", err) 230 } 231 if !cmp.Equal(array, opts.CustomClaims["cc-array"]) { 232 t.Errorf("tc.token.ArrayClaim('cc-array') = %q, want %q", array, opts.CustomClaims["cc-array"]) 233 } 234 object, err := tc.token.ObjectClaim("cc-object") 235 if err != nil { 236 t.Errorf("tc.token.ObjectClaim('cc-object') err = %v, want nil", err) 237 } 238 if !cmp.Equal(object, opts.CustomClaims["cc-object"]) { 239 t.Errorf("tc.token.ObjectClaim('cc-object') = %q, want %q", object, opts.CustomClaims["cc-object"]) 240 } 241 } 242} 243 244func TestGeneratingRawJWTWithoutClaims(t *testing.T) { 245 jsonToken, err := jwt.NewRawJWTFromJSON(nil, []byte("{}")) 246 if err != nil { 247 t.Fatalf("jwt.NewRawJWTFromJSON({}): %v", err) 248 } 249 optsToken, err := jwt.NewRawJWT(&jwt.RawJWTOptions{WithoutExpiration: true}) 250 if err != nil { 251 t.Fatalf("NewRawJWT with no claims: %v", err) 252 } 253 for _, tc := range []testCase{ 254 { 255 tag: "jwt.NewRawJWTFromJSON", 256 token: jsonToken, 257 }, 258 { 259 tag: "NewRawJWT", 260 token: optsToken, 261 }, 262 } { 263 if tc.token.HasTypeHeader() { 264 t.Errorf("tc.token.HasTypeHeader() = true, want false") 265 } 266 if tc.token.HasAudiences() { 267 t.Errorf("tc.token.HasAudiences() = true, want false") 268 } 269 if tc.token.HasSubject() { 270 t.Errorf("tc.token.HasSubject() = true, want false") 271 } 272 if tc.token.HasIssuer() { 273 t.Errorf("tc.token.HasIssuer() = true, want false") 274 } 275 if tc.token.HasJWTID() { 276 t.Errorf("tc.token.HasJWTID() = true, want false") 277 } 278 if tc.token.HasExpiration() { 279 t.Errorf("tc.token.HasExpiration() = true, want false") 280 } 281 if tc.token.HasNotBefore() { 282 t.Errorf("tc.token.HasNotBefore() = true, want false") 283 } 284 if tc.token.HasIssuedAt() { 285 t.Errorf("tc.token.HasIssuedAt() = true, want false") 286 } 287 if _, err := tc.token.Audiences(); err == nil { 288 t.Errorf("tc.token.Audiences() err = nil, want error") 289 } 290 if _, err := tc.token.Subject(); err == nil { 291 t.Errorf("tc.token.Subject() err = nil, want error") 292 } 293 if _, err := tc.token.Issuer(); err == nil { 294 t.Errorf("tc.token.Issuer() err = nil, want error") 295 } 296 if _, err := tc.token.JWTID(); err == nil { 297 t.Errorf("tc.token.JWTID() err = nil, want error") 298 } 299 if _, err := tc.token.ExpiresAt(); err == nil { 300 t.Errorf("tc.token.ExpiresAt() err = nil, want error") 301 } 302 if _, err := tc.token.IssuedAt(); err == nil { 303 t.Errorf("tc.token.IssuedAt() err = nil, want error") 304 } 305 if _, err := tc.token.NotBefore(); err == nil { 306 t.Errorf("tc.token.NotBefore() err = nil, want error") 307 } 308 if !cmp.Equal(tc.token.CustomClaimNames(), []string{}) { 309 t.Errorf("tc.token.CustomClaimNames() = %q want %q", tc.token.CustomClaimNames(), []string{}) 310 } 311 } 312} 313 314func TestNewRawJWTLargeValidTimestamps(t *testing.T) { 315 opts := &jwt.RawJWTOptions{ 316 TypeHeader: refString("typeHeader"), 317 ExpiresAt: refTime(253402300799), 318 NotBefore: refTime(253402300700), 319 IssuedAt: refTime(253402300000), 320 } 321 token, err := jwt.NewRawJWT(opts) 322 if err != nil { 323 t.Fatalf("generating RawJWT with valid timestamps (%q, %q, %q): %v", opts.ExpiresAt, opts.NotBefore, opts.IssuedAt, err) 324 } 325 expiresAt, err := token.ExpiresAt() 326 if err != nil { 327 t.Errorf("tc.token.ExpiresAt() err = %v, want nil", err) 328 } 329 if !cmp.Equal(expiresAt, *opts.ExpiresAt) { 330 t.Errorf("tc.token.ExpiresAt() = %q want %q", expiresAt, *opts.ExpiresAt) 331 } 332 333 notBefore, err := token.NotBefore() 334 if err != nil { 335 t.Errorf("tc.token.NotBefore() err = %v, want nil", err) 336 } 337 if !cmp.Equal(notBefore, *opts.NotBefore) { 338 t.Errorf("tc.token.NotBefore() = %q want %q", notBefore, *opts.NotBefore) 339 } 340 341 issuedAt, err := token.IssuedAt() 342 if err != nil { 343 t.Errorf("tc.token.IssuedAt() err = %v, want nil", err) 344 } 345 if !cmp.Equal(issuedAt, *opts.IssuedAt) { 346 t.Errorf("tc.token.IssuedAt() = %q want %q", issuedAt, *opts.IssuedAt) 347 } 348} 349 350func TestNewRawJWTSingleStringAudience(t *testing.T) { 351 opts := &jwt.RawJWTOptions{ 352 WithoutExpiration: true, 353 Audience: refString("tink-aud"), 354 } 355 rawJWT, err := jwt.NewRawJWT(opts) 356 if err != nil { 357 t.Fatalf("generating RawJWT with a single audience: %v", err) 358 } 359 aud, err := rawJWT.Audiences() 360 if err != nil { 361 t.Errorf("getting audience from token: %v", err) 362 } 363 want := []string{*opts.Audience} 364 if !cmp.Equal(aud, want) { 365 t.Errorf("rawJWT.Audiences() = %q, want %q", aud, want) 366 } 367} 368 369func TestSingleStringAudienceFromJSON(t *testing.T) { 370 rawJWT, err := jwt.NewRawJWTFromJSON(nil, []byte(`{"aud": "tink-aud"}`)) 371 if err != nil { 372 t.Fatalf("parsing valid RawJWT: %v", err) 373 } 374 aud, err := rawJWT.Audiences() 375 if err != nil { 376 t.Errorf("getting audience from token: %v", err) 377 } 378 want := []string{"tink-aud"} 379 if !cmp.Equal(aud, want) { 380 t.Errorf("rawJWT.Audiences() = %q, want %q", aud, want) 381 } 382} 383 384func TestNewRawJWTValidationFailures(t *testing.T) { 385 testCases := []testCase{ 386 { 387 tag: "empty jwt.RawJWTOptions options fails", 388 }, 389 { 390 tag: "no ExpiresAt specified and WithoutExpiration = false fails", 391 opts: &jwt.RawJWTOptions{ 392 Audiences: []string{"tink-foo"}, 393 }, 394 }, 395 { 396 tag: "ExpiresAt and WithoutExpiration = true fails", 397 opts: &jwt.RawJWTOptions{ 398 Audiences: []string{"tink-foo"}, 399 ExpiresAt: refTime(validExpiration), 400 WithoutExpiration: true, 401 }, 402 }, 403 { 404 tag: "specifying Audenience and Audiences fails", 405 opts: &jwt.RawJWTOptions{ 406 Audiences: []string{"tink-foo"}, 407 Audience: refString("tink-bar"), 408 WithoutExpiration: true, 409 }, 410 }, 411 { 412 tag: "empty audiences array fails", 413 opts: &jwt.RawJWTOptions{ 414 ExpiresAt: refTime(validExpiration), 415 Audiences: []string{}, 416 }, 417 }, 418 { 419 tag: "audiences with invalid UTF-8 string fails", 420 opts: &jwt.RawJWTOptions{ 421 WithoutExpiration: true, 422 Audiences: []string{"valid", invalidUTF8}, 423 }, 424 }, 425 { 426 tag: "custom claims containing registered subject claims fails", 427 opts: &jwt.RawJWTOptions{ 428 Audiences: []string{"tink-foo"}, 429 ExpiresAt: refTime(validExpiration), 430 CustomClaims: map[string]interface{}{ 431 "sub": "overwrite", 432 }, 433 }, 434 }, 435 { 436 tag: "custom claims containing registered issuer claims fails", 437 opts: &jwt.RawJWTOptions{ 438 Audiences: []string{"tink-foo"}, 439 ExpiresAt: refTime(validExpiration), 440 CustomClaims: map[string]interface{}{ 441 "iss": "overwrite", 442 }, 443 }, 444 }, 445 { 446 tag: "custom claims containing registered jwt id claims fails", 447 opts: &jwt.RawJWTOptions{ 448 Audiences: []string{"tink-foo"}, 449 ExpiresAt: refTime(validExpiration), 450 CustomClaims: map[string]interface{}{ 451 "jti": "overwrite", 452 }, 453 }, 454 }, 455 { 456 tag: "custom claims containing registered expiration claims fails", 457 opts: &jwt.RawJWTOptions{ 458 Audiences: []string{"tink-foo"}, 459 ExpiresAt: refTime(validExpiration), 460 CustomClaims: map[string]interface{}{ 461 "exp": "overwrite", 462 }, 463 }, 464 }, 465 { 466 tag: "custom claims containing registered audience claims fails", 467 opts: &jwt.RawJWTOptions{ 468 Audiences: []string{"tink-foo"}, 469 WithoutExpiration: true, 470 CustomClaims: map[string]interface{}{ 471 "aud": []interface{}{"overwrite"}, 472 }, 473 }, 474 }, 475 { 476 tag: "custom claims with non standard JSON types fails", 477 opts: &jwt.RawJWTOptions{ 478 Audiences: []string{"tink-foo"}, 479 ExpiresAt: refTime(validExpiration), 480 CustomClaims: map[string]interface{}{ 481 "complex": time.Time{}, 482 }, 483 }, 484 }, 485 { 486 tag: "non UTF-8 string on isser claim fails", 487 opts: &jwt.RawJWTOptions{ 488 Audiences: []string{"tink-foo"}, 489 ExpiresAt: refTime(validExpiration), 490 Issuer: refString(invalidUTF8), 491 }, 492 }, 493 { 494 tag: "non UTF-8 string on subject claim fails", 495 opts: &jwt.RawJWTOptions{ 496 Audiences: []string{"tink-foo"}, 497 WithoutExpiration: true, 498 Subject: refString(invalidUTF8), 499 }, 500 }, 501 { 502 tag: "non UTF-8 string on JWT ID claim fails", 503 opts: &jwt.RawJWTOptions{ 504 Audiences: []string{"tink-foo"}, 505 WithoutExpiration: true, 506 JWTID: refString(invalidUTF8), 507 }, 508 }, 509 { 510 tag: "non UTF-8 string on custom claim fails", 511 opts: &jwt.RawJWTOptions{ 512 Audiences: []string{"tink-foo"}, 513 Issuer: refString("ise-testing"), 514 ExpiresAt: refTime(validExpiration), 515 CustomClaims: map[string]interface{}{ 516 "esoteric": invalidUTF8, 517 }, 518 }, 519 }, 520 { 521 tag: "issued at timestamp greater than valid JWT max time fails", 522 opts: &jwt.RawJWTOptions{ 523 Audiences: []string{"tink-foo"}, 524 ExpiresAt: refTime(validExpiration), 525 IssuedAt: refTime(253402300800), 526 }, 527 }, 528 { 529 tag: "expires at timestamp greater than valid JWT max time fails", 530 opts: &jwt.RawJWTOptions{ 531 Audiences: []string{"tink-foo"}, 532 ExpiresAt: refTime(253402300800), 533 }, 534 }, 535 { 536 tag: "not before timestamp smaller than valid JWT min time fails", 537 opts: &jwt.RawJWTOptions{ 538 Audiences: []string{"tink-foo"}, 539 ExpiresAt: refTime(validExpiration), 540 NotBefore: refTime(-5), 541 }, 542 }, 543 } 544 for _, tc := range testCases { 545 t.Run(tc.tag, func(t *testing.T) { 546 _, err := jwt.NewRawJWT(tc.opts) 547 if err == nil { 548 t.Errorf("expected error instead got nil") 549 } 550 }) 551 } 552} 553 554func TestJSONPayload(t *testing.T) { 555 for _, tc := range []testCase{ 556 { 557 tag: "subject", 558 opts: &jwt.RawJWTOptions{ 559 WithoutExpiration: true, 560 Subject: refString("tink-subject"), 561 }, 562 json: `{"sub":"tink-subject"}`, 563 }, 564 { 565 tag: "audience list", 566 opts: &jwt.RawJWTOptions{ 567 WithoutExpiration: true, 568 Audiences: []string{"one"}, 569 }, 570 json: `{"aud":["one"]}`, 571 }, 572 { 573 tag: "audience string", 574 opts: &jwt.RawJWTOptions{ 575 WithoutExpiration: true, 576 Audience: refString("one"), 577 }, 578 json: `{"aud":"one"}`, 579 }, 580 { 581 tag: "issuer", 582 opts: &jwt.RawJWTOptions{ 583 WithoutExpiration: true, 584 Issuer: refString("tink-test"), 585 }, 586 json: `{"iss":"tink-test"}`, 587 }, 588 { 589 tag: "jwt id", 590 opts: &jwt.RawJWTOptions{ 591 WithoutExpiration: true, 592 JWTID: refString("tink-id"), 593 }, 594 json: `{"jti":"tink-id"}`, 595 }, 596 { 597 tag: "issued at", 598 opts: &jwt.RawJWTOptions{ 599 WithoutExpiration: true, 600 IssuedAt: refTime(78324), 601 }, 602 json: `{"iat":78324}`, 603 }, 604 { 605 tag: "not before", 606 opts: &jwt.RawJWTOptions{ 607 WithoutExpiration: true, 608 NotBefore: refTime(78324), 609 }, 610 json: `{"nbf":78324}`, 611 }, 612 { 613 tag: "expiration", 614 opts: &jwt.RawJWTOptions{ 615 ExpiresAt: refTime(78324), 616 }, 617 json: `{"exp":78324}`, 618 }, 619 { 620 tag: "integer", 621 opts: &jwt.RawJWTOptions{ 622 WithoutExpiration: true, 623 CustomClaims: map[string]interface{}{ 624 "num": 1, 625 }, 626 }, 627 json: `{"num":1}`, 628 }, 629 { 630 tag: "custom-claim", 631 opts: &jwt.RawJWTOptions{ 632 WithoutExpiration: true, 633 CustomClaims: map[string]interface{}{ 634 "cust": []interface{}{map[string]interface{}{"key": "val"}}, 635 }, 636 }, 637 json: `{"cust":[{"key":"val"}]}`, 638 }, 639 { 640 tag: "no claims", 641 opts: &jwt.RawJWTOptions{ 642 WithoutExpiration: true, 643 }, 644 json: `{}`, 645 }, 646 } { 647 t.Run(tc.tag, func(t *testing.T) { 648 token, err := jwt.NewRawJWT(tc.opts) 649 if err != nil { 650 t.Errorf("generating valid RawJWT: %v", err) 651 } 652 j, err := token.JSONPayload() 653 if err != nil { 654 t.Errorf("calling JSONPayload() on rawJWT: %v", err) 655 } 656 if !cmp.Equal(string(j), tc.json) { 657 t.Fatalf("JSONPayload output got %v, expected %v", string(j), tc.json) 658 } 659 }) 660 } 661} 662 663func TestFromJSONValidationFailures(t *testing.T) { 664 testCases := []testCase{ 665 { 666 tag: "json with empty audience", 667 json: `{"sub": "tink", "aud": []}`, 668 }, 669 { 670 tag: "json with audience of wrong type", 671 json: `{"aud": 5}`, 672 }, 673 { 674 tag: "json with audiences of wrong type", 675 json: `{"aud": ["one", null]}`, 676 }, 677 { 678 tag: "json with registered claim with wrong type", 679 json: `{"sub": 1}`, 680 }, 681 { 682 tag: "json with non UTF-8 string on subject claim fails", 683 json: `{"sub": "\xF4\x7F\xBF\xBF"}`, 684 }, 685 { 686 tag: "json with non UTF-8 string on issuer claim fails", 687 json: `{"iss": "\xF4\x7F\xBF\xBF"}`, 688 }, 689 { 690 tag: "json with non UTF-8 string on jwt id claim fails", 691 json: `{"jti": "\xF4\x7F\xBF\xBF"}`, 692 }, 693 { 694 tag: "json with `not before` timestamp claim greater than valid JWT max time fails", 695 json: `{"nbf": 253402301799}`, 696 }, 697 { 698 tag: "json with `issued at` timestamp claim greater than valid JWT max time fails", 699 json: `{"iat": 253402301799}`, 700 }, 701 { 702 tag: "json with `expiration` timestamp claim greater than valid JWT max time fails", 703 json: `{"exp": 253402301799}`, 704 }, 705 { 706 tag: "json with `not before` timestamp claim smaller than valid JWT min time fails", 707 json: `{"nbf": -4}`, 708 }, 709 { 710 tag: "json with `issued at` timestamp claim smaller than valid JWT min time fails", 711 json: `{"iat": -4}`, 712 }, 713 { 714 tag: "json with `expiration` timestamp claim smaller than valid JWT min time fails", 715 json: `{"exp": -4}`, 716 }, 717 { 718 tag: "json with `not before` claim of non numeric type fails", 719 json: `{"nbf": "invalid"}`, 720 }, 721 { 722 tag: "json with `issued at` claim of non numeric type fails", 723 json: `{"iat": "invalid"}`, 724 }, 725 { 726 tag: "json with `expiration` claim of non numeric type fails", 727 json: `{"exp": "invalid"}`, 728 }, 729 } 730 731 for _, tc := range testCases { 732 t.Run(tc.tag, func(t *testing.T) { 733 if _, err := jwt.NewRawJWTFromJSON(nil, []byte(tc.json)); err == nil { 734 t.Errorf("expected error instead got nil") 735 } 736 }) 737 } 738} 739 740func TestHasCustomClaimsOfKind(t *testing.T) { 741 opts := &jwt.RawJWTOptions{ 742 TypeHeader: refString("typeHeader"), 743 WithoutExpiration: true, 744 CustomClaims: map[string]interface{}{ 745 "cc-num": 1.67, 746 "cc-bool": false, 747 "cc-nil": nil, 748 "cc-list": []interface{}{1.0, 2.0, 3.0}, 749 "cc-string": "cc-val", 750 "cc-object": map[string]interface{}{ 751 "nested-cc-num": 5.5, 752 }, 753 }, 754 } 755 token, err := jwt.NewRawJWT(opts) 756 if err != nil { 757 t.Fatalf("generating valid RawJWT: %v", err) 758 } 759 if token.HasBooleanClaim("cc-num") { 760 t.Errorf("custom number claim 'cc-num' should return false when queried for another type") 761 } 762 if token.HasNullClaim("cc-bool") { 763 t.Errorf("custom boolean claim 'cc-bool' should return false when queried for another type") 764 } 765 if token.HasNumberClaim("cc-bool") { 766 t.Errorf("custom boolean claim 'cc-bool' should return false when queried for another type") 767 } 768 if token.HasStringClaim("cc-bool") { 769 t.Errorf("custom boolean claim 'cc-bool' should return false when queried for another type") 770 } 771 if token.HasArrayClaim("cc-bool") { 772 t.Errorf("custom boolean claim 'cc-bool' should return false when queried for another type") 773 } 774 if token.HasObjectClaim("cc-bool") { 775 t.Errorf("custom boolean claim 'cc-bool' should return false when queried for another type") 776 } 777} 778 779func TestGettingRegisteredClaimsThroughCustomFails(t *testing.T) { 780 opts := &jwt.RawJWTOptions{ 781 TypeHeader: refString("typeHeader"), 782 Subject: refString("tink-test-subject"), 783 Issuer: refString("tink-test-issuer"), 784 JWTID: refString("tink-jwt-id-1"), 785 Audiences: []string{"aud-1", "aud-2"}, 786 ExpiresAt: refTime(validExpiration), 787 IssuedAt: refTime(validExpiration - 100), 788 NotBefore: refTime(validExpiration - 50), 789 } 790 token, err := jwt.NewRawJWT(opts) 791 if err != nil { 792 t.Fatalf("generating valid RawJWT: %v", err) 793 } 794 if !cmp.Equal(token.CustomClaimNames(), []string{}) { 795 t.Errorf("tc.token.CustomClaimNames() = %q want %q", token.CustomClaimNames(), []string{}) 796 } 797 for _, c := range []string{"sub", "iss", "aud", "nbf", "exp", "iat", "jti"} { 798 if token.HasNullClaim(c) { 799 t.Errorf("registered '%q' claim should return false when calling HasNullClaim", c) 800 } 801 if token.HasBooleanClaim(c) { 802 t.Errorf("registered '%q' claim should return false when calling HasBooleanClaim", c) 803 } 804 if _, err := token.BooleanClaim(c); err == nil { 805 t.Errorf("expected error when calling token.BoolClaim(%q) instead got nil", c) 806 } 807 if token.HasNumberClaim(c) { 808 t.Errorf("registered '%q' claim should return false when calling HasNumberClaim", c) 809 } 810 if _, err := token.NumberClaim(c); err == nil { 811 t.Errorf("expected error when calling token.NumberClaim(%q) instead got nil", c) 812 } 813 if token.HasStringClaim(c) { 814 t.Errorf("registered '%q' claim should return false when calling HasStringClaim", c) 815 } 816 if _, err := token.StringClaim(c); err == nil { 817 t.Errorf("expected error when calling token.StringClaim(%q) instead got nil", c) 818 } 819 if token.HasArrayClaim(c) { 820 t.Errorf("registered '%q' claim should return false when calling HasArrayClaim", c) 821 } 822 if _, err := token.ArrayClaim(c); err == nil { 823 t.Errorf("expected error when calling token.ListClaim(%q) instead got nil", c) 824 } 825 if token.HasObjectClaim(c) { 826 t.Errorf("registered '%q' claim should return false when calling HasObjectClaim", c) 827 } 828 if _, err := token.ObjectClaim(c); err == nil { 829 t.Errorf("expected error when calling token.JSONClaim(%q) instead got nil", c) 830 } 831 } 832} 833