go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/auth/jwt/jwt_test.go (about) 1 // Copyright 2021 The LUCI Authors. 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 package jwt 16 17 import ( 18 "context" 19 "encoding/base64" 20 "encoding/json" 21 "fmt" 22 "testing" 23 24 "go.chromium.org/luci/server/auth/signing/signingtest" 25 26 . "github.com/smartystreets/goconvey/convey" 27 . "go.chromium.org/luci/common/testing/assertions" 28 ) 29 30 func TestVerifyJWT(t *testing.T) { 31 t.Parallel() 32 33 type fakeBody struct { 34 Field string 35 } 36 37 ctx := context.Background() 38 signer := signingtest.NewSigner(nil) 39 certs, _ := signer.Certificates(ctx) 40 goodKeyID := signer.KeyNameForTest() 41 42 prepareJWT := func(alg, kid string, body fakeBody) string { 43 bodyBlob, err := json.Marshal(&body) 44 So(err, ShouldBeNil) 45 b64hdr := base64.RawURLEncoding.EncodeToString([]byte( 46 fmt.Sprintf(`{"alg": "%s","kid": "%s"}`, alg, kid), 47 )) 48 b64bdy := base64.RawURLEncoding.EncodeToString(bodyBlob) 49 _, sig, err := signer.SignBytes(ctx, []byte(b64hdr+"."+b64bdy)) 50 So(err, ShouldBeNil) 51 return b64hdr + "." + b64bdy + "." + base64.RawURLEncoding.EncodeToString(sig) 52 } 53 54 verifyJWT := func(tok string) (body fakeBody, err error) { 55 err = VerifyAndDecode(tok, &body, certs) 56 return 57 } 58 59 Convey("Happy path", t, func() { 60 body := fakeBody{"body"} 61 verifiedBody, err := verifyJWT(prepareJWT("RS256", goodKeyID, body)) 62 So(err, ShouldBeNil) 63 So(verifiedBody, ShouldResemble, body) 64 }) 65 66 Convey("Malformed JWT", t, func() { 67 _, err := verifyJWT("wat") 68 So(err, ShouldErrLike, "expected 3 components") 69 So(NotJWT.In(err), ShouldBeTrue) 70 }) 71 72 Convey("Bad header format (not b64)", t, func() { 73 _, err := verifyJWT("???.aaaa.aaaa") 74 So(err, ShouldErrLike, "bad JWT header: not base64") 75 So(NotJWT.In(err), ShouldBeTrue) 76 }) 77 78 Convey("Bad header format (not json)", t, func() { 79 _, err := verifyJWT("aaaa.aaaa.aaaa") 80 So(err, ShouldErrLike, "bad JWT header: can't deserialize JSON") 81 So(NotJWT.In(err), ShouldBeTrue) 82 }) 83 84 Convey("Bad algo", t, func() { 85 _, err := verifyJWT(prepareJWT("bad-algo", goodKeyID, fakeBody{"body"})) 86 So(err, ShouldErrLike, "only RS256 alg is supported") 87 So(NotJWT.In(err), ShouldBeFalse) 88 }) 89 90 Convey("Missing key ID", t, func() { 91 _, err := verifyJWT(prepareJWT("RS256", "", fakeBody{"body"})) 92 So(err, ShouldErrLike, "missing the signing key ID in the header") 93 So(NotJWT.In(err), ShouldBeFalse) 94 }) 95 96 Convey("Unknown key", t, func() { 97 _, err := verifyJWT(prepareJWT("RS256", "unknown-key", fakeBody{"body"})) 98 So(err, ShouldErrLike, "no such certificate") 99 So(NotJWT.In(err), ShouldBeFalse) 100 }) 101 102 Convey("Bad signature encoding", t, func() { 103 jwt := prepareJWT("RS256", goodKeyID, fakeBody{"body"}) 104 _, err := verifyJWT(jwt + "???") 105 So(err, ShouldErrLike, "can't base64 decode the signature") 106 So(NotJWT.In(err), ShouldBeFalse) 107 }) 108 109 Convey("Bad signature", t, func() { 110 jwt := prepareJWT("RS256", goodKeyID, fakeBody{"body"}) 111 _, err := verifyJWT(jwt[:len(jwt)-2]) 112 So(err, ShouldErrLike, "signature check error") 113 So(NotJWT.In(err), ShouldBeFalse) 114 }) 115 116 Convey("Bad body JSON", t, func() { 117 jwt := prepareJWT("RS256", goodKeyID, fakeBody{"body"}) 118 var notAStruct int64 119 err := VerifyAndDecode(jwt, ¬AStruct, certs) 120 So(err, ShouldErrLike, "bad body: can't deserialize JSON") 121 }) 122 }