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 "strings" 21*e7b1675dSTing-Kang Chang "testing" 22*e7b1675dSTing-Kang Chang "time" 23*e7b1675dSTing-Kang Chang 24*e7b1675dSTing-Kang Chang "github.com/google/go-cmp/cmp" 25*e7b1675dSTing-Kang Chang 26*e7b1675dSTing-Kang Chang tpb "github.com/google/tink/go/proto/tink_go_proto" 27*e7b1675dSTing-Kang Chang) 28*e7b1675dSTing-Kang Chang 29*e7b1675dSTing-Kang Changfunc TestKIDForNonTinkKeysIsNil(t *testing.T) { 30*e7b1675dSTing-Kang Chang for _, op := range []tpb.OutputPrefixType{ 31*e7b1675dSTing-Kang Chang tpb.OutputPrefixType_LEGACY, 32*e7b1675dSTing-Kang Chang tpb.OutputPrefixType_RAW, 33*e7b1675dSTing-Kang Chang tpb.OutputPrefixType_CRUNCHY} { 34*e7b1675dSTing-Kang Chang if kid := keyID(1234, op); kid != nil { 35*e7b1675dSTing-Kang Chang t.Errorf("keyID(1234, %q) = %q, want nil", op, *kid) 36*e7b1675dSTing-Kang Chang } 37*e7b1675dSTing-Kang Chang } 38*e7b1675dSTing-Kang Chang} 39*e7b1675dSTing-Kang Chang 40*e7b1675dSTing-Kang Changfunc TestKeyIDForTinkKey(t *testing.T) { 41*e7b1675dSTing-Kang Chang want := "GsapRA" 42*e7b1675dSTing-Kang Chang kid := keyID(0x1ac6a944, tpb.OutputPrefixType_TINK) 43*e7b1675dSTing-Kang Chang if kid == nil { 44*e7b1675dSTing-Kang Chang t.Errorf("KeyID(0x1ac6a944, %q) = nil, want %q", tpb.OutputPrefixType_TINK, want) 45*e7b1675dSTing-Kang Chang } 46*e7b1675dSTing-Kang Chang if kid != nil && !cmp.Equal(*kid, want) { 47*e7b1675dSTing-Kang Chang t.Errorf("KeyID(0x1ac6a944, %q) = %q, want %q", tpb.OutputPrefixType_TINK, *kid, want) 48*e7b1675dSTing-Kang Chang } 49*e7b1675dSTing-Kang Chang} 50*e7b1675dSTing-Kang Chang 51*e7b1675dSTing-Kang Changtype payloadTestCase struct { 52*e7b1675dSTing-Kang Chang tag string 53*e7b1675dSTing-Kang Chang rawJWT *RawJWT 54*e7b1675dSTing-Kang Chang opts *RawJWTOptions 55*e7b1675dSTing-Kang Chang tinkKID *string 56*e7b1675dSTing-Kang Chang customKID *string 57*e7b1675dSTing-Kang Chang algorithm string 58*e7b1675dSTing-Kang Chang} 59*e7b1675dSTing-Kang Chang 60*e7b1675dSTing-Kang Changfunc refString(a string) *string { 61*e7b1675dSTing-Kang Chang return &a 62*e7b1675dSTing-Kang Chang} 63*e7b1675dSTing-Kang Chang 64*e7b1675dSTing-Kang Changfunc refTime(ts int64) *time.Time { 65*e7b1675dSTing-Kang Chang t := time.Unix(ts, 0) 66*e7b1675dSTing-Kang Chang return &t 67*e7b1675dSTing-Kang Chang} 68*e7b1675dSTing-Kang Chang 69*e7b1675dSTing-Kang Changfunc TestBase64Encode(t *testing.T) { 70*e7b1675dSTing-Kang Chang // Examples from: https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.1.1 71*e7b1675dSTing-Kang Chang want := "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" 72*e7b1675dSTing-Kang Chang payload := []byte{123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 73*e7b1675dSTing-Kang Chang 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125} 74*e7b1675dSTing-Kang Chang got := base64Encode(payload) 75*e7b1675dSTing-Kang Chang if got != want { 76*e7b1675dSTing-Kang Chang t.Errorf("base64Encode() got %q want %q", got, want) 77*e7b1675dSTing-Kang Chang } 78*e7b1675dSTing-Kang Chang} 79*e7b1675dSTing-Kang Chang 80*e7b1675dSTing-Kang Changfunc TestBase64Decode(t *testing.T) { 81*e7b1675dSTing-Kang Chang // Examples from: https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.1.1 82*e7b1675dSTing-Kang Chang want := []byte{123, 34, 105, 115, 115, 34, 58, 34, 106, 111, 101, 34, 44, 13, 10, 32, 34, 101, 120, 112, 34, 58, 49, 51, 48, 48, 56, 49, 57, 51, 56, 83*e7b1675dSTing-Kang Chang 48, 44, 13, 10, 32, 34, 104, 116, 116, 112, 58, 47, 47, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109, 47, 105, 115, 95, 114, 111, 111, 116, 34, 58, 116, 114, 117, 101, 125} 84*e7b1675dSTing-Kang Chang got, err := base64Decode("eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ") 85*e7b1675dSTing-Kang Chang if err != nil { 86*e7b1675dSTing-Kang Chang t.Errorf("base64Decode() err = %v, want nil", err) 87*e7b1675dSTing-Kang Chang } 88*e7b1675dSTing-Kang Chang if !cmp.Equal(got, want) { 89*e7b1675dSTing-Kang Chang t.Errorf("base64Decode() got %q, want %q", got, want) 90*e7b1675dSTing-Kang Chang } 91*e7b1675dSTing-Kang Chang} 92*e7b1675dSTing-Kang Chang 93*e7b1675dSTing-Kang Changfunc TestInvalidCharactersFailBase64Decode(t *testing.T) { 94*e7b1675dSTing-Kang Chang if _, err := base64Decode("iLA0KIC&hD"); err == nil { 95*e7b1675dSTing-Kang Chang t.Errorf("base64Decode() err = nil, want error") 96*e7b1675dSTing-Kang Chang } 97*e7b1675dSTing-Kang Chang} 98*e7b1675dSTing-Kang Chang 99*e7b1675dSTing-Kang Changfunc TestEncodeStaticHeaderWithPayloadIssuerTokenForSigning(t *testing.T) { 100*e7b1675dSTing-Kang Chang opts := &RawJWTOptions{ 101*e7b1675dSTing-Kang Chang WithoutExpiration: true, 102*e7b1675dSTing-Kang Chang Issuer: refString("tink-issuer"), 103*e7b1675dSTing-Kang Chang } 104*e7b1675dSTing-Kang Chang // Header 'RS256' alg from: https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.2.1 105*e7b1675dSTing-Kang Chang // Payload: `{"iss":"tink-issuer"}` 106*e7b1675dSTing-Kang Chang wantUnsigned := "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0aW5rLWlzc3VlciJ9" 107*e7b1675dSTing-Kang Chang rawJWT, err := NewRawJWT(opts) 108*e7b1675dSTing-Kang Chang if err != nil { 109*e7b1675dSTing-Kang Chang t.Fatalf("generating valid RawJWT: %v", err) 110*e7b1675dSTing-Kang Chang } 111*e7b1675dSTing-Kang Chang unsigned, err := createUnsigned(rawJWT, "RS256", nil, nil) 112*e7b1675dSTing-Kang Chang if err != nil { 113*e7b1675dSTing-Kang Chang t.Errorf("createUnsigned() err = %v, want nil", err) 114*e7b1675dSTing-Kang Chang } 115*e7b1675dSTing-Kang Chang 116*e7b1675dSTing-Kang Chang if unsigned != wantUnsigned { 117*e7b1675dSTing-Kang Chang t.Errorf("got unsigned %q, want %q", unsigned, wantUnsigned) 118*e7b1675dSTing-Kang Chang } 119*e7b1675dSTing-Kang Chang} 120*e7b1675dSTing-Kang Chang 121*e7b1675dSTing-Kang Changfunc TestEncodeHeaderWithHeaderFieldsAndEmptyPayload(t *testing.T) { 122*e7b1675dSTing-Kang Chang type testCase struct { 123*e7b1675dSTing-Kang Chang tag string 124*e7b1675dSTing-Kang Chang opts *RawJWTOptions 125*e7b1675dSTing-Kang Chang wantHeaderSubstring string 126*e7b1675dSTing-Kang Chang customKID *string 127*e7b1675dSTing-Kang Chang tinkKID *string 128*e7b1675dSTing-Kang Chang } 129*e7b1675dSTing-Kang Chang for _, tc := range []testCase{ 130*e7b1675dSTing-Kang Chang { 131*e7b1675dSTing-Kang Chang tag: "type header", 132*e7b1675dSTing-Kang Chang opts: &RawJWTOptions{ 133*e7b1675dSTing-Kang Chang WithoutExpiration: true, 134*e7b1675dSTing-Kang Chang TypeHeader: refString("JWT"), 135*e7b1675dSTing-Kang Chang }, 136*e7b1675dSTing-Kang Chang wantHeaderSubstring: `"typ":"JWT"`, 137*e7b1675dSTing-Kang Chang }, 138*e7b1675dSTing-Kang Chang { 139*e7b1675dSTing-Kang Chang tag: "custom kid", 140*e7b1675dSTing-Kang Chang opts: &RawJWTOptions{ 141*e7b1675dSTing-Kang Chang WithoutExpiration: true, 142*e7b1675dSTing-Kang Chang }, 143*e7b1675dSTing-Kang Chang customKID: refString("custom"), 144*e7b1675dSTing-Kang Chang wantHeaderSubstring: `"kid":"custom"`, 145*e7b1675dSTing-Kang Chang }, 146*e7b1675dSTing-Kang Chang { 147*e7b1675dSTing-Kang Chang tag: "tink kid", 148*e7b1675dSTing-Kang Chang opts: &RawJWTOptions{ 149*e7b1675dSTing-Kang Chang WithoutExpiration: true, 150*e7b1675dSTing-Kang Chang }, 151*e7b1675dSTing-Kang Chang tinkKID: refString("tink"), 152*e7b1675dSTing-Kang Chang wantHeaderSubstring: `"kid":"tink"`, 153*e7b1675dSTing-Kang Chang }, 154*e7b1675dSTing-Kang Chang } { 155*e7b1675dSTing-Kang Chang rawJWT, err := NewRawJWT(tc.opts) 156*e7b1675dSTing-Kang Chang if err != nil { 157*e7b1675dSTing-Kang Chang t.Fatalf("generating valid RawJWT: %v", err) 158*e7b1675dSTing-Kang Chang } 159*e7b1675dSTing-Kang Chang unsigned, err := createUnsigned(rawJWT, "RS256", tc.tinkKID, tc.customKID) 160*e7b1675dSTing-Kang Chang if err != nil { 161*e7b1675dSTing-Kang Chang t.Errorf("createUnsigned() err = %v, want nil", err) 162*e7b1675dSTing-Kang Chang } 163*e7b1675dSTing-Kang Chang token := strings.Split(unsigned, ".") 164*e7b1675dSTing-Kang Chang if len(token) != 2 { 165*e7b1675dSTing-Kang Chang t.Errorf("token[0] not encoded in compact serialization format") 166*e7b1675dSTing-Kang Chang } 167*e7b1675dSTing-Kang Chang header, err := base64Decode(token[0]) 168*e7b1675dSTing-Kang Chang if err != nil { 169*e7b1675dSTing-Kang Chang t.Errorf("base64Decode(token[0] = %q)", token[0]) 170*e7b1675dSTing-Kang Chang } 171*e7b1675dSTing-Kang Chang if !strings.Contains(string(header), tc.wantHeaderSubstring) { 172*e7b1675dSTing-Kang Chang t.Errorf("header %q, doesn't contain: %q", string(header), tc.wantHeaderSubstring) 173*e7b1675dSTing-Kang Chang } 174*e7b1675dSTing-Kang Chang wantPayload := "e30" // `{}` 175*e7b1675dSTing-Kang Chang if string(token[1]) != wantPayload { 176*e7b1675dSTing-Kang Chang t.Errorf("token[1] = %q, want %q", token[1], wantPayload) 177*e7b1675dSTing-Kang Chang } 178*e7b1675dSTing-Kang Chang } 179*e7b1675dSTing-Kang Chang} 180*e7b1675dSTing-Kang Chang 181*e7b1675dSTing-Kang Changfunc TestCreateUnsignedWithNilRawJWTFails(t *testing.T) { 182*e7b1675dSTing-Kang Chang if _, err := createUnsigned(nil, "HS256", nil, nil); err == nil { 183*e7b1675dSTing-Kang Chang t.Errorf("createUnsigned(rawJWT = nil) err = nil, want error") 184*e7b1675dSTing-Kang Chang } 185*e7b1675dSTing-Kang Chang} 186*e7b1675dSTing-Kang Chang 187*e7b1675dSTing-Kang Changfunc TestCreateUnsignedCustomAndTinkKIDFail(t *testing.T) { 188*e7b1675dSTing-Kang Chang rawJWT, err := NewRawJWT(&RawJWTOptions{WithoutExpiration: true}) 189*e7b1675dSTing-Kang Chang if err != nil { 190*e7b1675dSTing-Kang Chang t.Fatalf("generating valid RawJWT: %v", err) 191*e7b1675dSTing-Kang Chang } 192*e7b1675dSTing-Kang Chang if _, err := createUnsigned(rawJWT, "HS256", refString("123"), refString("456")); err == nil { 193*e7b1675dSTing-Kang Chang t.Errorf("createUnsigned(tinkKID = 456, customKID = 123) err = nil, want error") 194*e7b1675dSTing-Kang Chang } 195*e7b1675dSTing-Kang Chang} 196*e7b1675dSTing-Kang Chang 197*e7b1675dSTing-Kang Changfunc TestCombineTokenAndSignature(t *testing.T) { 198*e7b1675dSTing-Kang Chang // https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.2.1 199*e7b1675dSTing-Kang Chang payload := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" 200*e7b1675dSTing-Kang Chang signature := []byte{116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121} 201*e7b1675dSTing-Kang Chang token := combineUnsignedAndSignature(payload, signature) 202*e7b1675dSTing-Kang Chang want := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" 203*e7b1675dSTing-Kang Chang if !cmp.Equal(token, want) { 204*e7b1675dSTing-Kang Chang t.Errorf("combineUnsignedAndSignature(%q, %q) = %q, want %q", payload, signature, token, want) 205*e7b1675dSTing-Kang Chang } 206*e7b1675dSTing-Kang Chang} 207*e7b1675dSTing-Kang Chang 208*e7b1675dSTing-Kang Changfunc TestSplitSignedCompactInvalidInputs(t *testing.T) { 209*e7b1675dSTing-Kang Chang type testCases struct { 210*e7b1675dSTing-Kang Chang tag string 211*e7b1675dSTing-Kang Chang token string 212*e7b1675dSTing-Kang Chang } 213*e7b1675dSTing-Kang Chang for _, tc := range []testCases{ 214*e7b1675dSTing-Kang Chang { 215*e7b1675dSTing-Kang Chang tag: "empty payload", 216*e7b1675dSTing-Kang Chang token: "", 217*e7b1675dSTing-Kang Chang }, 218*e7b1675dSTing-Kang Chang { 219*e7b1675dSTing-Kang Chang tag: "not in compact serialization missing separators", 220*e7b1675dSTing-Kang Chang token: "Zm9vYmFyIVRpbms", 221*e7b1675dSTing-Kang Chang }, 222*e7b1675dSTing-Kang Chang { 223*e7b1675dSTing-Kang Chang tag: "not in compact serialization additional separators", 224*e7b1675dSTing-Kang Chang token: "Zm9vYmFyIVRpbms.Zm9vYmFyGVRpbms.Zm9vYmFyIVRpbms.Zm9vYmFyINRpbms", 225*e7b1675dSTing-Kang Chang }, 226*e7b1675dSTing-Kang Chang { 227*e7b1675dSTing-Kang Chang tag: "non web safe URL encoding character", 228*e7b1675dSTing-Kang Chang token: "Zm9vYmFyIVRpbms.m9vYmFy.Zm&mFyIVRpbms", 229*e7b1675dSTing-Kang Chang }, 230*e7b1675dSTing-Kang Chang { 231*e7b1675dSTing-Kang Chang tag: "no content", 232*e7b1675dSTing-Kang Chang token: ".Zm9vYmFyIVRpbms", 233*e7b1675dSTing-Kang Chang }, 234*e7b1675dSTing-Kang Chang { 235*e7b1675dSTing-Kang Chang tag: "no signature", 236*e7b1675dSTing-Kang Chang token: "Zm9vYmFyIVRpbms.Zm9vYmFyIVRpbms.", 237*e7b1675dSTing-Kang Chang }, 238*e7b1675dSTing-Kang Chang { 239*e7b1675dSTing-Kang Chang tag: "no signature and no content", 240*e7b1675dSTing-Kang Chang token: "..", 241*e7b1675dSTing-Kang Chang }, 242*e7b1675dSTing-Kang Chang } { 243*e7b1675dSTing-Kang Chang t.Run(tc.tag, func(t *testing.T) { 244*e7b1675dSTing-Kang Chang if _, _, err := splitSignedCompact(tc.token); err == nil { 245*e7b1675dSTing-Kang Chang t.Errorf("splitSignedCompact(%q) err = nil, want error", tc.token) 246*e7b1675dSTing-Kang Chang } 247*e7b1675dSTing-Kang Chang }) 248*e7b1675dSTing-Kang Chang } 249*e7b1675dSTing-Kang Chang} 250*e7b1675dSTing-Kang Chang 251*e7b1675dSTing-Kang Changfunc TestSplitSignedCompact(t *testing.T) { 252*e7b1675dSTing-Kang Chang // signed token from: https://datatracker.ietf.org/doc/html/rfc7515#appendix-A.1.1 253*e7b1675dSTing-Kang Chang signedToken := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk" 254*e7b1675dSTing-Kang Chang wantSig := []byte{116, 24, 223, 180, 151, 153, 224, 37, 79, 250, 96, 125, 216, 173, 187, 186, 22, 212, 37, 77, 105, 214, 191, 240, 91, 88, 5, 88, 83, 132, 141, 121} 255*e7b1675dSTing-Kang Chang wantToken := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" 256*e7b1675dSTing-Kang Chang sig, token, err := splitSignedCompact(signedToken) 257*e7b1675dSTing-Kang Chang if err != nil { 258*e7b1675dSTing-Kang Chang t.Errorf("splitSignedCompact(%q) err = %v, want nil", signedToken, err) 259*e7b1675dSTing-Kang Chang } 260*e7b1675dSTing-Kang Chang if !cmp.Equal(sig, wantSig) { 261*e7b1675dSTing-Kang Chang t.Errorf("splitSignedCompact() sig = %q, want %q", sig, wantSig) 262*e7b1675dSTing-Kang Chang } 263*e7b1675dSTing-Kang Chang if token != wantToken { 264*e7b1675dSTing-Kang Chang t.Errorf("splitSignedCompact() token = %q, want %q", token, wantToken) 265*e7b1675dSTing-Kang Chang } 266*e7b1675dSTing-Kang Chang} 267*e7b1675dSTing-Kang Chang 268*e7b1675dSTing-Kang Changfunc TestDecodeValidateInvalidHeaderFailures(t *testing.T) { 269*e7b1675dSTing-Kang Chang type testCases struct { 270*e7b1675dSTing-Kang Chang tag string 271*e7b1675dSTing-Kang Chang header string 272*e7b1675dSTing-Kang Chang alg string 273*e7b1675dSTing-Kang Chang tinkKID *string 274*e7b1675dSTing-Kang Chang customKID *string 275*e7b1675dSTing-Kang Chang } 276*e7b1675dSTing-Kang Chang for _, tc := range []testCases{ 277*e7b1675dSTing-Kang Chang { 278*e7b1675dSTing-Kang Chang tag: "invalid JSON header", 279*e7b1675dSTing-Kang Chang header: `JiVeQCo`, 280*e7b1675dSTing-Kang Chang }, 281*e7b1675dSTing-Kang Chang { 282*e7b1675dSTing-Kang Chang tag: "contains line feed", 283*e7b1675dSTing-Kang Chang header: "eyJ0eXAiOiJKV1Qi\nLA0KICJhbGciOiJIUzI1NiJ9", 284*e7b1675dSTing-Kang Chang alg: "HS256", 285*e7b1675dSTing-Kang Chang }, 286*e7b1675dSTing-Kang Chang { 287*e7b1675dSTing-Kang Chang tag: "header contains no fields", 288*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{}`)), 289*e7b1675dSTing-Kang Chang }, 290*e7b1675dSTing-Kang Chang { 291*e7b1675dSTing-Kang Chang tag: "type header not a string", 292*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256", "typ":5}`)), 293*e7b1675dSTing-Kang Chang alg: "HS256", 294*e7b1675dSTing-Kang Chang }, 295*e7b1675dSTing-Kang Chang { 296*e7b1675dSTing-Kang Chang tag: "wrong algorithm", 297*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256"}`)), 298*e7b1675dSTing-Kang Chang alg: "HS512", 299*e7b1675dSTing-Kang Chang }, 300*e7b1675dSTing-Kang Chang { 301*e7b1675dSTing-Kang Chang tag: "specyfing custom and tink kid", 302*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256", "kid":"tink"}`)), 303*e7b1675dSTing-Kang Chang alg: "HS256", 304*e7b1675dSTing-Kang Chang tinkKID: refString("tink"), 305*e7b1675dSTing-Kang Chang customKID: refString("custom"), 306*e7b1675dSTing-Kang Chang }, 307*e7b1675dSTing-Kang Chang { 308*e7b1675dSTing-Kang Chang tag: "invalid custom kid", 309*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256", "kid":"custom"}`)), 310*e7b1675dSTing-Kang Chang customKID: refString("notCustom"), 311*e7b1675dSTing-Kang Chang alg: "HS256", 312*e7b1675dSTing-Kang Chang }, 313*e7b1675dSTing-Kang Chang { 314*e7b1675dSTing-Kang Chang tag: "invalid tink kid", 315*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256", "kid":"tink"}`)), 316*e7b1675dSTing-Kang Chang tinkKID: refString("notTink"), 317*e7b1675dSTing-Kang Chang alg: "HS256", 318*e7b1675dSTing-Kang Chang }, 319*e7b1675dSTing-Kang Chang { 320*e7b1675dSTing-Kang Chang tag: "specify tink kid and token without kig", 321*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256"}`)), 322*e7b1675dSTing-Kang Chang tinkKID: refString("notTink"), 323*e7b1675dSTing-Kang Chang alg: "HS256", 324*e7b1675dSTing-Kang Chang }, 325*e7b1675dSTing-Kang Chang { 326*e7b1675dSTing-Kang Chang tag: "crit header", 327*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256", "crit":"fooBar"}`)), 328*e7b1675dSTing-Kang Chang alg: "HS256", 329*e7b1675dSTing-Kang Chang }, 330*e7b1675dSTing-Kang Chang { 331*e7b1675dSTing-Kang Chang tag: "no compact serialization", 332*e7b1675dSTing-Kang Chang header: "asd.asd", 333*e7b1675dSTing-Kang Chang }, 334*e7b1675dSTing-Kang Chang { 335*e7b1675dSTing-Kang Chang tag: "invalid UTF16 encoding", 336*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256", "typ":"\uD834"}`)), 337*e7b1675dSTing-Kang Chang }, 338*e7b1675dSTing-Kang Chang } { 339*e7b1675dSTing-Kang Chang t.Run(tc.tag, func(t *testing.T) { 340*e7b1675dSTing-Kang Chang if _, err := decodeUnsignedTokenAndValidateHeader(dotConcat(tc.header, base64Encode([]byte("{}"))), tc.alg, tc.tinkKID, tc.customKID); err == nil { 341*e7b1675dSTing-Kang Chang t.Errorf("decodeUnsignedTokenAndValidateHeader() err = nil, want error") 342*e7b1675dSTing-Kang Chang } 343*e7b1675dSTing-Kang Chang }) 344*e7b1675dSTing-Kang Chang } 345*e7b1675dSTing-Kang Chang} 346*e7b1675dSTing-Kang Chang 347*e7b1675dSTing-Kang Changfunc TestDecodeValidateKIDHeader(t *testing.T) { 348*e7b1675dSTing-Kang Chang type testCases struct { 349*e7b1675dSTing-Kang Chang tag string 350*e7b1675dSTing-Kang Chang header string 351*e7b1675dSTing-Kang Chang tinkKID *string 352*e7b1675dSTing-Kang Chang customKID *string 353*e7b1675dSTing-Kang Chang } 354*e7b1675dSTing-Kang Chang for _, tc := range []testCases{ 355*e7b1675dSTing-Kang Chang { 356*e7b1675dSTing-Kang Chang tag: "not kid header field", 357*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256"}`)), 358*e7b1675dSTing-Kang Chang }, 359*e7b1675dSTing-Kang Chang { 360*e7b1675dSTing-Kang Chang tag: "validates custom kid", 361*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256", "kid":"custom"}`)), 362*e7b1675dSTing-Kang Chang customKID: refString("custom"), 363*e7b1675dSTing-Kang Chang }, 364*e7b1675dSTing-Kang Chang { 365*e7b1675dSTing-Kang Chang tag: "validates tink kid", 366*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256", "kid":"tink"}`)), 367*e7b1675dSTing-Kang Chang tinkKID: refString("tink"), 368*e7b1675dSTing-Kang Chang }, 369*e7b1675dSTing-Kang Chang { 370*e7b1675dSTing-Kang Chang tag: "ignores kid if exists and tink kid isn't specified", 371*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256", "kid":"random"}`)), 372*e7b1675dSTing-Kang Chang }, 373*e7b1675dSTing-Kang Chang { 374*e7b1675dSTing-Kang Chang tag: "unkown headers are accepted", 375*e7b1675dSTing-Kang Chang header: base64Encode([]byte(`{"alg":"HS256","unknown":"header"}`)), 376*e7b1675dSTing-Kang Chang }, 377*e7b1675dSTing-Kang Chang } { 378*e7b1675dSTing-Kang Chang t.Run(tc.tag, func(t *testing.T) { 379*e7b1675dSTing-Kang Chang _, err := decodeUnsignedTokenAndValidateHeader(dotConcat(tc.header, base64Encode([]byte("{}"))), "HS256", tc.tinkKID, tc.customKID) 380*e7b1675dSTing-Kang Chang if err != nil { 381*e7b1675dSTing-Kang Chang t.Errorf("decodeUnsignedTokenAndValidateHeader() err = %v, want nil", err) 382*e7b1675dSTing-Kang Chang } 383*e7b1675dSTing-Kang Chang }) 384*e7b1675dSTing-Kang Chang } 385*e7b1675dSTing-Kang Chang} 386*e7b1675dSTing-Kang Chang 387*e7b1675dSTing-Kang Changfunc TestDecodeVerifyTokenFixedValues(t *testing.T) { 388*e7b1675dSTing-Kang Chang header := "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9" // Header example from https://tools.ietf.org/html/rfc7519#section-3.1 389*e7b1675dSTing-Kang Chang payload := "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ" // Payload example from https://tools.ietf.org/html/rfc7519#section-3.1 390*e7b1675dSTing-Kang Chang rawJWT, err := decodeUnsignedTokenAndValidateHeader(dotConcat(header, payload), "HS256", nil, nil) 391*e7b1675dSTing-Kang Chang if err != nil { 392*e7b1675dSTing-Kang Chang t.Errorf("decodeUnsignedTokenAndValidateHeader() err = %v, want nil", err) 393*e7b1675dSTing-Kang Chang } 394*e7b1675dSTing-Kang Chang iss, err := rawJWT.Issuer() 395*e7b1675dSTing-Kang Chang if err != nil { 396*e7b1675dSTing-Kang Chang t.Errorf("rawJWT.Issuer() err = %v, want nil", err) 397*e7b1675dSTing-Kang Chang } 398*e7b1675dSTing-Kang Chang if iss != "joe" { 399*e7b1675dSTing-Kang Chang t.Errorf("rawJWT.Issuer() = %q, want joe", iss) 400*e7b1675dSTing-Kang Chang } 401*e7b1675dSTing-Kang Chang exp, err := rawJWT.ExpiresAt() 402*e7b1675dSTing-Kang Chang if err != nil { 403*e7b1675dSTing-Kang Chang t.Errorf("rawJWT.ExpiresAt() err = %v, want nil", err) 404*e7b1675dSTing-Kang Chang } 405*e7b1675dSTing-Kang Chang wantExp := time.Unix(1300819380, 0) 406*e7b1675dSTing-Kang Chang if !exp.Equal(wantExp) { 407*e7b1675dSTing-Kang Chang t.Errorf("rawJWT.ExpiresAt() = %q, want %q", exp, wantExp) 408*e7b1675dSTing-Kang Chang } 409*e7b1675dSTing-Kang Chang cc, err := rawJWT.BooleanClaim("http://example.com/is_root") 410*e7b1675dSTing-Kang Chang if err != nil { 411*e7b1675dSTing-Kang Chang t.Errorf("rawJWT.BooleanClaim('http://example.com/is_root') err = %v want nil", err) 412*e7b1675dSTing-Kang Chang } 413*e7b1675dSTing-Kang Chang if cc != true { 414*e7b1675dSTing-Kang Chang t.Errorf("rawJWT.BooleanClaim('http://example.com/is_root') = %v, want true", cc) 415*e7b1675dSTing-Kang Chang } 416*e7b1675dSTing-Kang Chang} 417*e7b1675dSTing-Kang Chang 418*e7b1675dSTing-Kang Changfunc TestDecodeVerifyTokenPaylodWithInvalidEndcoding(t *testing.T) { 419*e7b1675dSTing-Kang Chang if _, err := decodeUnsignedTokenAndValidateHeader(dotConcat(base64Encode([]byte(`{"alg":"HS256"}`)), "_aSL&%"), "HS256", nil, nil); err == nil { 420*e7b1675dSTing-Kang Chang t.Errorf("decodeUnsignedTokenAndValidateHeader() err = nil, want error") 421*e7b1675dSTing-Kang Chang } 422*e7b1675dSTing-Kang Chang} 423