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, &notAStruct, certs)
   120  		So(err, ShouldErrLike, "bad body: can't deserialize JSON")
   121  	})
   122  }