| // Copyright 2022 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package jwt |
| |
| import ( |
| "encoding/base64" |
| "fmt" |
| "testing" |
| "time" |
| |
| "github.com/google/tink/go/mac/subtle" |
| ) |
| |
| func TestNewMACwithNilMACFails(t *testing.T) { |
| if _, err := newMACWithKID(nil, "", nil); err == nil { |
| t.Errorf("NewMACWithKID(nil, '', nil) err = nil, want error") |
| } |
| } |
| |
| func createMACwithKID(customKID *string) (*macWithKID, error) { |
| // https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.1.1 |
| key, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString("AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow") |
| if err != nil { |
| return nil, fmt.Errorf("failed parsing test key: %v", err) |
| } |
| mac, err := subtle.NewHMAC("SHA256", key, 32) |
| if err != nil { |
| return nil, err |
| } |
| return newMACWithKID(mac, "HS256", customKID) |
| } |
| |
| func TestCreateAndValidateToken(t *testing.T) { |
| m, err := createMACwithKID(nil) |
| if err != nil { |
| t.Fatalf("creating JWTMACwithKID primitive: %v", err) |
| } |
| rawOpts := &RawJWTOptions{ |
| TypeHeader: refString("typeHeader"), |
| JWTID: refString("123"), |
| WithoutExpiration: true, |
| } |
| rawJWT, err := NewRawJWT(rawOpts) |
| if err != nil { |
| t.Errorf("NewRawJWT() err = %v, want nil", err) |
| } |
| compact, err := m.ComputeMACAndEncodeWithKID(rawJWT, nil) |
| if err != nil { |
| t.Errorf("m.ComputeMACAndEncodeWithKID err = %v, want nil", err) |
| } |
| validatorOps := &ValidatorOpts{ |
| ExpectedTypeHeader: refString("typeHeader"), |
| AllowMissingExpiration: true, |
| } |
| validator, err := NewValidator(validatorOps) |
| if err != nil { |
| t.Errorf("NewValidator err = %v, want nil", err) |
| } |
| verifiedJWT, err := m.VerifyMACAndDecodeWithKID(compact, validator, nil) |
| if err != nil { |
| t.Errorf("m.VerifyMACAndDecodeWithKID() err = %v, want nil", err) |
| } |
| typeHeader, err := verifiedJWT.TypeHeader() |
| if err != nil { |
| t.Errorf("verifiedJWT.TypeHeader() err = %v, want nil", err) |
| } |
| if typeHeader != "typeHeader" { |
| t.Errorf("verifiedJWT.TypeHeader() = %q, want 'typeHeader'", typeHeader) |
| } |
| jwtID, err := verifiedJWT.JWTID() |
| if err != nil { |
| t.Errorf("verifiedJWT.JWTID() err = %v, want nil", err) |
| } |
| if jwtID != "123" { |
| t.Errorf("verifiedJWT.JWTID() = %q, want '123'", jwtID) |
| } |
| |
| validatorOps = &ValidatorOpts{ |
| ExpectedTypeHeader: refString("notTypeHeader"), |
| AllowMissingExpiration: true, |
| } |
| validator, err = NewValidator(validatorOps) |
| if err != nil { |
| t.Errorf("NewValidator err = %v, want nil", err) |
| } |
| if _, err := m.VerifyMACAndDecodeWithKID(compact, validator, nil); err == nil { |
| t.Errorf("m.VerifyMACAndDecodeWithKID() err = nil, want error") |
| } |
| } |
| |
| func TestCreateAndValidateTokenWithKID(t *testing.T) { |
| m, err := createMACwithKID(nil) |
| if err != nil { |
| t.Fatalf("creating JWTMACwithKID primitive: %v", err) |
| } |
| rawOpts := &RawJWTOptions{ |
| TypeHeader: refString("typeHeader"), |
| JWTID: refString("123"), |
| WithoutExpiration: true, |
| } |
| rawJWT, err := NewRawJWT(rawOpts) |
| if err != nil { |
| t.Errorf("NewRawJWT() err = %v, want nil", err) |
| } |
| compact, err := m.ComputeMACAndEncodeWithKID(rawJWT, refString("kid-123")) |
| if err != nil { |
| t.Errorf("m.ComputeMACAndEncodeWithKID err = %v, want nil", err) |
| } |
| opts := &ValidatorOpts{ |
| ExpectedTypeHeader: refString("typeHeader"), |
| AllowMissingExpiration: true, |
| } |
| validator, err := NewValidator(opts) |
| if err != nil { |
| t.Fatalf("creating JWT validator, NewValidator: %v", err) |
| } |
| verifiedJWT, err := m.VerifyMACAndDecodeWithKID(compact, validator, refString("kid-123")) |
| if err != nil { |
| t.Errorf("m.VerifyMACAndDecodeWithKID(kid = kid-123) err = %v, want nil", err) |
| } |
| |
| typeHeader, err := verifiedJWT.TypeHeader() |
| if err != nil { |
| t.Errorf("verifiedJWT.TypeHeader() err = %v, want nil", err) |
| } |
| if typeHeader != *rawOpts.TypeHeader { |
| t.Errorf("verifiedJWT.TypeHeader() = %q, want %q", typeHeader, *rawOpts.TypeHeader) |
| } |
| jwtID, err := verifiedJWT.JWTID() |
| if err != nil { |
| t.Errorf("verifiedJWT.JWTID() err = %v, want nil", err) |
| } |
| if jwtID != *rawOpts.JWTID { |
| t.Errorf("verifiedJWT.JWTID() = %q, want %q", jwtID, *rawOpts.JWTID) |
| } |
| |
| if _, err := m.VerifyMACAndDecodeWithKID(compact, validator, nil); err != nil { |
| t.Errorf("m.VerifyMACAndDecodeWithKID(kid = nil) err = %v, want nil", err) |
| } |
| if _, err := m.VerifyMACAndDecodeWithKID(compact, validator, refString("other-kid")); err == nil { |
| t.Errorf("m.VerifyMACAndDecodeWithKID(kid = 'other-kid') err = nil, want error") |
| } |
| } |
| |
| func TestValidateFixedToken(t *testing.T) { |
| // Key and Token are examples from: https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.1.1 |
| compact := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" |
| m, err := createMACwithKID(nil) |
| if err != nil { |
| t.Fatalf("creating JWTMACwithKID primitive: %v", err) |
| } |
| opts := &ValidatorOpts{ |
| ExpectedTypeHeader: refString("JWT"), |
| ExpectedIssuer: refString("joe"), |
| FixedNow: time.Unix(12345, 0), |
| } |
| pastValidator, err := NewValidator(opts) |
| if err != nil { |
| t.Fatalf("creating JWTValidator: %v", err) |
| } |
| // verification succeeds because token was valid valid on January 1, 1970 UTC. |
| verifiedJWT, err := m.VerifyMACAndDecodeWithKID(compact, pastValidator, nil) |
| if err != nil { |
| t.Fatalf("m.VerifyMACAndDecodeWithKID() err = %v, want nil", err) |
| } |
| typeHeader, err := verifiedJWT.TypeHeader() |
| if err != nil { |
| t.Errorf("verifiedJWT.TypeHeader() err = %v, want nil", err) |
| } |
| if typeHeader != *opts.ExpectedTypeHeader { |
| t.Errorf("verifiedJWT.TypeHeader() = %q, want %q", typeHeader, *opts.ExpectedTypeHeader) |
| } |
| issuer, err := verifiedJWT.Issuer() |
| if err != nil { |
| t.Errorf("verifiedJWT.Issuer() err = %v, want nil", err) |
| } |
| if issuer != *opts.ExpectedIssuer { |
| t.Errorf("verifiedJWT.Issuer() = %q, want %q", issuer, *opts.ExpectedIssuer) |
| } |
| boolClaim, err := verifiedJWT.BooleanClaim("http://example.com/is_root") |
| if err != nil { |
| t.Errorf("verifiedJWT.BooleanClaim('http://example.com/is_root') err = %v, want nil", err) |
| } |
| if boolClaim != true { |
| t.Errorf("verifiedJWT.BooleanClaim('http://example.com/is_root') = %q, want true", issuer) |
| } |
| |
| // expired token fails verification |
| opts.FixedNow = time.Now() |
| presentValidator, err := NewValidator(opts) |
| if err != nil { |
| t.Fatalf("creating JWTValidator: %v", err) |
| } |
| if _, err := m.VerifyMACAndDecodeWithKID(compact, presentValidator, nil); err == nil { |
| t.Fatalf("m.VerifyMACAndDecodeWithKID() with expired token err = nil, want error") |
| } |
| |
| // tampered token verification fails |
| tamperedCompact := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXx" |
| if _, err := m.VerifyMACAndDecodeWithKID(tamperedCompact, pastValidator, nil); err == nil { |
| t.Fatalf("m.VerifyMACAndDecodeWithKID() with expired tampered token err = nil, want error") |
| } |
| } |
| |
| func TestInvalidTokens(t *testing.T) { |
| m, err := createMACwithKID(nil) |
| if err != nil { |
| t.Fatalf("creating JWTMACwithKID primitive: %v", err) |
| } |
| validator, err := NewValidator(&ValidatorOpts{}) |
| if err != nil { |
| t.Fatalf("creating JWTValidator: %v", err) |
| } |
| for _, compact := range []string{ |
| "eyJhbGciOiJIUzI1NiJ9.e30.abc.", |
| "eyJhbGciOiJIUzI1NiJ9?.e30.abc", |
| "eyJhbGciOiJIUzI1NiJ9.e30?.abc", |
| "eyJhbGciOiJIUzI1NiJ9.e30.abc?", |
| "eyJhbGciOiJIUzI1NiJ9.e30", |
| "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOi&Jqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", |
| } { |
| if _, err := m.VerifyMACAndDecodeWithKID(compact, validator, nil); err == nil { |
| t.Errorf("m.VerifyMACAndDecodeWithKID(%q) err = nil, want error", compact) |
| } |
| } |
| } |
| |
| func TestCustomKIDAndTinkPrefixKeyFail(t *testing.T) { |
| m, err := createMACwithKID(refString("custom-kid")) |
| if err != nil { |
| t.Fatalf("creating JWTMACwithKID primitive: %v", err) |
| } |
| rawJWT, err := NewRawJWT(&RawJWTOptions{WithoutExpiration: true}) |
| if err != nil { |
| t.Fatalf("NewRawJWT() err = %v, want nil", err) |
| } |
| if _, err := m.ComputeMACAndEncodeWithKID(rawJWT, refString("tink-kid")); err == nil { |
| t.Errorf("specifying kid when primitive contains kid to ComputeMACAndEncodeWithKID() err = nil, want error") |
| } |
| } |