From e141d88f801641051d6ef6900439eb2398f35cac Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Mon, 1 Jul 2019 23:51:35 -0300 Subject: [PATCH 01/99] ecdsa: create function generate test keys --- ecdsa_sha_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ecdsa_sha_test.go b/ecdsa_sha_test.go index 30619ec..58a6029 100644 --- a/ecdsa_sha_test.go +++ b/ecdsa_sha_test.go @@ -1 +1,26 @@ package jwt_test + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" +) + +var ( + es256PrivateKey1, es256PublicKey1 = genECDSAKeys(elliptic.P256()) + es256PrivateKey2, es256PublicKey2 = genECDSAKeys(elliptic.P256()) + + es384PrivateKey1, es384PublicKey1 = genECDSAKeys(elliptic.P384()) + es384PrivateKey2, es384PublicKey2 = genECDSAKeys(elliptic.P384()) + + es512PrivateKey1, es512PublicKey1 = genECDSAKeys(elliptic.P521()) + es512PrivateKey2, es512PublicKey2 = genECDSAKeys(elliptic.P521()) +) + +func genECDSAKeys(c elliptic.Curve) (*ecdsa.PrivateKey, *ecdsa.PublicKey) { + priv, err := ecdsa.GenerateKey(c, rand.Reader) + if err != nil { + panic(err) + } + return priv, &priv.PublicKey +} From f4f6ae0fd759902a22d35aefabdc085d30bd04f3 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Mon, 1 Jul 2019 23:52:58 -0300 Subject: [PATCH 02/99] sign: add test cases for ECDSA --- sign_test.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/sign_test.go b/sign_test.go index a6bb90e..074853f 100644 --- a/sign_test.go +++ b/sign_test.go @@ -240,7 +240,86 @@ func TestSign(t *testing.T) { verifyErr: nil, }, }, - "ECDSA": []testCase{}, + "ECDSA": []testCase{ + { + alg: jwt.NewES256(es256PrivateKey1, nil), + hd: jwt.Header{}, + payload: tp, + verifyAlg: jwt.NewES256(nil, es256PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES256(es256PrivateKey1, nil), + hd: jwt.Header{}, + payload: tp, + verifyAlg: jwt.NewES256(es256PrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + hd: jwt.Header{}, + payload: tp, + verifyAlg: jwt.NewES384(nil, es384PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + hd: jwt.Header{}, + payload: tp, + verifyAlg: jwt.NewES384(es384PrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + hd: jwt.Header{}, + payload: tp, + verifyAlg: jwt.NewES512(nil, es512PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + hd: jwt.Header{}, + payload: tp, + verifyAlg: jwt.NewES512(es512PrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + }, "Ed25519": []testCase{}, } for k, v := range testCases { From 2548b4254a5188f9f7d627d31b2c50e4343bbf44 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Mon, 1 Jul 2019 23:55:33 -0300 Subject: [PATCH 03/99] ecdsa: fix nil pointer for public key --- ecdsa_sha.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ecdsa_sha.go b/ecdsa_sha.go index 778d639..0699b3f 100644 --- a/ecdsa_sha.go +++ b/ecdsa_sha.go @@ -40,6 +40,9 @@ type ecdsaSHA struct { } func newECDSASHA(name string, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, sha crypto.Hash) *ecdsaSHA { + if pub == nil { + pub = &priv.PublicKey + } return &ecdsaSHA{ name: name, priv: priv, From c7a44c20a4d0e71e68bcf44b0de16ede1cec28b8 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 00:28:40 -0300 Subject: [PATCH 04/99] ed25519: add test cases --- ed25519_test.go | 8 ++++++++ internal/ed25519_go1_12.go | 17 +++++++++++++++++ sign_test.go | 16 +++++++++++++++- 3 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 ed25519_test.go create mode 100644 internal/ed25519_go1_12.go diff --git a/ed25519_test.go b/ed25519_test.go new file mode 100644 index 0000000..e57692a --- /dev/null +++ b/ed25519_test.go @@ -0,0 +1,8 @@ +package jwt_test + +import "github.com/gbrlsnchs/jwt/v3/internal" + +var ( + ed25519PrivateKey1, ed25519PublicKey1 = internal.GenerateEd25519Keys() + ed25519PrivateKey2, ed25519PublicKey2 = internal.GenerateEd25519Keys() +) diff --git a/internal/ed25519_go1_12.go b/internal/ed25519_go1_12.go new file mode 100644 index 0000000..95b7e55 --- /dev/null +++ b/internal/ed25519_go1_12.go @@ -0,0 +1,17 @@ +// +build !go1.13 + +package internal + +import ( + "crypto/rand" + + "golang.org/x/crypto/ed25519" +) + +func GenerateEd25519Keys() (ed25519.PrivateKey, ed25519.PublicKey) { + pub, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + panic(err) + } + return priv, pub +} diff --git a/sign_test.go b/sign_test.go index 074853f..7a72d49 100644 --- a/sign_test.go +++ b/sign_test.go @@ -320,7 +320,21 @@ func TestSign(t *testing.T) { verifyErr: nil, }, }, - "Ed25519": []testCase{}, + "Ed25519": []testCase{ + { + alg: jwt.NewEd25519(ed25519PrivateKey1, nil), + hd: jwt.Header{}, + payload: tp, + verifyAlg: jwt.NewEd25519(ed25519PrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "Ed25519", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + }, } for k, v := range testCases { t.Run(k, func(t *testing.T) { From da5737168f86865aaa12e5470a169d961e7bc818 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 00:33:23 -0300 Subject: [PATCH 05/99] ed25519: fix description of errors --- ed25519_go1_12.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ed25519_go1_12.go b/ed25519_go1_12.go index 16d2fc9..0306fd6 100644 --- a/ed25519_go1_12.go +++ b/ed25519_go1_12.go @@ -9,11 +9,11 @@ import ( var ( // ErrEd25519PrivKey is the error for trying to sign a JWT with a nil private key. - ErrEd25519PrivKey = internal.NewError("jwt: edDSA private key is nil") + ErrEd25519PrivKey = internal.NewError("jwt: Ed25519 private key is nil") // ErrEd25519PubKey is the error for trying to verify a JWT with a nil public key. - ErrEd25519PubKey = internal.NewError("jwt: edDSA public key is nil") + ErrEd25519PubKey = internal.NewError("jwt: Ed25519 public key is nil") // ErrEd25519Verification is the error for when verification with edDSA fails. - ErrEd25519Verification = internal.NewError("jwt: edDSA verification failed") + ErrEd25519Verification = internal.NewError("jwt: Ed25519 verification failed") _ Algorithm = new(edDSA) ) From 2eb1bf99064bad9f597294e8934347e2ea44ef63 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 00:34:57 -0300 Subject: [PATCH 06/99] hmac: add new testing key --- hmac_sha_test.go | 5 ++++- sign_test.go | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/hmac_sha_test.go b/hmac_sha_test.go index edfdcee..70f52b9 100644 --- a/hmac_sha_test.go +++ b/hmac_sha_test.go @@ -1,3 +1,6 @@ package jwt_test -var hmacKey = []byte("secret") +var ( + hmacKey1 = []byte("secret") + hmacKey2 = []byte("terces") +) diff --git a/sign_test.go b/sign_test.go index 7a72d49..b83279b 100644 --- a/sign_test.go +++ b/sign_test.go @@ -41,10 +41,10 @@ func TestSign(t *testing.T) { testCases := map[string][]testCase{ "HMAC": []testCase{ { - alg: jwt.NewHS256(hmacKey), + alg: jwt.NewHS256(hmacKey1), hd: jwt.Header{}, payload: tp, - verifyAlg: jwt.NewHS256(hmacKey), + verifyAlg: jwt.NewHS256(hmacKey1), wantHeader: jwt.Header{ Algorithm: "HS256", Type: "JWT", @@ -54,10 +54,10 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewHS384(hmacKey), + alg: jwt.NewHS384(hmacKey1), hd: jwt.Header{}, payload: tp, - verifyAlg: jwt.NewHS384(hmacKey), + verifyAlg: jwt.NewHS384(hmacKey1), wantHeader: jwt.Header{ Algorithm: "HS384", Type: "JWT", @@ -67,10 +67,10 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewHS512(hmacKey), + alg: jwt.NewHS512(hmacKey1), hd: jwt.Header{}, payload: tp, - verifyAlg: jwt.NewHS512(hmacKey), + verifyAlg: jwt.NewHS512(hmacKey1), wantHeader: jwt.Header{ Algorithm: "HS512", Type: "JWT", From a6057a8c8e786570a2943ebbf2fe85b7a4df678f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 00:44:51 -0300 Subject: [PATCH 07/99] ed25519: fix nil public key --- ed25519_go1_12.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ed25519_go1_12.go b/ed25519_go1_12.go index 0306fd6..b28007d 100644 --- a/ed25519_go1_12.go +++ b/ed25519_go1_12.go @@ -25,6 +25,9 @@ type edDSA struct { // NewEd25519 creates a new algorithm using EdDSA and SHA-512. func NewEd25519(priv ed25519.PrivateKey, pub ed25519.PublicKey) Algorithm { + if pub == nil { + pub = priv.Public().(ed25519.PublicKey) + } return &edDSA{priv: priv, pub: pub} } From dc16f8fc51ca620c91417c8f0c9770b8628d9b81 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 01:11:58 -0300 Subject: [PATCH 08/99] README: update examples --- README.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index d10d711..48b0303 100644 --- a/README.md +++ b/README.md @@ -59,9 +59,9 @@ import ( ### Signing a simple JWT ```go now := time.Now() -hs256 := jwt.NewHMAC(jwt.SHA256, []byte("secret")) -h := jwt.Header{KeyID: "kid"} -p := jwt.Payload{ +hs256 := jwt.NewHS256([]byte("secret")) +hd := jwt.Header{KeyID: "kid"} +pl := jwt.Payload{ Issuer: "gbrlsnchs", Subject: "someone", Audience: jwt.Audience{"https://golang.org", "https://jwt.io"}, @@ -70,7 +70,7 @@ p := jwt.Payload{ IssuedAt: now.Unix(), JWTID: "foobar", } -token, err := jwt.Sign(h, p, hs256) +token, err := jwt.Sign(hs256, hd, pl) if err != nil { // Handle error. } @@ -90,9 +90,9 @@ type CustomPayload struct { #### Now initialize, marshal and sign it ```go now := time.Now() -hs256 := jwt.NewHMAC(jwt.SHA256, []byte("secret")) -h := jwt.Header{KeyID: "kid"} -p := CustomPayload{ +hs256 := jwt.NewHS256([]byte("secret")) +hd := jwt.Header{KeyID: "kid"} +pl := CustomPayload{ Payload: jwt.Payload{ Issuer: "gbrlsnchs", Subject: "someone", @@ -105,7 +105,7 @@ p := CustomPayload{ IsLoggedIn: true, CustomField: "myCustomField", } -token, err := jwt.Sign(h, p, hs256) +token, err := jwt.Sign(hs256, hd, pl) if err != nil { // Handle error. } @@ -115,32 +115,34 @@ log.Printf("token = %s", token) ### Verifying and validating a JWT ```go now := time.Now() -hs256 := jwt.NewHMAC(jwt.SHA256, []byte("secret")) +hs256 := jwt.NewHS256([]byte("secret")) token := []byte("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." + "lZ1zDoGNAv3u-OclJtnoQKejE8_viHlMtGlAxE8AE0Q") -raw, err := jwt.Parse(token) +raw, err := jwt.Verify(hs256, token) if err != nil { // Handle error. } -if err = raw.Verify(hs256); err != nil { - // Handle error. -} var ( - h jwt.Header - p CustomPayload + hd = raw.Header() + pl CustomPayload ) -if h, err = raw.Decode(&p); err != nil { +if err = raw.Decode(&pl); err != nil { // Handle error. } -fmt.Println(h.Algorithm) -fmt.Println(h.KeyID) +fmt.Println(hd.Algorithm) +fmt.Println(hd.KeyID) iatValidator := jwt.IssuedAtValidator(now) expValidator := jwt.ExpirationTimeValidator(now, true) -audValidator := jwt.AudienceValidator(jwt.Audience{"https://golang.org", "https://jwt.io", "https://google.com", "https://reddit.com"}) -if err := p.Validate(iatValidator, expValidator, audValidator); err != nil { +audValidator := jwt.AudienceValidator(jwt.Audience{ + "https://golang.org", + "https://jwt.io", + "https://google.com", + "https://reddit.com", +}) +if err := pl.Validate(iatValidator, expValidator, audValidator); err != nil { switch err { case jwt.ErrIatValidation: // handle "iat" validation error From ad2e9a2f4b77b9d827129d0c0b3496091647dd06 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 01:17:29 -0300 Subject: [PATCH 09/99] README: fix instruction --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 48b0303..47f33e6 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ log.Printf("token = %s", token) ``` ### Signing a JWT with public claims -#### First, create a custom type and embed a JWT pointer in it +#### First, create a custom type and embed a `Payload` in it ```go type CustomPayload struct { jwt.Payload From 7c485b8c9eae4857db363052e583c66894657f71 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 01:33:58 -0300 Subject: [PATCH 10/99] Remove interface Validator --- payload.go | 6 ------ validator.go | 5 ----- 2 files changed, 11 deletions(-) delete mode 100644 validator.go diff --git a/payload.go b/payload.go index d072dbb..915640f 100644 --- a/payload.go +++ b/payload.go @@ -1,11 +1,5 @@ package jwt -var ( - _ Validator = new(Payload) - _ Validator = &struct{ Payload }{} - _ Validator = &struct{ *Payload }{} -) - // Payload is a JWT payload according to the RFC 7519. type Payload struct { Issuer string `json:"iss,omitempty"` diff --git a/validator.go b/validator.go deleted file mode 100644 index 35064ca..0000000 --- a/validator.go +++ /dev/null @@ -1,5 +0,0 @@ -package jwt - -type Validator interface { - Validate(...ValidatorFunc) error -} From 606a118cd6a669978cd2391a7abfa49e557f3023 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 01:34:16 -0300 Subject: [PATCH 11/99] Makefile: use new template This template is based on the Makefile from https://github.com/gbrlsnchs/prompt. --- Makefile | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 077a1d1..02115c3 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,16 @@ -install: export GO111MODULE := on -install: - current_dir := ${PWD} - @cd ${GOPATH} - go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.16.0 - go get -u golang.org/x/tools/cmd/goimports +export GO111MODULE := on -lint: golint_cmd := golint -set_exit_status -lint: - @echo "+++ 'lint' (${golint_cmd})" - @${golint_cmd} +all: export GO111MODULE := off +all: + go get -u golang.org/x/tools/cmd/goimports + go get -u golang.org/x/lint/golint -test-units: export GO111MODULE := on -test-units: go_test_flags := -v -coverprofile=c.out -ifdef GO_TEST_RUN -test-units: go_test_flags += -run=${GO_TEST_RUN} -endif -test-units: GO_TEST_TARGET ?= ./... -test-units: go_test_cmd := go test ${go_test_flags} ${GO_TEST_TARGET} -test-units: - @echo "+++ 'test-units' (${go_test_cmd})" - @${go_test_cmd} +fix: + @goimports -w *.go -test-cover: export GO111MODULE := on -test-cover: go_tool_cover := go tool cover -func=c.out -test-cover: - @echo "+++ 'test-cover' (${go_tool_cover})" - @${go_tool_cover} +lint: + @! goimports -d . | grep -vF "no errors" + @golint -set_exit_status ./... -test: test-units lint +test: lint + @go test -v ./... From b44d683ae4cf6096b6c08fe71be85f3e6d52fdef Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 01:36:36 -0300 Subject: [PATCH 12/99] internal: add description to Ed25519 function --- internal/ed25519_go1_12.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/ed25519_go1_12.go b/internal/ed25519_go1_12.go index 95b7e55..3436a4c 100644 --- a/internal/ed25519_go1_12.go +++ b/internal/ed25519_go1_12.go @@ -8,6 +8,7 @@ import ( "golang.org/x/crypto/ed25519" ) +// GenerateEd25519Keys generates a pair of keys for testing purposes. func GenerateEd25519Keys() (ed25519.PrivateKey, ed25519.PublicKey) { pub, priv, err := ed25519.GenerateKey(rand.Reader) if err != nil { From 2cf2cb8e49766953e22998e4b2eb223553828798 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 01:37:44 -0300 Subject: [PATCH 13/99] circleci: add config file :tada: This closes #28. --- .circleci/config.yml | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..ebce6b5 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,47 @@ +version: 2.1 + +executors: + golang: + parameters: + version: + description: Go version + type: string + docker: + - image: circleci/golang:<< parameters.version >> + +workflows: + test: + jobs: + - go1_9 + - go1_10 + - go1_11 + - go1_12 + +jobs: + go1_12: &template + executor: + name: golang + version: "1.12" + steps: + - checkout + - run: make + - run: make test + + go1_11: + <<: *template + executor: + name: golang + version: "1.11" + + go1_10: &nomod_template + <<: *template + executor: + name: golang + version: "1.10" + working_directory: /go/src/github.com/gbrlsnchs/jwt + + go1_9: + <<: *nomod_template + executor: + name: golang + version: "1.9" From 50bc5ac419621f765e4d95be48da00b39d1f05f2 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 01:58:06 -0300 Subject: [PATCH 14/99] README: update badges and refactor examples --- README.md | 57 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 47f33e6..f27e5bb 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,17 @@ # jwt (JSON Web Token for Go) +[![JWT compatible](https://jwt.io/img/badge.svg)](https://jwt.io) -

- -

- -

- -

- -

- Build Status - Go Report Card - Sourcegraph - GoDoc - Minimal Version - Join the chat at https://gitter.im/gbrlsnchs/jwt -

- -

- JWT compatible -

- -## Important -Branch `master` is unstable, **always** use tagged versions. That way it is possible to differentiate pre-release tags from production ones. -In other words, API changes all the time in `master`. It's a place for public experiment. Thus, make use of the latest stable version via Go modules. +[![CircleCI](https://circleci.com/gh/gbrlsnchs/jwt.svg?style=shield)](https://circleci.com/gh/gbrlsnchs/jwt) +[![Go Report Card](https://goreportcard.com/badge/github.com/gbrlsnchs/jwt)](https://goreportcard.com/report/github.com/gbrlsnchs/jwt) +[![GoDoc](https://godoc.org/github.com/gbrlsnchs/jwt?status.svg)](https://godoc.org/github.com/gbrlsnchs/jwt) +[![Join the chat at https://gitter.im/gbrlsnchs/jwt](https://badges.gitter.im/gbrlsnchs/jwt.svg)](https://gitter.im/gbrlsnchs/jwt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## About This package is a JWT signer, verifier and validator for [Go](https://golang.org) (or Golang). Although there are many JWT packages out there for Go, many lack support for some signing, verifying or validation methods and, when they don't, they're overcomplicated. This package tries to mimic the ease of use from [Node JWT library](https://github.com/auth0/node-jsonwebtoken)'s API while following the [Effective Go](https://golang.org/doc/effective_go.html) guidelines. -Support for [JWE](https://tools.ietf.org/html/rfc7516) isn't provided. Instead, [JWS](https://tools.ietf.org/html/rfc7515) is used, narrowed down to the [JWT specification](https://tools.ietf.org/html/rfc7519). +Support for [JWE](https://tools.ietf.org/html/rfc7516) isn't provided (not yet but is in the roadmap, see #17). Instead, [JWS](https://tools.ietf.org/html/rfc7515) is used, narrowed down to the [JWT specification](https://tools.ietf.org/html/rfc7519). ### Supported signing methods | | SHA-256 | SHA-384 | SHA-512 | @@ -41,6 +22,10 @@ Support for [JWE](https://tools.ietf.org/html/rfc7516) isn't provided. Instead, | ECDSA | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | EdDSA | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: | +## Important +Branch `master` is unstable, **always** use tagged versions. That way it is possible to differentiate pre-release tags from production ones. +In other words, API changes all the time in `master`. It's a place for public experiment. Thus, make use of the latest stable version via Go modules. + ## Usage Full documentation [here](https://godoc.org/github.com/gbrlsnchs/jwt). @@ -56,7 +41,10 @@ import ( ) ``` -### Signing a simple JWT +### Examples +
Signing a JWT with default claims +

+ ```go now := time.Now() hs256 := jwt.NewHS256([]byte("secret")) @@ -77,7 +65,12 @@ if err != nil { log.Printf("token = %s", token) ``` -### Signing a JWT with public claims +

+
+ +
Signing a JWT with custom claims +

+ #### First, create a custom type and embed a `Payload` in it ```go type CustomPayload struct { @@ -112,7 +105,12 @@ if err != nil { log.Printf("token = %s", token) ``` -### Verifying and validating a JWT +

+
+ +
Verifying and validating a JWT +

+ ```go now := time.Now() hs256 := jwt.NewHS256([]byte("secret")) @@ -154,6 +152,9 @@ if err := pl.Validate(iatValidator, expValidator, audValidator); err != nil { } ``` +

+
+ ## Contributing ### How to help - For bugs and opinions, please [open an issue](https://github.com/gbrlsnchs/jwt/issues/new) From 77bb3903a92e5113eda85601f971ede6924dfd88 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 02:00:10 -0300 Subject: [PATCH 15/99] README: fix example description --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f27e5bb..b318584 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ type CustomPayload struct { } ``` -#### Now initialize, marshal and sign it +#### Now initialize and sign it ```go now := time.Now() hs256 := jwt.NewHS256([]byte("secret")) From a67cb56d8a2316d049f99417425187e03a85caac Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 02:01:31 -0300 Subject: [PATCH 16/99] Remove PNG files --- encoded_jwt.png | Bin 76794 -> 0 bytes gopher_head.png | Bin 31174 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 encoded_jwt.png delete mode 100644 gopher_head.png diff --git a/encoded_jwt.png b/encoded_jwt.png deleted file mode 100644 index 0f5ed18fa646c90b2d36e278b6387237ef4f9f5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76794 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 Date: Tue, 2 Jul 2019 02:02:33 -0300 Subject: [PATCH 17/99] Remove TravisCI config file --- .travis.yml | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8b712a2..0000000 --- a/.travis.yml +++ /dev/null @@ -1,17 +0,0 @@ -os: - - 'linux' - - 'osx' - - 'windows' -sudo: false - -language: 'go' -go: - - '1.11' - - '1.12beta2' - -install: - - 'cd $GOPATH' - - 'if [ "$(go version | awk ''{print $3}'')" == "go1.10" ]; then go get -u golang.org/x/vgo && BIN=vgo; else BIN=go; fi' - - 'mv ${TRAVIS_BUILD_DIR} ${TRAVIS_HOME}/test' - - 'cd ${TRAVIS_HOME}/test' -script: '${BIN} test -v -race -count=10' From d44fd3030d3a2f995fff4ac6f846633a8871557f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 2 Jul 2019 09:42:50 -0300 Subject: [PATCH 18/99] Add new type Time --- time.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 time.go diff --git a/time.go b/time.go new file mode 100644 index 0000000..e6bd48b --- /dev/null +++ b/time.go @@ -0,0 +1,34 @@ +package jwt + +import ( + "encoding/json" + "time" +) + +var epoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) + +// Time is the allowed format for time, as per the RFC 7519. +type Time struct { + time.Time +} + +// MarshalJSON implements a marshaling function for time-related claims. +func (t Time) MarshalJSON() ([]byte, error) { + if t.Before(epoch) { + return json.Marshal(0) + } + return json.Marshal(t.Unix()) +} + +// UnmarshalJSON implements an unmarshaling function for time-related claims. +func (t *Time) UnmarshalJSON(b []byte) error { + var tt time.Time + if err := json.Unmarshal(b, &tt); err != nil { + return err + } + if tt.Before(epoch) { + tt = epoch + } + t.Time = tt + return nil +} From 54abd86b36e962d67d6ed6c7bace68827bc96d47 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 13:47:46 -0300 Subject: [PATCH 19/99] sign: use functional options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes the signature of `Sign` again, but for the better. I've decided to use functional options after reading a blog post¹ from Dave Cheney about it. It introduces a friendlier way to sign a JWT, as passing a `Header` is no longer mandatory. ¹ https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis --- sign.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/sign.go b/sign.go index b7de417..9d73a97 100644 --- a/sign.go +++ b/sign.go @@ -5,8 +5,15 @@ import ( "encoding/json" ) -// Sign generates a JWT from hd and payload and signs it with alg. -func Sign(alg Algorithm, hd Header, payload interface{}) ([]byte, error) { +// SignOption is a functional option for signing. +type SignOption func(*Header) + +// Sign signs a payload with alg. +func Sign(payload interface{}, alg Algorithm, opts ...SignOption) ([]byte, error) { + var hd Header + for _, opt := range opts { + opt(&hd) + } // Override some values or set them if empty. hd.Algorithm = alg.Name() hd.Type = "JWT" @@ -38,3 +45,17 @@ func Sign(alg Algorithm, hd Header, payload interface{}) ([]byte, error) { enc.Encode(token[h64len+1+p64len+1:], sig) return token, nil } + +// ContentType sets the "cty" claim for a Header before signing. +func ContentType(cty string) SignOption { + return func(hd *Header) { + hd.ContentType = cty + } +} + +// KeyID sets the "kid" claim for a Header before signing. +func KeyID(kid string) SignOption { + return func(hd *Header) { + hd.KeyID = kid + } +} From 3062562b2342568aa22a1ba73e0788570c8ec2aa Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 18:41:36 -0300 Subject: [PATCH 20/99] header: reorder property --- header.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/header.go b/header.go index 2e48cd4..ae4fb6d 100644 --- a/header.go +++ b/header.go @@ -5,7 +5,7 @@ package jwt // Parameters are ordered according to the RFC 7515. type Header struct { Algorithm string `json:"alg,omitempty"` + ContentType string `json:"cty,omitempty"` KeyID string `json:"kid,omitempty"` Type string `json:"typ,omitempty"` - ContentType string `json:"cty,omitempty"` } From 808c70670bb31408c37e3242e4b2ae4d48eaca7c Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 18:42:02 -0300 Subject: [PATCH 21/99] payload: remove method to run validators --- payload.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/payload.go b/payload.go index 915640f..4874bdb 100644 --- a/payload.go +++ b/payload.go @@ -10,13 +10,3 @@ type Payload struct { IssuedAt int64 `json:"iat,omitempty"` JWTID string `json:"jti,omitempty"` } - -// Validate validates Payload claims. -func (p *Payload) Validate(funcs ...ValidatorFunc) error { - for _, fn := range funcs { - if err := fn(p); err != nil { - return err - } - } - return nil -} From db41bc6e169395fdc4405e02eb4d446962a21e39 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 18:43:17 -0300 Subject: [PATCH 22/99] validators: change signature and use methods In order to integrate validators with the proposed functional options, the `Payload` must be passed somehow. To avoid repetition when using validators, it's easier to use them as methods instead. This also facilitates their usage with custom payloads. --- validators.go | 46 ++++++++++++++--------------- validators_test.go | 73 +++++++++++++++++++++------------------------- 2 files changed, 56 insertions(+), 63 deletions(-) diff --git a/validators.go b/validators.go index d9353c1..94fcb43 100644 --- a/validators.go +++ b/validators.go @@ -24,14 +24,14 @@ var ( // ValidatorFunc is a function for running extra // validators when parsing a Payload string. -type ValidatorFunc func(*Payload) error +type ValidatorFunc func() error // AudienceValidator validates the "aud" claim. // It checks if at least one of the audiences in the JWT's payload is listed in aud. -func AudienceValidator(aud Audience) ValidatorFunc { - return func(p *Payload) error { +func (pl *Payload) AudienceValidator(aud Audience) ValidatorFunc { + return func() error { for _, serverAud := range aud { - for _, clientAud := range p.Audience { + for _, clientAud := range pl.Audience { if clientAud == serverAud { return nil } @@ -42,9 +42,9 @@ func AudienceValidator(aud Audience) ValidatorFunc { } // ExpirationTimeValidator validates the "exp" claim. -func ExpirationTimeValidator(now time.Time, validateZero bool) ValidatorFunc { - return func(p *Payload) error { - expint := p.ExpirationTime +func (pl *Payload) ExpirationTimeValidator(now time.Time, validateZero bool) ValidatorFunc { + return func() error { + expint := pl.ExpirationTime if !validateZero && expint == 0 { return nil } @@ -56,9 +56,9 @@ func ExpirationTimeValidator(now time.Time, validateZero bool) ValidatorFunc { } // IssuedAtValidator validates the "iat" claim. -func IssuedAtValidator(now time.Time) ValidatorFunc { - return func(p *Payload) error { - if iat := time.Unix(p.IssuedAt, 0); now.Before(iat) { +func (pl *Payload) IssuedAtValidator(now time.Time) ValidatorFunc { + return func() error { + if iat := time.Unix(pl.IssuedAt, 0); now.Before(iat) { return ErrIatValidation } return nil @@ -66,19 +66,19 @@ func IssuedAtValidator(now time.Time) ValidatorFunc { } // IssuerValidator validates the "iss" claim. -func IssuerValidator(iss string) ValidatorFunc { - return func(p *Payload) error { - if p.Issuer != iss { +func (pl *Payload) IssuerValidator(iss string) ValidatorFunc { + return func() error { + if pl.Issuer != iss { return ErrIssValidation } return nil } } -// IDValidator validates the "jti" claim. -func IDValidator(jti string) ValidatorFunc { - return func(p *Payload) error { - if p.JWTID != jti { +// JWTIDValidator validates the "jti" claim. +func (pl *Payload) JWTIDValidator(jti string) ValidatorFunc { + return func() error { + if pl.JWTID != jti { return ErrJtiValidation } return nil @@ -86,9 +86,9 @@ func IDValidator(jti string) ValidatorFunc { } // NotBeforeValidator validates the "nbf" claim. -func NotBeforeValidator(now time.Time) ValidatorFunc { - return func(p *Payload) error { - if nbf := time.Unix(p.NotBefore, 0); now.Before(nbf) { +func (pl *Payload) NotBeforeValidator(now time.Time) ValidatorFunc { + return func() error { + if nbf := time.Unix(pl.NotBefore, 0); now.Before(nbf) { return ErrNbfValidation } return nil @@ -96,9 +96,9 @@ func NotBeforeValidator(now time.Time) ValidatorFunc { } // SubjectValidator validates the "sub" claim. -func SubjectValidator(sub string) ValidatorFunc { - return func(p *Payload) error { - if p.Subject != sub { +func (pl *Payload) SubjectValidator(sub string) ValidatorFunc { + return func() error { + if pl.Subject != sub { return ErrSubValidation } return nil diff --git a/validators_test.go b/validators_test.go index 09e1b40..33b1890 100644 --- a/validators_test.go +++ b/validators_test.go @@ -1,9 +1,6 @@ package jwt_test import ( - "reflect" - "runtime" - "strings" "testing" "time" @@ -20,46 +17,42 @@ func TestValidators(t *testing.T) { sub := "sub" iss := "iss" testCases := []struct { - p Payload - vl ValidatorFunc - err error + claim string + vl ValidatorFunc + err error }{ - {Payload{Issuer: iss}, IssuerValidator("iss"), nil}, - {Payload{Issuer: iss}, IssuerValidator("not_iss"), ErrIssValidation}, - {Payload{Subject: sub}, SubjectValidator("sub"), nil}, - {Payload{Subject: sub}, SubjectValidator("not_sub"), ErrSubValidation}, - {Payload{Audience: aud}, AudienceValidator(Audience{"aud"}), nil}, - {Payload{Audience: aud}, AudienceValidator(Audience{"foo", "aud1"}), nil}, - {Payload{Audience: aud}, AudienceValidator(Audience{"bar", "aud2"}), nil}, - {Payload{Audience: aud}, AudienceValidator(Audience{"baz", "aud3"}), nil}, - {Payload{Audience: aud}, AudienceValidator(Audience{"qux", "aud4"}), ErrAudValidation}, - {Payload{Audience: aud}, AudienceValidator(Audience{"not_aud"}), ErrAudValidation}, - {Payload{ExpirationTime: exp}, ExpirationTimeValidator(now, true), nil}, - {Payload{ExpirationTime: exp}, ExpirationTimeValidator(now, false), nil}, - {Payload{ExpirationTime: exp}, ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), true), nil}, - {Payload{ExpirationTime: exp}, ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), false), nil}, - {Payload{ExpirationTime: exp}, ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), true), ErrExpValidation}, - {Payload{ExpirationTime: exp}, ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), false), ErrExpValidation}, - {Payload{}, ExpirationTimeValidator(time.Now(), false), nil}, - {Payload{}, ExpirationTimeValidator(time.Now(), true), ErrExpValidation}, - {Payload{NotBefore: nbf}, NotBeforeValidator(now), ErrNbfValidation}, - {Payload{NotBefore: nbf}, NotBeforeValidator(time.Unix(now.Unix()+int64(15*time.Second), 0)), nil}, - {Payload{NotBefore: nbf}, NotBeforeValidator(time.Unix(now.Unix()-int64(15*time.Second), 0)), ErrNbfValidation}, - {Payload{}, NotBeforeValidator(time.Now()), nil}, - {Payload{IssuedAt: iat}, IssuedAtValidator(now), nil}, - {Payload{IssuedAt: iat}, IssuedAtValidator(time.Unix(now.Unix()+1, 0)), nil}, - {Payload{IssuedAt: iat}, IssuedAtValidator(time.Unix(now.Unix()-1, 0)), ErrIatValidation}, - {Payload{}, IssuedAtValidator(time.Now()), nil}, - {Payload{JWTID: jti}, IDValidator("jti"), nil}, - {Payload{JWTID: jti}, IDValidator("not_jti"), ErrJtiValidation}, + {"iss", (&Payload{Issuer: iss}).IssuerValidator("iss"), nil}, + {"iss", (&Payload{Issuer: iss}).IssuerValidator("not_iss"), ErrIssValidation}, + {"sub", (&Payload{Subject: sub}).SubjectValidator("sub"), nil}, + {"sub", (&Payload{Subject: sub}).SubjectValidator("not_sub"), ErrSubValidation}, + {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"aud"}), nil}, + {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"foo", "aud1"}), nil}, + {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"bar", "aud2"}), nil}, + {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"baz", "aud3"}), nil}, + {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"qux", "aud4"}), ErrAudValidation}, + {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"not_aud"}), ErrAudValidation}, + {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(now, true), nil}, + {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(now, false), nil}, + {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), true), nil}, + {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), false), nil}, + {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), true), ErrExpValidation}, + {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), false), ErrExpValidation}, + {"exp", (&Payload{}).ExpirationTimeValidator(time.Now(), false), nil}, + {"exp", (&Payload{}).ExpirationTimeValidator(time.Now(), true), ErrExpValidation}, + {"nbf", (&Payload{NotBefore: nbf}).NotBeforeValidator(now), ErrNbfValidation}, + {"nbf", (&Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()+int64(15*time.Second), 0)), nil}, + {"nbf", (&Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()-int64(15*time.Second), 0)), ErrNbfValidation}, + {"nbf", (&Payload{}).NotBeforeValidator(time.Now()), nil}, + {"iat", (&Payload{IssuedAt: iat}).IssuedAtValidator(now), nil}, + {"iat", (&Payload{IssuedAt: iat}).IssuedAtValidator(time.Unix(now.Unix()+1, 0)), nil}, + {"iat", (&Payload{IssuedAt: iat}).IssuedAtValidator(time.Unix(now.Unix()-1, 0)), ErrIatValidation}, + {"iat", (&Payload{}).IssuedAtValidator(time.Now()), nil}, + {"jti", (&Payload{JWTID: jti}).JWTIDValidator("jti"), nil}, + {"jti", (&Payload{JWTID: jti}).JWTIDValidator("not_jti"), ErrJtiValidation}, } for _, tc := range testCases { - fn := runtime.FuncForPC(reflect.ValueOf(tc.vl).Pointer()) - name := fn.Name()[:] - name = strings.TrimPrefix(name, "github.com/gbrlsnchs/jwt/v3.") - name = strings.TrimSuffix(name, ".func1") - t.Run(name, func(t *testing.T) { - if want, got := tc.err, tc.vl(&tc.p); want != got { + t.Run(tc.claim, func(t *testing.T) { + if want, got := tc.err, tc.vl(); want != got { t.Errorf("want %v, got %v", want, got) } }) From 1c6a7d4d583f9e9def998fb62705a3f8fc2acf39 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 18:46:38 -0300 Subject: [PATCH 23/99] raw_token: use inner variables to decode Payload By setting some variables within the `RawToken`, it's possible to decode a `Payload` only after verifying the signature, since decoding may be costing and meaningless when signature verification fails. --- raw_token.go | 45 +++++++++++++++++++++++---------------------- raw_token_test.go | 47 ----------------------------------------------- 2 files changed, 23 insertions(+), 69 deletions(-) delete mode 100644 raw_token_test.go diff --git a/raw_token.go b/raw_token.go index c25ec03..46e6362 100644 --- a/raw_token.go +++ b/raw_token.go @@ -9,32 +9,33 @@ var ErrMalformed = internal.NewError("jwt: malformed token") type RawToken struct { token []byte sep1, sep2 int - valid bool - hd Header -} + alg Algorithm -// Decode decodes a raw JWT into a payload and returns its header. -func (raw RawToken) Decode(payload interface{}) error { - if !raw.valid { - return ErrMalformed - } - return internal.Decode(raw.payload(), payload) + // Verify options. + payloadAddr interface{} + payloadValidators []ValidatorFunc } -// Header returns a JOSE Header extracted from a JWT. -func (raw RawToken) Header() Header { - return raw.hd -} +func (rt *RawToken) header() []byte { return rt.token[:rt.sep1] } +func (rt *RawToken) headerPayload() []byte { return rt.token[:rt.sep2] } +func (rt *RawToken) payload() []byte { return rt.token[rt.sep1+1 : rt.sep2] } +func (rt *RawToken) sig() []byte { return rt.token[rt.sep2+1:] } -func (raw RawToken) header() []byte { return raw.token[:raw.sep1] } -func (raw RawToken) headerPayload() []byte { return raw.token[:raw.sep2] } -func (raw RawToken) payload() []byte { return raw.token[raw.sep1+1 : raw.sep2] } -func (raw RawToken) sig() []byte { return raw.token[raw.sep2+1:] } +func (rt *RawToken) setToken(token []byte, sep1, sep2 int) { + rt.sep1 = sep1 + rt.sep2 = sep1 + 1 + sep2 + rt.token = token +} -func (raw RawToken) withToken(token []byte, sep1, sep2 int) RawToken { - raw.sep1 = sep1 - raw.sep2 = sep1 + 1 + sep2 - raw.token = token - return raw +func (rt *RawToken) decode() (err error) { + if err = internal.Decode(rt.payload(), rt.payloadAddr); err != nil { + return err + } + for _, vd := range rt.payloadValidators { + if err = vd(); err != nil { + return err + } + } + return nil } diff --git a/raw_token_test.go b/raw_token_test.go deleted file mode 100644 index 8693f0b..0000000 --- a/raw_token_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package jwt_test - -import ( - "reflect" - "testing" - - "github.com/gbrlsnchs/jwt/v3" -) - -var ( - testToken = []byte( - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + - "eyJzdHJpbmciOiJmb29iYXIiLCJpbnQiOjEzMzcsImlhdCI6MTUxNjIzOTAyMn0." + - "bVYo9Q0lGouCj1y9zFY17bfxQaRUuM6wtpnIy0m4uD0", - ) - testRaw, _ = jwt.Verify(jwt.NewHS256([]byte("secret")), testToken) -) - -func TestRawTokenDecode(t *testing.T) { - testCases := []struct { - raw jwt.RawToken - wantPayload testPayload - }{ - { - raw: testRaw, - wantPayload: testPayload{ - String: "foobar", - Int: 1337, - Payload: jwt.Payload{ - IssuedAt: 1516239022, - }, - }, - }, - } - for _, tc := range testCases { - t.Run("", func(t *testing.T) { - var payload testPayload - err := tc.raw.Decode(&payload) - if err != nil { - t.Fatal(err) - } - if want, got := tc.wantPayload, payload; !reflect.DeepEqual(got, want) { - t.Errorf("want %#+v, got %#+v", want, got) - } - }) - } -} From 3a088735fa4eadf618f7804b7cedb7a52fd2d117 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 18:49:58 -0300 Subject: [PATCH 24/99] verify: use functional options By using functional options, every validation and decoding needed can be gracefully centralized in a single function. --- verify.go | 61 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/verify.go b/verify.go index 2edac85..5d07bd3 100644 --- a/verify.go +++ b/verify.go @@ -9,29 +9,68 @@ import ( // ErrAlgValidation indicates an incoming JWT's "alg" field mismatches the Validator's. var ErrAlgValidation = internal.NewError(`"alg" field mismatch`) +// VerifyOption is a functional option for verifying. +type VerifyOption func(*RawToken) error + // Verify verifies a token's signature. -func Verify(alg Algorithm, token []byte) (RawToken, error) { - var raw RawToken +func Verify(token []byte, alg Algorithm, opts ...VerifyOption) error { + rt := &RawToken{alg: alg} sep1 := bytes.IndexByte(token, '.') if sep1 < 0 { - return raw, ErrMalformed + return ErrMalformed } cbytes := token[sep1+1:] sep2 := bytes.IndexByte(cbytes, '.') if sep2 < 0 { - return raw, ErrMalformed + return ErrMalformed + } + rt.setToken(token, sep1, sep2) + + var err error + for _, opt := range opts { + if err = opt(rt); err != nil { + return err + } + } + if rt.payloadAddr != nil { + if err = rt.decode(); err != nil { + return err + } + } + + if err = alg.Verify(rt.headerPayload(), rt.sig()); err != nil { + return err } - raw = raw.withToken(token, sep1, sep2) + return nil +} - if err := internal.Decode(raw.header(), &raw.hd); err != nil { - return raw, err +// DecodeHeader decodes into hd and validates it when validate is true. +func DecodeHeader(hd *Header, validate bool) VerifyOption { + return func(rt *RawToken) error { + if err := internal.Decode(rt.header(), hd); err != nil { + return err + } + if validate && rt.alg.Name() != hd.Algorithm { + return internal.Errorf("jwt: unexpected algorithm %q: %w", hd.Algorithm, ErrAlgValidation) + } + return nil } - raw.valid = true +} - if alg.Name() != raw.hd.Algorithm { - return raw, internal.Errorf("jwt: unexpected algorithm %q: %w", raw.hd.Algorithm, ErrAlgValidation) +// DecodePayload decodes into payload and run validators, if any. +func DecodePayload(payload interface{}, validators ...ValidatorFunc) VerifyOption { + return func(rt *RawToken) (err error) { + rt.payloadAddr = payload + rt.payloadValidators = validators + return nil } - return raw, alg.Verify(raw.headerPayload(), raw.sig()) +} + +// ValidateHeader checks whether the algorithm contained +// in the JOSE header is the same used by the algorithm. +func ValidateHeader(rt *RawToken) error { + var hd Header + return DecodeHeader(&hd, true)(rt) } From 7ff3e98a5f7ae86ab012724ca280a52f3eda5a4d Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 18:51:20 -0300 Subject: [PATCH 25/99] sign: update tests --- sign_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/sign_test.go b/sign_test.go index b83279b..957bfc8 100644 --- a/sign_test.go +++ b/sign_test.go @@ -32,6 +32,7 @@ func TestSign(t *testing.T) { payload interface{} verifyAlg jwt.Algorithm + opts []func(*jwt.RawToken) wantHeader jwt.Header wantPayload testPayload @@ -340,7 +341,7 @@ func TestSign(t *testing.T) { t.Run(k, func(t *testing.T) { for _, tc := range v { t.Run(tc.alg.Name(), func(t *testing.T) { - token, err := jwt.Sign(tc.alg, tc.hd, tc.payload) + token, err := jwt.Sign(tc.payload, tc.alg) if want, got := tc.signErr, err; !internal.ErrorIs(got, want) { t.Fatalf("want %v, got %v", want, got) } @@ -348,20 +349,19 @@ func TestSign(t *testing.T) { return } - raw, err := jwt.Verify(tc.verifyAlg, token) + var ( + hd jwt.Header + payload testPayload + ) + err = jwt.Verify(token, tc.verifyAlg, + jwt.DecodeHeader(&hd, false), + jwt.DecodePayload(&payload)) if want, got := tc.verifyErr, err; !internal.ErrorIs(got, want) { t.Fatalf("want %v, got %v", want, got) } - if err != nil { - return - } - if want, got := tc.wantHeader, raw.Header(); !reflect.DeepEqual(got, want) { + if want, got := tc.wantHeader, hd; !reflect.DeepEqual(got, want) { t.Errorf("want %#+v, got %#+v", want, got) } - var payload testPayload - if err = raw.Decode(&payload); err != nil { - t.Fatal(err) - } if want, got := tc.wantPayload, payload; !reflect.DeepEqual(got, want) { t.Errorf("want %#+v, got %#+v", want, got) } From 5d4dc4dedf5b2e5a4eebf985e7e33d19888e244c Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 18:51:42 -0300 Subject: [PATCH 26/99] README: update examples with functional options --- README.md | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b318584..933d497 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,6 @@ import ( ```go now := time.Now() hs256 := jwt.NewHS256([]byte("secret")) -hd := jwt.Header{KeyID: "kid"} pl := jwt.Payload{ Issuer: "gbrlsnchs", Subject: "someone", @@ -58,7 +57,7 @@ pl := jwt.Payload{ IssuedAt: now.Unix(), JWTID: "foobar", } -token, err := jwt.Sign(hs256, hd, pl) +token, err := jwt.Sign(pl, hs256) if err != nil { // Handle error. } @@ -84,7 +83,6 @@ type CustomPayload struct { ```go now := time.Now() hs256 := jwt.NewHS256([]byte("secret")) -hd := jwt.Header{KeyID: "kid"} pl := CustomPayload{ Payload: jwt.Payload{ Issuer: "gbrlsnchs", @@ -98,7 +96,7 @@ pl := CustomPayload{ IsLoggedIn: true, CustomField: "myCustomField", } -token, err := jwt.Sign(hs256, hd, pl) +token, err := jwt.Sign(pl, hs256) if err != nil { // Handle error. } @@ -111,6 +109,16 @@ log.Printf("token = %s", token)
Verifying and validating a JWT

+

Set "cty" and "kid" claims +

+ +```go +token, err := jwt.Sign(pl, hs256, jwt.ContentType("JWT"), jwt.KeyID("0xDEADBABE")) +``` + +

+
+ ```go now := time.Now() hs256 := jwt.NewHS256([]byte("secret")) @@ -118,29 +126,22 @@ token := []byte("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." + "lZ1zDoGNAv3u-OclJtnoQKejE8_viHlMtGlAxE8AE0Q") -raw, err := jwt.Verify(hs256, token) -if err != nil { - // Handle error. -} var ( - hd = raw.Header() - pl CustomPayload + hd jwt.Header + payload CustomPayload ) -if err = raw.Decode(&pl); err != nil { - // Handle error. -} -fmt.Println(hd.Algorithm) -fmt.Println(hd.KeyID) - -iatValidator := jwt.IssuedAtValidator(now) -expValidator := jwt.ExpirationTimeValidator(now, true) -audValidator := jwt.AudienceValidator(jwt.Audience{ +iatValidator := payload.IssuedAtValidator(now) +expValidator := payload.ExpirationTimeValidator(now, true) +audValidator := payload.AudienceValidator(jwt.Audience{ "https://golang.org", "https://jwt.io", "https://google.com", "https://reddit.com", }) -if err := pl.Validate(iatValidator, expValidator, audValidator); err != nil { +err = jwt.Verify(token, hs256, + jwt.DecodeHeader(&hd, true), + jwt.DecodePayload(&payload, iatValidator, expValidator, audValidator)) +if err != nil { switch err { case jwt.ErrIatValidation: // handle "iat" validation error @@ -148,8 +149,13 @@ if err := pl.Validate(iatValidator, expValidator, audValidator); err != nil { // handle "exp" validation error case jwt.ErrAudValidation: // handle "aud" validation error + default: + // handle other errors } } + +fmt.Println(hd.Algorithm) +fmt.Println(hd.KeyID) ```

From 3b65886d6d89e71c25d1fef5c50faf73d6c0e107 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 22:24:59 -0300 Subject: [PATCH 27/99] README: refactor examples --- README.md | 195 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 102 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index 933d497..71c630e 100644 --- a/README.md +++ b/README.md @@ -32,130 +32,139 @@ Full documentation [here](https://godoc.org/github.com/gbrlsnchs/jwt). ### Installing `GO111MODULE=on go get -u github.com/gbrlsnchs/jwt/v3` -### Importing +### Signing ```go import ( - // ... + "time" "github.com/gbrlsnchs/jwt/v3" ) -``` - -### Examples -
Signing a JWT with default claims -

-```go -now := time.Now() -hs256 := jwt.NewHS256([]byte("secret")) -pl := jwt.Payload{ - Issuer: "gbrlsnchs", - Subject: "someone", - Audience: jwt.Audience{"https://golang.org", "https://jwt.io"}, - ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(), - NotBefore: now.Add(30 * time.Minute).Unix(), - IssuedAt: now.Unix(), - JWTID: "foobar", -} -token, err := jwt.Sign(pl, hs256) -if err != nil { - // Handle error. +type CustomPayload struct { + jwt.Payload + Foo string `json:"foo,omitempty"` + Bar int `json:"bar,omitempty"` } -log.Printf("token = %s", token) -``` -

-
+var hs = jwt.NewHS256([]byte("secret")) + +func main() { + now := time.Now() + pl := CustomPayload{ + Payload: jwt.Payload{ + Issuer: "gbrlsnchs", + Subject: "someone", + Audience: jwt.Audience{"https://golang.org", "https://jwt.io"}, + ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(), + NotBefore: now.Add(30 * time.Minute).Unix(), + IssuedAt: now.Unix(), + JWTID: "foobar", + }, + Foo: "foo", + Bar: 1337, + } -
Signing a JWT with custom claims -

+ token, err := jwt.Sign(pl, hs) + if err != nil { + // ... + } -#### First, create a custom type and embed a `Payload` in it + // ... +} +``` + +### Verifying (and decoding) ```go +import "github.com/gbrlsnchs/jwt/v3" + type CustomPayload struct { jwt.Payload - IsLoggedIn bool `json:"isLoggedIn"` - CustomField string `json:"customField,omitempty"` + Foo string `json:"foo,omitempty"` + Bar int `json:"bar,omitempty"` +} + +var hs = jwt.NewHS256([]byte("secret")) + +func main() { + // ... + + var pl CustomPayload + if err := jwt.Verify(token, hs, jwt.DecodePayload(&pl)); err != nil { + // ... + } + + // ... } ``` -#### Now initialize and sign it +### Other examples +

Setting "cty" and "kid" claims +

+ ```go -now := time.Now() -hs256 := jwt.NewHS256([]byte("secret")) -pl := CustomPayload{ - Payload: jwt.Payload{ - Issuer: "gbrlsnchs", - Subject: "someone", - Audience: jwt.Audience{"https://golang.org", "https://jwt.io"}, - ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(), - NotBefore: now.Add(30 * time.Minute).Unix(), - IssuedAt: now.Unix(), - JWTID: "foobar", - }, - IsLoggedIn: true, - CustomField: "myCustomField", -} -token, err := jwt.Sign(pl, hs256) -if err != nil { - // Handle error. +import ( + "time" + + "github.com/gbrlsnchs/jwt/v3" +) + +var hs = jwt.NewHS256([]byte("secret")) + +func main() { + pl := jwt.Payload{ + Subject: "gbrlsnchs", + Issuer: "gsr.dev", + IssuedAt: time.Now().Unix(), + } + + token, err := jwt.Sign(pl, hs, jwt.ContentType("JWT"), jwt.KeyID("my_key")) + if err != nil { + // ... + } + + // ... } -log.Printf("token = %s", token) ```

-
Verifying and validating a JWT -

- -

Set "cty" and "kid" claims +
Validating "alg" before verifying

+#### Without decoding the header ```go -token, err := jwt.Sign(pl, hs256, jwt.ContentType("JWT"), jwt.KeyID("0xDEADBABE")) -``` +import "github.com/gbrlsnchs/jwt/v3" -

-
+var hs = jwt.NewHS256([]byte("secret")) -```go -now := time.Now() -hs256 := jwt.NewHS256([]byte("secret")) -token := []byte("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + - "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ." + - "lZ1zDoGNAv3u-OclJtnoQKejE8_viHlMtGlAxE8AE0Q") - -var ( - hd jwt.Header - payload CustomPayload -) -iatValidator := payload.IssuedAtValidator(now) -expValidator := payload.ExpirationTimeValidator(now, true) -audValidator := payload.AudienceValidator(jwt.Audience{ - "https://golang.org", - "https://jwt.io", - "https://google.com", - "https://reddit.com", -}) -err = jwt.Verify(token, hs256, - jwt.DecodeHeader(&hd, true), - jwt.DecodePayload(&payload, iatValidator, expValidator, audValidator)) -if err != nil { - switch err { - case jwt.ErrIatValidation: - // handle "iat" validation error - case jwt.ErrExpValidation: - // handle "exp" validation error - case jwt.ErrAudValidation: - // handle "aud" validation error - default: - // handle other errors +func main() { + // ... + + if err := jwt.Verify(token, hs, jwt.ValidateHeader); err != nil { + // ... } + + // ... } +``` + +#### Decoding the header +```go +import "github.com/gbrlsnchs/jwt/v3" -fmt.Println(hd.Algorithm) -fmt.Println(hd.KeyID) +var hs = jwt.NewHS256([]byte("secret")) + +func main() { + // ... + + var hd jwt.Header + if err := jwt.Verify(token, hs, jwt.DecodeHeader(&hd, true)); err != nil { + // ... + } + + // ... +} ```

From e54320d745b1945c49afb1038085a29da7107868 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 22:25:22 -0300 Subject: [PATCH 28/99] sign: use default value for payload when nil --- sign.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sign.go b/sign.go index 9d73a97..272625e 100644 --- a/sign.go +++ b/sign.go @@ -22,6 +22,10 @@ func Sign(payload interface{}, alg Algorithm, opts ...SignOption) ([]byte, error if err != nil { return nil, err } + + if payload == nil { + payload = Payload{} + } // Marshal the claims part of the JWT. pb, err := json.Marshal(payload) if err != nil { From 2be6597e4742c8aae1d24e3558a4749e8d32384b Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 22:25:52 -0300 Subject: [PATCH 29/99] verify: add compile-time check for ValidateHeader --- verify.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/verify.go b/verify.go index 5d07bd3..acf9b13 100644 --- a/verify.go +++ b/verify.go @@ -74,3 +74,5 @@ func ValidateHeader(rt *RawToken) error { var hd Header return DecodeHeader(&hd, true)(rt) } + +var _ VerifyOption = ValidateHeader // compile-time test From a442abc0c2daa8b018f780892aef146960b3001f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 22:26:19 -0300 Subject: [PATCH 30/99] Makefile: add rule for benchmarking --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 02115c3..6b9a06e 100644 --- a/Makefile +++ b/Makefile @@ -12,5 +12,8 @@ lint: @! goimports -d . | grep -vF "no errors" @golint -set_exit_status ./... +bench: + @go test -v -run=^$$ -bench=. + test: lint @go test -v ./... From 12b3b1380d3cd74a7b6fb49ae3c9dd60f6d35f97 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 22:26:49 -0300 Subject: [PATCH 31/99] Add some benchmarks --- bench_test.go | 128 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 bench_test.go diff --git a/bench_test.go b/bench_test.go new file mode 100644 index 0000000..80bf43e --- /dev/null +++ b/bench_test.go @@ -0,0 +1,128 @@ +package jwt_test + +import ( + "testing" + "time" + + "github.com/gbrlsnchs/jwt/v3" +) + +var ( + benchHS256 = jwt.NewHS256([]byte("secret")) + benchRecv []byte +) + +func BenchmarkSign(b *testing.B) { + now := time.Now() + var ( + token []byte + err error + pl = jwt.Payload{ + Issuer: "gbrlsnchs", + Subject: "someone", + Audience: jwt.Audience{"https://golang.org", "https://jwt.io"}, + ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(), + NotBefore: now.Add(30 * time.Minute).Unix(), + IssuedAt: now.Unix(), + JWTID: "foobar", + } + ) + b.Run("Default", func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + token, err = jwt.Sign(pl, benchHS256) + if err != nil { + b.Fatal(err) + } + } + }) + b.Run(`With "kid"`, func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + token, err = jwt.Sign(pl, benchHS256, jwt.KeyID("kid")) + if err != nil { + b.Fatal(err) + } + } + }) + b.Run(`With "cty" and "kid"`, func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + token, err = jwt.Sign(pl, benchHS256, jwt.ContentType("cty"), jwt.KeyID("kid")) + if err != nil { + b.Fatal(err) + } + } + }) + + benchRecv = token + +} + +func BenchmarkVerify(b *testing.B) { + var ( + token = []byte( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9." + + "eyJpc3MiOiJnYnJsc25jaHMiLCJzdWIiOiJzb21lb25lIiwiYXVkIjpbImh0dHBzOi8vZ29sYW5nLm9yZyIsImh0dHBzOi8vand0LmlvIl0sImV4cCI6MTU5MzM5MTE4MiwibmJmIjoxNTYyMjg4OTgyLCJpYXQiOjE1NjIyODcxODIsImp0aSI6ImZvb2JhciJ9." + + "bKevp7jmMbH9-Hy5g5OxLgq8tg13z9voH7lZ4m9y484", + ) + err error + ) + b.Run("Default", func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + if err = jwt.Verify(token, benchHS256); err != nil { + b.Fatal(err) + } + } + }) + + b.Run("ValidateHeader", func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + if err = jwt.Verify(token, benchHS256, jwt.ValidateHeader); err != nil { + b.Fatal(err) + } + } + }) + + b.Run("DecodePayload", func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + var pl jwt.Payload + if err = jwt.Verify(token, benchHS256, jwt.DecodePayload(&pl)); err != nil { + b.Fatal(err) + } + } + }) + b.Run("Full decode", func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + var ( + hd jwt.Header + pl jwt.Payload + ) + if err = jwt.Verify(token, benchHS256, + jwt.DecodeHeader(&hd, false), jwt.DecodePayload(&pl)); err != nil { + b.Fatal(err) + } + } + }) + + b.Run("Payload validator", func(b *testing.B) { + b.ReportAllocs() + for n := 0; n < b.N; n++ { + var pl jwt.Payload + audValidator := pl.AudienceValidator(jwt.Audience{ + "https://golang.org", + "https://jwt.io", + "https://google.com", + "https://reddit.com", + }) + if err = jwt.Verify(token, benchHS256, + jwt.DecodePayload(&pl, audValidator)); err != nil { + b.Fatal(err) + } + } + }) +} From 93d584d26ba5f74118c0af64ab3a1aa0a301a426 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 22:43:00 -0300 Subject: [PATCH 32/99] rsa_sha: add function to retrieve signature size --- internal/rsa_signature_size.go | 10 ++++++++++ internal/rsa_signature_size_go1_10.go | 11 +++++++++++ rsa_sha.go | 2 +- 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 internal/rsa_signature_size.go create mode 100644 internal/rsa_signature_size_go1_10.go diff --git a/internal/rsa_signature_size.go b/internal/rsa_signature_size.go new file mode 100644 index 0000000..ed99e7b --- /dev/null +++ b/internal/rsa_signature_size.go @@ -0,0 +1,10 @@ +// +build go1.11 + +package internal + +import "crypto/rsa" + +// RSASignatureSize returns the signature size of an RSA signature. +func RSASignatureSize(pub *rsa.PublicKey) int { + return pub.Size() +} diff --git a/internal/rsa_signature_size_go1_10.go b/internal/rsa_signature_size_go1_10.go new file mode 100644 index 0000000..55a12aa --- /dev/null +++ b/internal/rsa_signature_size_go1_10.go @@ -0,0 +1,11 @@ +// +build !go1.11 + +package internal + +import "crypto/rsa" + +// RSASignatureSize returns the signature size of an RSA signature. +func RSASignatureSize(pub *rsa.PublicKey) int { + // As defined at https://golang.org/src/crypto/rsa/rsa.go?s=1609:1641#L39. + return (pub.N.BitLen() + 7) / 8 +} diff --git a/rsa_sha.go b/rsa_sha.go index eff9205..9940544 100644 --- a/rsa_sha.go +++ b/rsa_sha.go @@ -37,7 +37,7 @@ func newRSASHA(name string, priv *rsa.PrivateKey, pub *rsa.PublicKey, sha crypto priv: priv, pub: pub, sha: sha, - size: pub.Size(), // cache size + size: internal.RSASignatureSize(pub), // cache size pool: newHashPool(sha.New), } if pss { From 8a4ea39f8fbf08c7c393f7c8c79ded7bbbe0e7ca Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:02:07 -0300 Subject: [PATCH 33/99] Makefile: use env variable as command This enables `vgo` or `dep` to be used instead. --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 6b9a06e..6ddcffb 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,5 @@ export GO111MODULE := on +GO_COMMAND ?= go all: export GO111MODULE := off all: @@ -13,7 +14,7 @@ lint: @golint -set_exit_status ./... bench: - @go test -v -run=^$$ -bench=. + @${GO_COMMAND} test -v -run=^$$ -bench=. test: lint - @go test -v ./... + @${GO_COMMAND} test -v ./... From cc46b0dec45f0ad883ab8810e81ca2e560f42066 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:03:08 -0300 Subject: [PATCH 34/99] Makefile: download vgo when needed --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 6ddcffb..5560d3f 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,9 @@ all: export GO111MODULE := off all: go get -u golang.org/x/tools/cmd/goimports go get -u golang.org/x/lint/golint +ifeq (${GO_COMMAND},vgo) + go get -u golang.org/x/vgo +endif fix: @goimports -w *.go From ddc7faa9bcc8292dd0ad3d02ebdf1c0de6a142c9 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:03:31 -0300 Subject: [PATCH 35/99] circleci: use vgo for go1.10 --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index ebce6b5..776b835 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,6 +35,8 @@ jobs: go1_10: &nomod_template <<: *template + environment: + GO_COMMAND: vgo executor: name: golang version: "1.10" From 7f667dbbde8bb065c355664988cf93c8c831d279 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:07:43 -0300 Subject: [PATCH 36/99] circleci: set GO111MODULE to off for go1.10 --- .circleci/config.yml | 1 + Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 776b835..45bc995 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -36,6 +36,7 @@ jobs: go1_10: &nomod_template <<: *template environment: + GO111MODULE: off GO_COMMAND: vgo executor: name: golang diff --git a/Makefile b/Makefile index 5560d3f..48f0e2d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export GO111MODULE := on +export GO111MODULE ?= on GO_COMMAND ?= go all: export GO111MODULE := off From 928c690a275134a61cd527c49c56e054c2f1aff6 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:13:39 -0300 Subject: [PATCH 37/99] Makefile: undefine GO111MODULE for vgo --- Makefile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile b/Makefile index 48f0e2d..320f282 100644 --- a/Makefile +++ b/Makefile @@ -19,5 +19,8 @@ lint: bench: @${GO_COMMAND} test -v -run=^$$ -bench=. +ifeq (${GO_COMMAND},vgo) +test: undefine GO111MODULE +endif test: lint @${GO_COMMAND} test -v ./... From e808276e5a0b41c3517b2d670bf594f2e7aae723 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:22:20 -0300 Subject: [PATCH 38/99] Makefile: fix conditional undefine --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 320f282..14d329a 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ bench: @${GO_COMMAND} test -v -run=^$$ -bench=. ifeq (${GO_COMMAND},vgo) -test: undefine GO111MODULE +undefine GO111MODULE endif test: lint @${GO_COMMAND} test -v ./... From 6f2c6c8ab398fb00dd34c4d56e993afd4f7ce16f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:37:06 -0300 Subject: [PATCH 39/99] Makefile: remove unsetting GO111MODULE --- Makefile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 14d329a..5560d3f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export GO111MODULE ?= on +export GO111MODULE := on GO_COMMAND ?= go all: export GO111MODULE := off @@ -19,8 +19,5 @@ lint: bench: @${GO_COMMAND} test -v -run=^$$ -bench=. -ifeq (${GO_COMMAND},vgo) -undefine GO111MODULE -endif test: lint @${GO_COMMAND} test -v ./... From 3c56ff589f1986abf76d6542ce594d02fe5f59bf Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:38:23 -0300 Subject: [PATCH 40/99] internal: remove error wrapper Unfortunately, the error wrapper is not compatible with Go versions prior to 1.11, and since it's not vital to this package, it has been removed. --- ed25519_go1_12.go | 8 +++--- go.mod | 7 +++--- go.sum | 2 -- internal/decode_test.go | 53 ---------------------------------------- internal/error_go1_12.go | 17 ------------- raw_token.go | 8 ++++-- sign_test.go | 5 ++-- verify.go | 5 ++-- 8 files changed, 19 insertions(+), 86 deletions(-) delete mode 100644 internal/decode_test.go delete mode 100644 internal/error_go1_12.go diff --git a/ed25519_go1_12.go b/ed25519_go1_12.go index b28007d..557a41b 100644 --- a/ed25519_go1_12.go +++ b/ed25519_go1_12.go @@ -3,17 +3,19 @@ package jwt import ( + "errors" + "github.com/gbrlsnchs/jwt/v3/internal" "golang.org/x/crypto/ed25519" ) var ( // ErrEd25519PrivKey is the error for trying to sign a JWT with a nil private key. - ErrEd25519PrivKey = internal.NewError("jwt: Ed25519 private key is nil") + ErrEd25519PrivKey = errors.New("jwt: Ed25519 private key is nil") // ErrEd25519PubKey is the error for trying to verify a JWT with a nil public key. - ErrEd25519PubKey = internal.NewError("jwt: Ed25519 public key is nil") + ErrEd25519PubKey = errors.New("jwt: Ed25519 public key is nil") // ErrEd25519Verification is the error for when verification with edDSA fails. - ErrEd25519Verification = internal.NewError("jwt: Ed25519 verification failed") + ErrEd25519Verification = errors.New("jwt: Ed25519 verification failed") _ Algorithm = new(edDSA) ) diff --git a/go.mod b/go.mod index c586c25..dcd1127 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,5 @@ module github.com/gbrlsnchs/jwt/v3 -require ( - golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 - golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 -) +require golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 + +go 1.9.7 diff --git a/go.sum b/go.sum index 3811b06..ab4e508 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,2 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 h1:PPwnA7z1Pjf7XYaBP9GL1VAMZmcIWyFz7QCMSIIa3Bg= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/decode_test.go b/internal/decode_test.go deleted file mode 100644 index 1db91ef..0000000 --- a/internal/decode_test.go +++ /dev/null @@ -1,53 +0,0 @@ -package internal_test - -import ( - "encoding/base64" - "testing" - - "github.com/gbrlsnchs/jwt/v3/internal" -) - -var ( - stdEnc = base64.StdEncoding - rawURLEnc = base64.RawURLEncoding -) - -type decodeTest struct { - X string `json:"x,omitempty"` -} - -func TestDecode(t *testing.T) { - testCases := []struct { - encoding *base64.Encoding - json string - expected string - errors bool - }{ - {rawURLEnc, "{}", "", false}, - {rawURLEnc, `{"x":"test"}`, "test", false}, - {stdEnc, "{}", "", true}, - {stdEnc, `{"x":"test"}`, "test", false}, - {nil, "{}", "", true}, - {nil, `{"x":"test"}`, "", true}, - } - for _, tc := range testCases { - t.Run(tc.json, func(t *testing.T) { - b64 := tc.json - if tc.encoding != nil { - b64 = tc.encoding.EncodeToString([]byte(tc.json)) - } - t.Logf("b64: %s", b64) - var ( - dt decodeTest - err = internal.Decode([]byte(b64), &dt) - b64err = new(base64.CorruptInputError) - ) - if want, got := tc.errors, internal.ErrorAs(err, b64err); want != got { - t.Fatalf("want %t, got %t: %v", want, got, err) - } - if want, got := tc.expected, dt.X; want != got { - t.Errorf("want %q, got %q", want, got) - } - }) - } -} diff --git a/internal/error_go1_12.go b/internal/error_go1_12.go deleted file mode 100644 index 0d25232..0000000 --- a/internal/error_go1_12.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build !go1.13 - -package internal - -import "golang.org/x/xerrors" - -// Errorf is a wrapper for xerrors.Errorf. -func Errorf(format string, a ...interface{}) error { return xerrors.Errorf(format, a...) } - -// ErrorAs is a wrapper for xerrors.As. -func ErrorAs(err error, target interface{}) bool { return xerrors.As(err, target) } - -// ErrorIs is a wrapper for xerrors.Is. -func ErrorIs(err, target error) bool { return xerrors.Is(err, target) } - -// NewError is a wrapper for xerrors.New. -func NewError(text string) error { return xerrors.New(text) } diff --git a/raw_token.go b/raw_token.go index 46e6362..514d979 100644 --- a/raw_token.go +++ b/raw_token.go @@ -1,9 +1,13 @@ package jwt -import "github.com/gbrlsnchs/jwt/v3/internal" +import ( + "errors" + + "github.com/gbrlsnchs/jwt/v3/internal" +) // ErrMalformed indicates a token doesn't have a valid format, as per the RFC 7519. -var ErrMalformed = internal.NewError("jwt: malformed token") +var ErrMalformed = errors.New("jwt: malformed token") // RawToken is a representation of a parsed JWT string. type RawToken struct { diff --git a/sign_test.go b/sign_test.go index 957bfc8..83b0a8b 100644 --- a/sign_test.go +++ b/sign_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/gbrlsnchs/jwt/v3" - "github.com/gbrlsnchs/jwt/v3/internal" ) type testPayload struct { @@ -342,7 +341,7 @@ func TestSign(t *testing.T) { for _, tc := range v { t.Run(tc.alg.Name(), func(t *testing.T) { token, err := jwt.Sign(tc.payload, tc.alg) - if want, got := tc.signErr, err; !internal.ErrorIs(got, want) { + if want, got := tc.signErr, err; got != want { t.Fatalf("want %v, got %v", want, got) } if err != nil { @@ -356,7 +355,7 @@ func TestSign(t *testing.T) { err = jwt.Verify(token, tc.verifyAlg, jwt.DecodeHeader(&hd, false), jwt.DecodePayload(&payload)) - if want, got := tc.verifyErr, err; !internal.ErrorIs(got, want) { + if want, got := tc.verifyErr, err; got != want { t.Fatalf("want %v, got %v", want, got) } if want, got := tc.wantHeader, hd; !reflect.DeepEqual(got, want) { diff --git a/verify.go b/verify.go index acf9b13..4a2b167 100644 --- a/verify.go +++ b/verify.go @@ -2,12 +2,13 @@ package jwt import ( "bytes" + "errors" "github.com/gbrlsnchs/jwt/v3/internal" ) // ErrAlgValidation indicates an incoming JWT's "alg" field mismatches the Validator's. -var ErrAlgValidation = internal.NewError(`"alg" field mismatch`) +var ErrAlgValidation = errors.New(`jwt: "alg" field mismatch`) // VerifyOption is a functional option for verifying. type VerifyOption func(*RawToken) error @@ -53,7 +54,7 @@ func DecodeHeader(hd *Header, validate bool) VerifyOption { return err } if validate && rt.alg.Name() != hd.Algorithm { - return internal.Errorf("jwt: unexpected algorithm %q: %w", hd.Algorithm, ErrAlgValidation) + return ErrAlgValidation } return nil } From c38f6b3035a7110816f38e82f99ea48874b24a54 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:40:58 -0300 Subject: [PATCH 41/99] go.mod: change Go version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index dcd1127..2faf5e1 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/gbrlsnchs/jwt/v3 require golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 -go 1.9.7 +go 1.11 From f0093f864e497ff9e0b2705e858eba7a8e9cc580 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:48:19 -0300 Subject: [PATCH 42/99] Revert "Makefile: remove unsetting GO111MODULE" This reverts commit 6f2c6c8ab398fb00dd34c4d56e993afd4f7ce16f. --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5560d3f..14d329a 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export GO111MODULE := on +export GO111MODULE ?= on GO_COMMAND ?= go all: export GO111MODULE := off @@ -19,5 +19,8 @@ lint: bench: @${GO_COMMAND} test -v -run=^$$ -bench=. +ifeq (${GO_COMMAND},vgo) +undefine GO111MODULE +endif test: lint @${GO_COMMAND} test -v ./... From bf66d3c98e2cb62848056e4cf2371046c6115903 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:51:12 -0300 Subject: [PATCH 43/99] Makefile: log steps --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 14d329a..0ee803d 100644 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ fix: @goimports -w *.go lint: + @echo "Linting..." @! goimports -d . | grep -vF "no errors" @golint -set_exit_status ./... @@ -23,4 +24,5 @@ ifeq (${GO_COMMAND},vgo) undefine GO111MODULE endif test: lint + @echo "Testing..." @${GO_COMMAND} test -v ./... From c827185ccecf544c464f0cc9077eedabb1a3912f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:55:06 -0300 Subject: [PATCH 44/99] go.mod: change minimal version to 1.10 --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2faf5e1..f93a627 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/gbrlsnchs/jwt/v3 require golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 -go 1.11 +go 1.10 From 7395ca1d7232e4469f4ccc71602017affd89ff1e Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Thu, 4 Jul 2019 23:57:11 -0300 Subject: [PATCH 45/99] rsa_sha: return cached size --- rsa_sha.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rsa_sha.go b/rsa_sha.go index 9940544..d20fae6 100644 --- a/rsa_sha.go +++ b/rsa_sha.go @@ -101,10 +101,7 @@ func (rs *rsaSHA) Sign(headerPayload []byte) ([]byte, error) { // Size returns the signature's byte size. func (rs *rsaSHA) Size() int { - if rs.pub == nil { - return 0 - } - return rs.pub.Size() + return rs.size } // Verify verifies a signature based on headerPayload using either RSA-SHA or RSA-PSS-SHA. From f57cde8ef966adc04436ecc4901b60f55a1c57d7 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 00:02:49 -0300 Subject: [PATCH 46/99] Makefile: use go instead of vgo for version 1.10 --- Makefile | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 0ee803d..aea42b0 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,19 @@ export GO111MODULE ?= on -GO_COMMAND ?= go all: export GO111MODULE := off all: go get -u golang.org/x/tools/cmd/goimports go get -u golang.org/x/lint/golint -ifeq (${GO_COMMAND},vgo) - go get -u golang.org/x/vgo -endif fix: @goimports -w *.go lint: - @echo "Linting..." @! goimports -d . | grep -vF "no errors" @golint -set_exit_status ./... bench: - @${GO_COMMAND} test -v -run=^$$ -bench=. + go test -v -run=^$$ -bench=. -ifeq (${GO_COMMAND},vgo) -undefine GO111MODULE -endif test: lint - @echo "Testing..." - @${GO_COMMAND} test -v ./... + go test -v ./... From 91842e0bda4c29ea0e1c03e92c9e0f45153e71af Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 00:09:33 -0300 Subject: [PATCH 47/99] circleci: get dependencies for go1.9 and go1.10 --- .circleci/config.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 45bc995..3be291c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,6 +9,12 @@ executors: docker: - image: circleci/golang:<< parameters.version >> +commands: + test: + steps: + - run: make + - run: make test + workflows: test: jobs: @@ -24,8 +30,7 @@ jobs: version: "1.12" steps: - checkout - - run: make - - run: make test + - test go1_11: <<: *template @@ -42,6 +47,10 @@ jobs: name: golang version: "1.10" working_directory: /go/src/github.com/gbrlsnchs/jwt + steps: + - checkout + - run: go get -u golang.org/x/crypto + - test go1_9: <<: *nomod_template From 33e6c1b4c901bbd079b8f92486668d4aaa117170 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 00:11:08 -0300 Subject: [PATCH 48/99] circleci: fix "go get" step --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3be291c..f1f92a9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,7 +49,7 @@ jobs: working_directory: /go/src/github.com/gbrlsnchs/jwt steps: - checkout - - run: go get -u golang.org/x/crypto + - run: go get -u golang.org/x/crypto/ed25519 - test go1_9: From 283208ea9539a38da09ddd7534c7d2d6da522061 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 00:13:39 -0300 Subject: [PATCH 49/99] Revert "internal: remove error wrapper" This reverts commit 3c56ff589f1986abf76d6542ce594d02fe5f59bf. --- ed25519_go1_12.go | 8 +++--- go.mod | 5 +++- go.sum | 2 ++ internal/decode_test.go | 53 ++++++++++++++++++++++++++++++++++++++++ internal/error_go1_12.go | 17 +++++++++++++ raw_token.go | 8 ++---- sign_test.go | 5 ++-- verify.go | 5 ++-- 8 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 internal/decode_test.go create mode 100644 internal/error_go1_12.go diff --git a/ed25519_go1_12.go b/ed25519_go1_12.go index 557a41b..b28007d 100644 --- a/ed25519_go1_12.go +++ b/ed25519_go1_12.go @@ -3,19 +3,17 @@ package jwt import ( - "errors" - "github.com/gbrlsnchs/jwt/v3/internal" "golang.org/x/crypto/ed25519" ) var ( // ErrEd25519PrivKey is the error for trying to sign a JWT with a nil private key. - ErrEd25519PrivKey = errors.New("jwt: Ed25519 private key is nil") + ErrEd25519PrivKey = internal.NewError("jwt: Ed25519 private key is nil") // ErrEd25519PubKey is the error for trying to verify a JWT with a nil public key. - ErrEd25519PubKey = errors.New("jwt: Ed25519 public key is nil") + ErrEd25519PubKey = internal.NewError("jwt: Ed25519 public key is nil") // ErrEd25519Verification is the error for when verification with edDSA fails. - ErrEd25519Verification = errors.New("jwt: Ed25519 verification failed") + ErrEd25519Verification = internal.NewError("jwt: Ed25519 verification failed") _ Algorithm = new(edDSA) ) diff --git a/go.mod b/go.mod index f93a627..d34e936 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,8 @@ module github.com/gbrlsnchs/jwt/v3 -require golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 +require ( + golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 + golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 +) go 1.10 diff --git a/go.sum b/go.sum index ab4e508..3811b06 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 h1:PPwnA7z1Pjf7XYaBP9GL1VAMZmcIWyFz7QCMSIIa3Bg= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/decode_test.go b/internal/decode_test.go new file mode 100644 index 0000000..1db91ef --- /dev/null +++ b/internal/decode_test.go @@ -0,0 +1,53 @@ +package internal_test + +import ( + "encoding/base64" + "testing" + + "github.com/gbrlsnchs/jwt/v3/internal" +) + +var ( + stdEnc = base64.StdEncoding + rawURLEnc = base64.RawURLEncoding +) + +type decodeTest struct { + X string `json:"x,omitempty"` +} + +func TestDecode(t *testing.T) { + testCases := []struct { + encoding *base64.Encoding + json string + expected string + errors bool + }{ + {rawURLEnc, "{}", "", false}, + {rawURLEnc, `{"x":"test"}`, "test", false}, + {stdEnc, "{}", "", true}, + {stdEnc, `{"x":"test"}`, "test", false}, + {nil, "{}", "", true}, + {nil, `{"x":"test"}`, "", true}, + } + for _, tc := range testCases { + t.Run(tc.json, func(t *testing.T) { + b64 := tc.json + if tc.encoding != nil { + b64 = tc.encoding.EncodeToString([]byte(tc.json)) + } + t.Logf("b64: %s", b64) + var ( + dt decodeTest + err = internal.Decode([]byte(b64), &dt) + b64err = new(base64.CorruptInputError) + ) + if want, got := tc.errors, internal.ErrorAs(err, b64err); want != got { + t.Fatalf("want %t, got %t: %v", want, got, err) + } + if want, got := tc.expected, dt.X; want != got { + t.Errorf("want %q, got %q", want, got) + } + }) + } +} diff --git a/internal/error_go1_12.go b/internal/error_go1_12.go new file mode 100644 index 0000000..0d25232 --- /dev/null +++ b/internal/error_go1_12.go @@ -0,0 +1,17 @@ +// +build !go1.13 + +package internal + +import "golang.org/x/xerrors" + +// Errorf is a wrapper for xerrors.Errorf. +func Errorf(format string, a ...interface{}) error { return xerrors.Errorf(format, a...) } + +// ErrorAs is a wrapper for xerrors.As. +func ErrorAs(err error, target interface{}) bool { return xerrors.As(err, target) } + +// ErrorIs is a wrapper for xerrors.Is. +func ErrorIs(err, target error) bool { return xerrors.Is(err, target) } + +// NewError is a wrapper for xerrors.New. +func NewError(text string) error { return xerrors.New(text) } diff --git a/raw_token.go b/raw_token.go index 514d979..46e6362 100644 --- a/raw_token.go +++ b/raw_token.go @@ -1,13 +1,9 @@ package jwt -import ( - "errors" - - "github.com/gbrlsnchs/jwt/v3/internal" -) +import "github.com/gbrlsnchs/jwt/v3/internal" // ErrMalformed indicates a token doesn't have a valid format, as per the RFC 7519. -var ErrMalformed = errors.New("jwt: malformed token") +var ErrMalformed = internal.NewError("jwt: malformed token") // RawToken is a representation of a parsed JWT string. type RawToken struct { diff --git a/sign_test.go b/sign_test.go index 83b0a8b..957bfc8 100644 --- a/sign_test.go +++ b/sign_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/gbrlsnchs/jwt/v3" + "github.com/gbrlsnchs/jwt/v3/internal" ) type testPayload struct { @@ -341,7 +342,7 @@ func TestSign(t *testing.T) { for _, tc := range v { t.Run(tc.alg.Name(), func(t *testing.T) { token, err := jwt.Sign(tc.payload, tc.alg) - if want, got := tc.signErr, err; got != want { + if want, got := tc.signErr, err; !internal.ErrorIs(got, want) { t.Fatalf("want %v, got %v", want, got) } if err != nil { @@ -355,7 +356,7 @@ func TestSign(t *testing.T) { err = jwt.Verify(token, tc.verifyAlg, jwt.DecodeHeader(&hd, false), jwt.DecodePayload(&payload)) - if want, got := tc.verifyErr, err; got != want { + if want, got := tc.verifyErr, err; !internal.ErrorIs(got, want) { t.Fatalf("want %v, got %v", want, got) } if want, got := tc.wantHeader, hd; !reflect.DeepEqual(got, want) { diff --git a/verify.go b/verify.go index 4a2b167..acf9b13 100644 --- a/verify.go +++ b/verify.go @@ -2,13 +2,12 @@ package jwt import ( "bytes" - "errors" "github.com/gbrlsnchs/jwt/v3/internal" ) // ErrAlgValidation indicates an incoming JWT's "alg" field mismatches the Validator's. -var ErrAlgValidation = errors.New(`jwt: "alg" field mismatch`) +var ErrAlgValidation = internal.NewError(`"alg" field mismatch`) // VerifyOption is a functional option for verifying. type VerifyOption func(*RawToken) error @@ -54,7 +53,7 @@ func DecodeHeader(hd *Header, validate bool) VerifyOption { return err } if validate && rt.alg.Name() != hd.Algorithm { - return ErrAlgValidation + return internal.Errorf("jwt: unexpected algorithm %q: %w", hd.Algorithm, ErrAlgValidation) } return nil } From a3f749b320116cb2f6605b00bc4b2018f5faa9bf Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 00:14:07 -0300 Subject: [PATCH 50/99] Makefile: silence commands --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index aea42b0..f429d0c 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ lint: @golint -set_exit_status ./... bench: - go test -v -run=^$$ -bench=. + @go test -v -run=^$$ -bench=. test: lint - go test -v ./... + @go test -v ./... From ce34614c3b7e5123394e2d9a1508df5ff8eeff4f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 00:15:09 -0300 Subject: [PATCH 51/99] circleci: get "xerrors" dependency --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index f1f92a9..83d34b7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -50,6 +50,7 @@ jobs: steps: - checkout - run: go get -u golang.org/x/crypto/ed25519 + - run: go get -u golang.org/x/xerrors - test go1_9: From d439cfe018a8a325bc1f61f5152d2a717b51fbd2 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 00:29:57 -0300 Subject: [PATCH 52/99] README: add new install instructions --- README.md | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 71c630e..53403f4 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,48 @@ In other words, API changes all the time in `master`. It's a place for public ex Full documentation [here](https://godoc.org/github.com/gbrlsnchs/jwt). ### Installing -`GO111MODULE=on go get -u github.com/gbrlsnchs/jwt/v3` +
Go 1.12 onward +

+ +```sh +$ go get -u github.com/gbrlsnchs/jwt/v3 +``` + +

+
+ +
Go 1.11 +

+ +```sh +$ GO111MODULE=on go get -u github.com/gbrlsnchs/jwt/v3 +``` + +

+
+ +
Go 1.10 with vgo +

+ +```sh +$ vgo get -u github.com/gbrlsnchs/jwt/v3 +``` + +

+
+ +
Go 1.10.3 (without vgo) and Go 1.9.7 +

+ +```sh +$ go get -u github.com/gbrlsnchs/jwt/v3 +``` + +#### Important +Your project must be inside the `GOPATH`. + +

+
### Signing ```go From d6d32835f4b836bd1b0d8c43bde4c43372b18614 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 00:54:59 -0300 Subject: [PATCH 53/99] README: add compatibility badges This resolves #26 by supporting the following versions: * Go 1.11 onward by using modules * Go 1.10 by using `vgo` * Go 1.9.7+, Go 1.10.3+ and Go 1.11 when modules are not used somehow --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 53403f4..72de234 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,11 @@ [![GoDoc](https://godoc.org/github.com/gbrlsnchs/jwt?status.svg)](https://godoc.org/github.com/gbrlsnchs/jwt) [![Join the chat at https://gitter.im/gbrlsnchs/jwt](https://badges.gitter.im/gbrlsnchs/jwt.svg)](https://gitter.im/gbrlsnchs/jwt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +## Compatibility +[![Version Compatibility](https://img.shields.io/badge/go%20modules-go1.11+-5272b4.svg)](https://github.com/gbrlsnchs/jwt#installing) +[![Version Compatibility](https://img.shields.io/badge/vgo-go1.10-5272b4.svg)](https://github.com/gbrlsnchs/jwt#installing) +[![go get](https://img.shields.io/badge/go%20get-go1.9.7+,%20go1.10.3+%20and%20go1.11-5272b4.svg)](https://github.com/gbrlsnchs/jwt#installing) + ## About This package is a JWT signer, verifier and validator for [Go](https://golang.org) (or Golang). @@ -60,7 +65,7 @@ $ vgo get -u github.com/gbrlsnchs/jwt/v3

-
Go 1.10.3 (without vgo) and Go 1.9.7 +
Go 1.9.7+, Go 1.10.3+ (without vgo) and Go 1.11 (when GO111MODULE=off)

```sh From f563b84250f595b3b1497ab58da2b423116cabb7 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 01:18:43 -0300 Subject: [PATCH 54/99] time: fix unmarshal and add tests --- internal/epoch.go | 6 +++++ time.go | 15 ++++++----- time_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 internal/epoch.go create mode 100644 time_test.go diff --git a/internal/epoch.go b/internal/epoch.go new file mode 100644 index 0000000..494c09e --- /dev/null +++ b/internal/epoch.go @@ -0,0 +1,6 @@ +package internal + +import "time" + +// Epoch is 01/01/1970. +var Epoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) diff --git a/time.go b/time.go index e6bd48b..81bc1f7 100644 --- a/time.go +++ b/time.go @@ -3,9 +3,9 @@ package jwt import ( "encoding/json" "time" -) -var epoch = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) + "github.com/gbrlsnchs/jwt/v3/internal" +) // Time is the allowed format for time, as per the RFC 7519. type Time struct { @@ -14,7 +14,7 @@ type Time struct { // MarshalJSON implements a marshaling function for time-related claims. func (t Time) MarshalJSON() ([]byte, error) { - if t.Before(epoch) { + if t.Before(internal.Epoch) { return json.Marshal(0) } return json.Marshal(t.Unix()) @@ -22,12 +22,13 @@ func (t Time) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements an unmarshaling function for time-related claims. func (t *Time) UnmarshalJSON(b []byte) error { - var tt time.Time - if err := json.Unmarshal(b, &tt); err != nil { + var unix int64 + if err := json.Unmarshal(b, &unix); err != nil { return err } - if tt.Before(epoch) { - tt = epoch + tt := time.Unix(unix, 0) + if tt.Before(internal.Epoch) { + tt = internal.Epoch } t.Time = tt return nil diff --git a/time_test.go b/time_test.go new file mode 100644 index 0000000..f4ea80e --- /dev/null +++ b/time_test.go @@ -0,0 +1,65 @@ +package jwt_test + +import ( + "encoding/json" + "testing" + "time" + + "github.com/gbrlsnchs/jwt/v3" + "github.com/gbrlsnchs/jwt/v3/internal" +) + +func TestTimeMarshalJSON(t *testing.T) { + now := time.Now() + testCases := []struct { + tt jwt.Time + want int64 + }{ + {jwt.Time{}, 0}, + {jwt.Time{now}, now.Unix()}, + {jwt.Time{now.Add(24 * time.Hour)}, now.Add(24 * time.Hour).Unix()}, + } + for _, tc := range testCases { + t.Run("", func(t *testing.T) { + b, err := tc.tt.MarshalJSON() + if err != nil { + t.Fatal(err) + } + var n int64 + if err = json.Unmarshal(b, &n); err != nil { + t.Fatal(err) + } + if want, got := tc.want, n; got != want { + t.Errorf("want %d, got %d", want, got) + } + }) + } +} + +func TestTimeUnmarshalJSON(t *testing.T) { + now := time.Now() + testCases := []struct { + n int64 + want jwt.Time + }{ + {now.Unix(), jwt.Time{now}}, + {internal.Epoch.Unix() - 1337, jwt.Time{internal.Epoch}}, + {internal.Epoch.Unix(), jwt.Time{internal.Epoch}}, + {internal.Epoch.Unix() + 1337, jwt.Time{internal.Epoch.Add(1337 * time.Second)}}, + } + for _, tc := range testCases { + t.Run("", func(t *testing.T) { + b, err := json.Marshal(tc.n) + if err != nil { + t.Fatal(err) + } + var tt jwt.Time + if err = tt.UnmarshalJSON(b); err != nil { + t.Fatal(err) + } + if want, got := tc.want, tt; got.Unix() != want.Unix() { + t.Errorf("want %d, got %d", want.Unix(), got.Unix()) + } + }) + } +} From 66763c28ada2b1adf534c4218637cb06e69527be Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 13:56:13 -0300 Subject: [PATCH 55/99] time: preserve zeroed time when unmarshaling zero --- time.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/time.go b/time.go index 81bc1f7..cf9951c 100644 --- a/time.go +++ b/time.go @@ -26,6 +26,9 @@ func (t *Time) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(b, &unix); err != nil { return err } + if unix == 0 { + return nil + } tt := time.Unix(unix, 0) if tt.Before(internal.Epoch) { tt = internal.Epoch From 21df4635600f378a64e9c432f04b5a32ddd3e63a Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 13:56:52 -0300 Subject: [PATCH 56/99] time: add function to resolve NumericDate from RFC --- time.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/time.go b/time.go index cf9951c..f6df66d 100644 --- a/time.go +++ b/time.go @@ -12,9 +12,22 @@ type Time struct { time.Time } +// NumericDate is a resolved Unix time. +func NumericDate(tt time.Time) Time { + if tt.Before(internal.Epoch) { + tt = internal.Epoch + } + return Time{time.Unix(tt.Unix(), 0)} +} + +// IsZero checks whether no seconds have elapsed since epoch. +func (t Time) IsZero() bool { + return t.Before(internal.Epoch) || t.Equal(internal.Epoch) +} + // MarshalJSON implements a marshaling function for time-related claims. func (t Time) MarshalJSON() ([]byte, error) { - if t.Before(internal.Epoch) { + if t.IsZero() { return json.Marshal(0) } return json.Marshal(t.Unix()) From 85007ae9f641ea52820d819dd0fba7c01bec9391 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 13:57:47 -0300 Subject: [PATCH 57/99] payload: use Time type instead of int64 --- payload.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/payload.go b/payload.go index 4874bdb..953fb9f 100644 --- a/payload.go +++ b/payload.go @@ -5,8 +5,8 @@ type Payload struct { Issuer string `json:"iss,omitempty"` Subject string `json:"sub,omitempty"` Audience Audience `json:"aud,omitempty"` - ExpirationTime int64 `json:"exp,omitempty"` - NotBefore int64 `json:"nbf,omitempty"` - IssuedAt int64 `json:"iat,omitempty"` + ExpirationTime Time `json:"exp,omitempty"` + NotBefore Time `json:"nbf,omitempty"` + IssuedAt Time `json:"iat,omitempty"` JWTID string `json:"jti,omitempty"` } From d3c890fbc22991f25c6eb1e91e8eb6bc76fa7dc3 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 13:58:12 -0300 Subject: [PATCH 58/99] validators: use Time type --- validators.go | 9 +++--- validators_test.go | 68 +++++++++++++++++++++++----------------------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/validators.go b/validators.go index 94fcb43..0889084 100644 --- a/validators.go +++ b/validators.go @@ -44,11 +44,10 @@ func (pl *Payload) AudienceValidator(aud Audience) ValidatorFunc { // ExpirationTimeValidator validates the "exp" claim. func (pl *Payload) ExpirationTimeValidator(now time.Time, validateZero bool) ValidatorFunc { return func() error { - expint := pl.ExpirationTime - if !validateZero && expint == 0 { + if !validateZero && pl.ExpirationTime.IsZero() { return nil } - if exp := time.Unix(expint, 0); now.After(exp) { + if now.After(pl.ExpirationTime.Time) { return ErrExpValidation } return nil @@ -58,7 +57,7 @@ func (pl *Payload) ExpirationTimeValidator(now time.Time, validateZero bool) Val // IssuedAtValidator validates the "iat" claim. func (pl *Payload) IssuedAtValidator(now time.Time) ValidatorFunc { return func() error { - if iat := time.Unix(pl.IssuedAt, 0); now.Before(iat) { + if now.Before(pl.IssuedAt.Time) { return ErrIatValidation } return nil @@ -88,7 +87,7 @@ func (pl *Payload) JWTIDValidator(jti string) ValidatorFunc { // NotBeforeValidator validates the "nbf" claim. func (pl *Payload) NotBeforeValidator(now time.Time) ValidatorFunc { return func() error { - if nbf := time.Unix(pl.NotBefore, 0); now.Before(nbf) { + if now.Before(pl.NotBefore.Time) { return ErrNbfValidation } return nil diff --git a/validators_test.go b/validators_test.go index 33b1890..cad874e 100644 --- a/validators_test.go +++ b/validators_test.go @@ -4,51 +4,51 @@ import ( "testing" "time" - . "github.com/gbrlsnchs/jwt/v3" + "github.com/gbrlsnchs/jwt/v3" ) func TestValidators(t *testing.T) { now := time.Now() - iat := now.Unix() - exp := now.Add(24 * time.Hour).Unix() - nbf := now.Add(15 * time.Second).Unix() + iat := jwt.Time{now} + exp := jwt.Time{now.Add(24 * time.Hour)} + nbf := jwt.Time{now.Add(15 * time.Second)} jti := "jti" - aud := Audience{"aud", "aud1", "aud2", "aud3"} + aud := jwt.Audience{"aud", "aud1", "aud2", "aud3"} sub := "sub" iss := "iss" testCases := []struct { claim string - vl ValidatorFunc + vl jwt.ValidatorFunc err error }{ - {"iss", (&Payload{Issuer: iss}).IssuerValidator("iss"), nil}, - {"iss", (&Payload{Issuer: iss}).IssuerValidator("not_iss"), ErrIssValidation}, - {"sub", (&Payload{Subject: sub}).SubjectValidator("sub"), nil}, - {"sub", (&Payload{Subject: sub}).SubjectValidator("not_sub"), ErrSubValidation}, - {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"aud"}), nil}, - {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"foo", "aud1"}), nil}, - {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"bar", "aud2"}), nil}, - {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"baz", "aud3"}), nil}, - {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"qux", "aud4"}), ErrAudValidation}, - {"aud", (&Payload{Audience: aud}).AudienceValidator(Audience{"not_aud"}), ErrAudValidation}, - {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(now, true), nil}, - {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(now, false), nil}, - {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), true), nil}, - {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), false), nil}, - {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), true), ErrExpValidation}, - {"exp", (&Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), false), ErrExpValidation}, - {"exp", (&Payload{}).ExpirationTimeValidator(time.Now(), false), nil}, - {"exp", (&Payload{}).ExpirationTimeValidator(time.Now(), true), ErrExpValidation}, - {"nbf", (&Payload{NotBefore: nbf}).NotBeforeValidator(now), ErrNbfValidation}, - {"nbf", (&Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()+int64(15*time.Second), 0)), nil}, - {"nbf", (&Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()-int64(15*time.Second), 0)), ErrNbfValidation}, - {"nbf", (&Payload{}).NotBeforeValidator(time.Now()), nil}, - {"iat", (&Payload{IssuedAt: iat}).IssuedAtValidator(now), nil}, - {"iat", (&Payload{IssuedAt: iat}).IssuedAtValidator(time.Unix(now.Unix()+1, 0)), nil}, - {"iat", (&Payload{IssuedAt: iat}).IssuedAtValidator(time.Unix(now.Unix()-1, 0)), ErrIatValidation}, - {"iat", (&Payload{}).IssuedAtValidator(time.Now()), nil}, - {"jti", (&Payload{JWTID: jti}).JWTIDValidator("jti"), nil}, - {"jti", (&Payload{JWTID: jti}).JWTIDValidator("not_jti"), ErrJtiValidation}, + {"iss", (&jwt.Payload{Issuer: iss}).IssuerValidator("iss"), nil}, + {"iss", (&jwt.Payload{Issuer: iss}).IssuerValidator("not_iss"), jwt.ErrIssValidation}, + {"sub", (&jwt.Payload{Subject: sub}).SubjectValidator("sub"), nil}, + {"sub", (&jwt.Payload{Subject: sub}).SubjectValidator("not_sub"), jwt.ErrSubValidation}, + {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"aud"}), nil}, + {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"foo", "aud1"}), nil}, + {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"bar", "aud2"}), nil}, + {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"baz", "aud3"}), nil}, + {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"qux", "aud4"}), jwt.ErrAudValidation}, + {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"not_aud"}), jwt.ErrAudValidation}, + {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(now, true), nil}, + {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(now, false), nil}, + {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), true), nil}, + {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), false), nil}, + {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), true), jwt.ErrExpValidation}, + {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), false), jwt.ErrExpValidation}, + {"exp", (&jwt.Payload{}).ExpirationTimeValidator(time.Now(), false), nil}, + {"exp", (&jwt.Payload{}).ExpirationTimeValidator(time.Now(), true), jwt.ErrExpValidation}, + {"nbf", (&jwt.Payload{NotBefore: nbf}).NotBeforeValidator(now), jwt.ErrNbfValidation}, + {"nbf", (&jwt.Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()+int64(15*time.Second), 0)), nil}, + {"nbf", (&jwt.Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()-int64(15*time.Second), 0)), jwt.ErrNbfValidation}, + {"nbf", (&jwt.Payload{}).NotBeforeValidator(time.Now()), nil}, + {"iat", (&jwt.Payload{IssuedAt: iat}).IssuedAtValidator(now), nil}, + {"iat", (&jwt.Payload{IssuedAt: iat}).IssuedAtValidator(time.Unix(now.Unix()+1, 0)), nil}, + {"iat", (&jwt.Payload{IssuedAt: iat}).IssuedAtValidator(time.Unix(now.Unix()-1, 0)), jwt.ErrIatValidation}, + {"iat", (&jwt.Payload{}).IssuedAtValidator(time.Now()), nil}, + {"jti", (&jwt.Payload{JWTID: jti}).JWTIDValidator("jti"), nil}, + {"jti", (&jwt.Payload{JWTID: jti}).JWTIDValidator("not_jti"), jwt.ErrJtiValidation}, } for _, tc := range testCases { t.Run(tc.claim, func(t *testing.T) { From 1386a7f6f9f63a20cde7112b8c345d3ada6db41a Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 13:59:02 -0300 Subject: [PATCH 59/99] sign: use NumericDate function in tests --- bench_test.go | 6 +++--- sign_test.go | 25 ++++++++++++++++--------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/bench_test.go b/bench_test.go index 80bf43e..7059b8b 100644 --- a/bench_test.go +++ b/bench_test.go @@ -21,9 +21,9 @@ func BenchmarkSign(b *testing.B) { Issuer: "gbrlsnchs", Subject: "someone", Audience: jwt.Audience{"https://golang.org", "https://jwt.io"}, - ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(), - NotBefore: now.Add(30 * time.Minute).Unix(), - IssuedAt: now.Unix(), + ExpirationTime: jwt.NumericDate(now.Add(24 * 30 * 12 * time.Hour)), + NotBefore: jwt.NumericDate(now.Add(30 * time.Minute)), + IssuedAt: jwt.NumericDate(now), JWTID: "foobar", } ) diff --git a/sign_test.go b/sign_test.go index 957bfc8..2a01d2c 100644 --- a/sign_test.go +++ b/sign_test.go @@ -15,15 +15,22 @@ type testPayload struct { Int int `json:"int,omitempty"` } -var tp = testPayload{ - Payload: jwt.Payload{ - Subject: "test", - Audience: jwt.Audience{"github.com", "gsr.dev"}, - NotBefore: time.Now().Unix(), - }, - String: "foobar", - Int: 1337, -} +var ( + now = time.Now() + tp = testPayload{ + Payload: jwt.Payload{ + Issuer: "gbrlsnchs", + Subject: "someone", + Audience: jwt.Audience{"https://golang.org", "https://jwt.io"}, + ExpirationTime: jwt.NumericDate(now.Add(24 * 30 * 12 * time.Hour)), + NotBefore: jwt.NumericDate(now.Add(30 * time.Minute)), + IssuedAt: jwt.NumericDate(now), + JWTID: "foobar", + }, + String: "foobar", + Int: 1337, + } +) func TestSign(t *testing.T) { type testCase struct { From 68e09295ac80a8a6ecff2714e3a9d87459829b55 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Fri, 5 Jul 2019 13:59:32 -0300 Subject: [PATCH 60/99] README: use NumericDate in examples --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 72de234..9806c68 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,9 @@ func main() { Issuer: "gbrlsnchs", Subject: "someone", Audience: jwt.Audience{"https://golang.org", "https://jwt.io"}, - ExpirationTime: now.Add(24 * 30 * 12 * time.Hour).Unix(), - NotBefore: now.Add(30 * time.Minute).Unix(), - IssuedAt: now.Unix(), + ExpirationTime: jwt.NumericDate(now.Add(24 * 30 * 12 * time.Hour)), + NotBefore: jwt.NumericDate(now.Add(30 * time.Minute)), + IssuedAt: jwt.NumericDate(now), JWTID: "foobar", }, Foo: "foo", @@ -160,7 +160,7 @@ func main() { pl := jwt.Payload{ Subject: "gbrlsnchs", Issuer: "gsr.dev", - IssuedAt: time.Now().Unix(), + IssuedAt: jwt.NumericDate(time.Now()), } token, err := jwt.Sign(pl, hs, jwt.ContentType("JWT"), jwt.KeyID("my_key")) From 85f8365fa0d96df0108008251199632e08bfc33e Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:22:50 -0300 Subject: [PATCH 61/99] payload: use pointer to Time instead of value This fix numeric dates not being omitted. --- payload.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/payload.go b/payload.go index 953fb9f..c8329a6 100644 --- a/payload.go +++ b/payload.go @@ -5,8 +5,8 @@ type Payload struct { Issuer string `json:"iss,omitempty"` Subject string `json:"sub,omitempty"` Audience Audience `json:"aud,omitempty"` - ExpirationTime Time `json:"exp,omitempty"` - NotBefore Time `json:"nbf,omitempty"` - IssuedAt Time `json:"iat,omitempty"` + ExpirationTime *Time `json:"exp,omitempty"` + NotBefore *Time `json:"nbf,omitempty"` + IssuedAt *Time `json:"iat,omitempty"` JWTID string `json:"jti,omitempty"` } From a6cd4ffbe20293680ac7c1112cdff88f9a09e27d Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:24:01 -0300 Subject: [PATCH 62/99] time: return a pointer from NumericDate --- time.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/time.go b/time.go index f6df66d..cafe88f 100644 --- a/time.go +++ b/time.go @@ -13,11 +13,11 @@ type Time struct { } // NumericDate is a resolved Unix time. -func NumericDate(tt time.Time) Time { +func NumericDate(tt time.Time) *Time { if tt.Before(internal.Epoch) { tt = internal.Epoch } - return Time{time.Unix(tt.Unix(), 0)} + return &Time{time.Unix(tt.Unix(), 0)} } // IsZero checks whether no seconds have elapsed since epoch. From 47b10fb775ec8c784fe97c12f4baad2dbff9dc27 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:25:21 -0300 Subject: [PATCH 63/99] time: consider nil pointer a zero value --- time.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/time.go b/time.go index cafe88f..3cb1e6a 100644 --- a/time.go +++ b/time.go @@ -21,12 +21,12 @@ func NumericDate(tt time.Time) *Time { } // IsZero checks whether no seconds have elapsed since epoch. -func (t Time) IsZero() bool { - return t.Before(internal.Epoch) || t.Equal(internal.Epoch) +func (t *Time) IsZero() bool { + return t == nil || t.Before(internal.Epoch) || t.Equal(internal.Epoch) } // MarshalJSON implements a marshaling function for time-related claims. -func (t Time) MarshalJSON() ([]byte, error) { +func (t *Time) MarshalJSON() ([]byte, error) { if t.IsZero() { return json.Marshal(0) } From f8296377566893c3ba2df3416282875775e57c3f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:25:44 -0300 Subject: [PATCH 64/99] time: fix unmarshaling values less than zero --- time.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/time.go b/time.go index 3cb1e6a..1417db9 100644 --- a/time.go +++ b/time.go @@ -39,7 +39,7 @@ func (t *Time) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(b, &unix); err != nil { return err } - if unix == 0 { + if unix <= 0 { return nil } tt := time.Unix(unix, 0) From 0b0a3c0fed8cccaa85b0b6368bf821e804faa22f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:27:10 -0300 Subject: [PATCH 65/99] time: fix tests --- time_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/time_test.go b/time_test.go index f4ea80e..54b05e6 100644 --- a/time_test.go +++ b/time_test.go @@ -43,8 +43,8 @@ func TestTimeUnmarshalJSON(t *testing.T) { want jwt.Time }{ {now.Unix(), jwt.Time{now}}, - {internal.Epoch.Unix() - 1337, jwt.Time{internal.Epoch}}, - {internal.Epoch.Unix(), jwt.Time{internal.Epoch}}, + {internal.Epoch.Unix() - 1337, jwt.Time{}}, + {internal.Epoch.Unix(), jwt.Time{}}, {internal.Epoch.Unix() + 1337, jwt.Time{internal.Epoch.Add(1337 * time.Second)}}, } for _, tc := range testCases { From 00c183418a6805ec45d558b420163f0397a0d4e9 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:27:26 -0300 Subject: [PATCH 66/99] validators: check for nil Time pointers --- validators.go | 11 ++++------- validators_test.go | 18 +++++++----------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/validators.go b/validators.go index 0889084..9524bb2 100644 --- a/validators.go +++ b/validators.go @@ -42,12 +42,9 @@ func (pl *Payload) AudienceValidator(aud Audience) ValidatorFunc { } // ExpirationTimeValidator validates the "exp" claim. -func (pl *Payload) ExpirationTimeValidator(now time.Time, validateZero bool) ValidatorFunc { +func (pl *Payload) ExpirationTimeValidator(now time.Time) ValidatorFunc { return func() error { - if !validateZero && pl.ExpirationTime.IsZero() { - return nil - } - if now.After(pl.ExpirationTime.Time) { + if pl.ExpirationTime == nil || NumericDate(now).After(pl.ExpirationTime.Time) { return ErrExpValidation } return nil @@ -57,7 +54,7 @@ func (pl *Payload) ExpirationTimeValidator(now time.Time, validateZero bool) Val // IssuedAtValidator validates the "iat" claim. func (pl *Payload) IssuedAtValidator(now time.Time) ValidatorFunc { return func() error { - if now.Before(pl.IssuedAt.Time) { + if pl.IssuedAt != nil && NumericDate(now).Before(pl.IssuedAt.Time) { return ErrIatValidation } return nil @@ -87,7 +84,7 @@ func (pl *Payload) JWTIDValidator(jti string) ValidatorFunc { // NotBeforeValidator validates the "nbf" claim. func (pl *Payload) NotBeforeValidator(now time.Time) ValidatorFunc { return func() error { - if now.Before(pl.NotBefore.Time) { + if pl.NotBefore != nil && NumericDate(now).Before(pl.NotBefore.Time) { return ErrNbfValidation } return nil diff --git a/validators_test.go b/validators_test.go index cad874e..533f179 100644 --- a/validators_test.go +++ b/validators_test.go @@ -9,9 +9,9 @@ import ( func TestValidators(t *testing.T) { now := time.Now() - iat := jwt.Time{now} - exp := jwt.Time{now.Add(24 * time.Hour)} - nbf := jwt.Time{now.Add(15 * time.Second)} + iat := jwt.NumericDate(now) + exp := jwt.NumericDate(now.Add(24 * time.Hour)) + nbf := jwt.NumericDate(now.Add(15 * time.Second)) jti := "jti" aud := jwt.Audience{"aud", "aud1", "aud2", "aud3"} sub := "sub" @@ -31,14 +31,10 @@ func TestValidators(t *testing.T) { {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"baz", "aud3"}), nil}, {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"qux", "aud4"}), jwt.ErrAudValidation}, {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"not_aud"}), jwt.ErrAudValidation}, - {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(now, true), nil}, - {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(now, false), nil}, - {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), true), nil}, - {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0), false), nil}, - {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), true), jwt.ErrExpValidation}, - {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0), false), jwt.ErrExpValidation}, - {"exp", (&jwt.Payload{}).ExpirationTimeValidator(time.Now(), false), nil}, - {"exp", (&jwt.Payload{}).ExpirationTimeValidator(time.Now(), true), jwt.ErrExpValidation}, + {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(now), nil}, + {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0)), nil}, + {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0)), jwt.ErrExpValidation}, + {"exp", (&jwt.Payload{}).ExpirationTimeValidator(time.Now()), jwt.ErrExpValidation}, {"nbf", (&jwt.Payload{NotBefore: nbf}).NotBeforeValidator(now), jwt.ErrNbfValidation}, {"nbf", (&jwt.Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()+int64(15*time.Second), 0)), nil}, {"nbf", (&jwt.Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()-int64(15*time.Second), 0)), jwt.ErrNbfValidation}, From 233beb4647e60e1e0d6f51a0c389f0afb8ae45b1 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:29:53 -0300 Subject: [PATCH 67/99] audience: move test to subtest --- audience_test.go | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/audience_test.go b/audience_test.go index 9d9711b..28aec26 100644 --- a/audience_test.go +++ b/audience_test.go @@ -9,6 +9,21 @@ import ( ) func TestAudienceMarshal(t *testing.T) { + t.Run("omitempty", func(t *testing.T) { + var ( + b []byte + err error + v = struct { + Audience jwt.Audience `json:"aud,omitempty"` + }{} + ) + if b, err = json.Marshal(v); err != nil { + t.Fatal(err) + } + checkAudMarshal(t, "{}", b) + + }) + testCases := []struct { aud jwt.Audience expected string @@ -39,20 +54,6 @@ func TestAudienceMarshal(t *testing.T) { } } -func TestAudienceOmitempty(t *testing.T) { - var ( - b []byte - err error - v = struct { - Audience jwt.Audience `json:"aud,omitempty"` - }{} - ) - if b, err = json.Marshal(v); err != nil { - t.Fatal(err) - } - checkAudMarshal(t, "{}", b) -} - func TestAudienceUnmarshal(t *testing.T) { testCases := []struct { jstr []byte From 40c57b0ab1a110a2f575a43e5a4650a568d4cfd1 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:54:18 -0300 Subject: [PATCH 68/99] verify: fix documentation --- verify.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/verify.go b/verify.go index acf9b13..ca2cb50 100644 --- a/verify.go +++ b/verify.go @@ -12,7 +12,8 @@ var ErrAlgValidation = internal.NewError(`"alg" field mismatch`) // VerifyOption is a functional option for verifying. type VerifyOption func(*RawToken) error -// Verify verifies a token's signature. +// Verify verifies a token's signature using alg. Before verification, opts is iterated and +// each option in it is run. func Verify(token []byte, alg Algorithm, opts ...VerifyOption) error { rt := &RawToken{alg: alg} @@ -46,7 +47,7 @@ func Verify(token []byte, alg Algorithm, opts ...VerifyOption) error { return nil } -// DecodeHeader decodes into hd and validates it when validate is true. +// DecodeHeader decodes into hd. If validate is truthy, hd is also validated. func DecodeHeader(hd *Header, validate bool) VerifyOption { return func(rt *RawToken) error { if err := internal.Decode(rt.header(), hd); err != nil { @@ -59,7 +60,8 @@ func DecodeHeader(hd *Header, validate bool) VerifyOption { } } -// DecodePayload decodes into payload and run validators, if any. +// DecodePayload flags a payload to be decoded. After decoding, validators is iterated and +// each validator in it is run. func DecodePayload(payload interface{}, validators ...ValidatorFunc) VerifyOption { return func(rt *RawToken) (err error) { rt.payloadAddr = payload @@ -75,4 +77,7 @@ func ValidateHeader(rt *RawToken) error { return DecodeHeader(&hd, true)(rt) } -var _ VerifyOption = ValidateHeader // compile-time test +// Compile-time checks. +var _ VerifyOption = ValidateHeader +var _ VerifyOption = DecodeHeader(nil, false) +var _ VerifyOption = DecodePayload(nil) From 83ef841a72b5cb0fbcffbeadb5a3702c3d40f29d Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:54:55 -0300 Subject: [PATCH 69/99] verify: fix decoding payload before verifying The payload should be decoded only after verification is run. --- verify.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/verify.go b/verify.go index ca2cb50..612d245 100644 --- a/verify.go +++ b/verify.go @@ -35,15 +35,14 @@ func Verify(token []byte, alg Algorithm, opts ...VerifyOption) error { return err } } + if err = alg.Verify(rt.headerPayload(), rt.sig()); err != nil { + return err + } if rt.payloadAddr != nil { if err = rt.decode(); err != nil { return err } } - - if err = alg.Verify(rt.headerPayload(), rt.sig()); err != nil { - return err - } return nil } From 96b30cbb31589f02a549dffabc627a4e35a7f361 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 19:58:01 -0300 Subject: [PATCH 70/99] README: remove badge for vgo compatibility Using `vgo` with `go1.10` causes an error because of the `xerrors` package. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9806c68..d7ea204 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ ## Compatibility [![Version Compatibility](https://img.shields.io/badge/go%20modules-go1.11+-5272b4.svg)](https://github.com/gbrlsnchs/jwt#installing) -[![Version Compatibility](https://img.shields.io/badge/vgo-go1.10-5272b4.svg)](https://github.com/gbrlsnchs/jwt#installing) [![go get](https://img.shields.io/badge/go%20get-go1.9.7+,%20go1.10.3+%20and%20go1.11-5272b4.svg)](https://github.com/gbrlsnchs/jwt#installing) ## About From a177b253ada35d5946b90ceb3be6a3f854c27bf6 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 20:00:31 -0300 Subject: [PATCH 71/99] README: remove installation instructions for vgo --- README.md | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/README.md b/README.md index d7ea204..c82db47 100644 --- a/README.md +++ b/README.md @@ -54,16 +54,6 @@ $ GO111MODULE=on go get -u github.com/gbrlsnchs/jwt/v3

-
Go 1.10 with vgo -

- -```sh -$ vgo get -u github.com/gbrlsnchs/jwt/v3 -``` - -

-
-
Go 1.9.7+, Go 1.10.3+ (without vgo) and Go 1.11 (when GO111MODULE=off)

From 0308d2e1f150cb17c98d04b85be2c0cf99230fd8 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Sat, 6 Jul 2019 20:01:48 -0300 Subject: [PATCH 72/99] go.mod: bump minimal version --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d34e936..6798f2d 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,4 @@ require ( golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 ) -go 1.10 +go 1.11 From 0efefb75c477b2c4b494bfd76555f6b1688603b6 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Mon, 8 Jul 2019 20:31:42 -0300 Subject: [PATCH 73/99] Store Header pointer in RawToken By persisting a pointer in the `RawToken`, it is possible to reuse an already decoded Header when using `DecodeHeader`. --- raw_token.go | 1 + verify.go | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/raw_token.go b/raw_token.go index 46e6362..225abc3 100644 --- a/raw_token.go +++ b/raw_token.go @@ -13,6 +13,7 @@ type RawToken struct { alg Algorithm // Verify options. + hdAddr *Header payloadAddr interface{} payloadValidators []ValidatorFunc } diff --git a/verify.go b/verify.go index 612d245..02e2d12 100644 --- a/verify.go +++ b/verify.go @@ -49,10 +49,15 @@ func Verify(token []byte, alg Algorithm, opts ...VerifyOption) error { // DecodeHeader decodes into hd. If validate is truthy, hd is also validated. func DecodeHeader(hd *Header, validate bool) VerifyOption { return func(rt *RawToken) error { - if err := internal.Decode(rt.header(), hd); err != nil { - return err + if rt.hdAddr == nil { + var hd Header + if err := internal.Decode(rt.header(), &hd); err != nil { + return err + } + rt.hdAddr = &hd } - if validate && rt.alg.Name() != hd.Algorithm { + *hd = *rt.hdAddr + if validate && rt.alg.Name() != rt.hdAddr.Algorithm { return internal.Errorf("jwt: unexpected algorithm %q: %w", hd.Algorithm, ErrAlgValidation) } return nil From 785fdcdbe630014e91e0ddcc7a6e3399bc8121d1 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:02:16 -0300 Subject: [PATCH 74/99] time: fix JSON-related methods --- time.go | 17 ++++++----------- time_test.go | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/time.go b/time.go index 1417db9..ed3bbbb 100644 --- a/time.go +++ b/time.go @@ -17,17 +17,12 @@ func NumericDate(tt time.Time) *Time { if tt.Before(internal.Epoch) { tt = internal.Epoch } - return &Time{time.Unix(tt.Unix(), 0)} -} - -// IsZero checks whether no seconds have elapsed since epoch. -func (t *Time) IsZero() bool { - return t == nil || t.Before(internal.Epoch) || t.Equal(internal.Epoch) + return &Time{time.Unix(tt.Unix(), 0)} // set time using Unix time } // MarshalJSON implements a marshaling function for time-related claims. -func (t *Time) MarshalJSON() ([]byte, error) { - if t.IsZero() { +func (t Time) MarshalJSON() ([]byte, error) { + if t.Before(internal.Epoch) { return json.Marshal(0) } return json.Marshal(t.Unix()) @@ -35,14 +30,14 @@ func (t *Time) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements an unmarshaling function for time-related claims. func (t *Time) UnmarshalJSON(b []byte) error { - var unix int64 + var unix *int64 if err := json.Unmarshal(b, &unix); err != nil { return err } - if unix <= 0 { + if unix == nil { return nil } - tt := time.Unix(unix, 0) + tt := time.Unix(*unix, 0) if tt.Before(internal.Epoch) { tt = internal.Epoch } diff --git a/time_test.go b/time_test.go index 54b05e6..eccef04 100644 --- a/time_test.go +++ b/time_test.go @@ -18,6 +18,7 @@ func TestTimeMarshalJSON(t *testing.T) { {jwt.Time{}, 0}, {jwt.Time{now}, now.Unix()}, {jwt.Time{now.Add(24 * time.Hour)}, now.Add(24 * time.Hour).Unix()}, + {jwt.Time{now.Add(24 * 30 * 12 * time.Hour)}, now.Add(24 * 30 * 12 * time.Hour).Unix()}, } for _, tc := range testCases { t.Run("", func(t *testing.T) { @@ -39,17 +40,23 @@ func TestTimeMarshalJSON(t *testing.T) { func TestTimeUnmarshalJSON(t *testing.T) { now := time.Now() testCases := []struct { - n int64 - want jwt.Time + n int64 + want jwt.Time + isNil bool }{ - {now.Unix(), jwt.Time{now}}, - {internal.Epoch.Unix() - 1337, jwt.Time{}}, - {internal.Epoch.Unix(), jwt.Time{}}, - {internal.Epoch.Unix() + 1337, jwt.Time{internal.Epoch.Add(1337 * time.Second)}}, + {now.Unix(), jwt.Time{now}, false}, + {internal.Epoch.Unix() - 1337, jwt.Time{internal.Epoch}, false}, + {internal.Epoch.Unix(), jwt.Time{internal.Epoch}, false}, + {internal.Epoch.Unix() + 1337, jwt.Time{internal.Epoch.Add(1337 * time.Second)}, false}, + {0, jwt.Time{}, true}, } for _, tc := range testCases { t.Run("", func(t *testing.T) { - b, err := json.Marshal(tc.n) + var n *int64 + if !tc.isNil { + n = &tc.n + } + b, err := json.Marshal(n) if err != nil { t.Fatal(err) } From a889cd2e3bbad19ec76aed70e319ceb3d91093dd Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:04:02 -0300 Subject: [PATCH 75/99] raw_token: add method to decode a Header --- raw_token.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/raw_token.go b/raw_token.go index 225abc3..44ef4a6 100644 --- a/raw_token.go +++ b/raw_token.go @@ -10,6 +10,7 @@ type RawToken struct { token []byte sep1, sep2 int + hd Header alg Algorithm // Verify options. @@ -40,3 +41,10 @@ func (rt *RawToken) decode() (err error) { } return nil } + +func (rt *RawToken) decodeHeader() error { + if err := internal.Decode(rt.header(), &rt.hd); err != nil { + return err + } + return nil +} From 5c2629ddac3d0d7999f42b43a38fe706db56c0ef Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:04:47 -0300 Subject: [PATCH 76/99] raw_token: add a way to validate a Payload --- raw_token.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/raw_token.go b/raw_token.go index 44ef4a6..d9e7e32 100644 --- a/raw_token.go +++ b/raw_token.go @@ -13,10 +13,8 @@ type RawToken struct { hd Header alg Algorithm - // Verify options. - hdAddr *Header - payloadAddr interface{} - payloadValidators []ValidatorFunc + pl *Payload + vds []ValidatorFunc } func (rt *RawToken) header() []byte { return rt.token[:rt.sep1] } @@ -30,12 +28,12 @@ func (rt *RawToken) setToken(token []byte, sep1, sep2 int) { rt.token = token } -func (rt *RawToken) decode() (err error) { - if err = internal.Decode(rt.payload(), rt.payloadAddr); err != nil { +func (rt *RawToken) decode(payload interface{}) (err error) { + if err = internal.Decode(rt.payload(), payload); err != nil { return err } - for _, vd := range rt.payloadValidators { - if err = vd(); err != nil { + for _, vd := range rt.vds { + if err = vd(rt.pl); err != nil { return err } } From 0c7f22c3617bb237c582281afc8026d196370af8 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:07:45 -0300 Subject: [PATCH 77/99] verify: return a Header after verifying --- verify.go | 51 +++++++++++++++------------------------------------ 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/verify.go b/verify.go index 02e2d12..3218529 100644 --- a/verify.go +++ b/verify.go @@ -14,54 +14,42 @@ type VerifyOption func(*RawToken) error // Verify verifies a token's signature using alg. Before verification, opts is iterated and // each option in it is run. -func Verify(token []byte, alg Algorithm, opts ...VerifyOption) error { - rt := &RawToken{alg: alg} +func Verify(token []byte, payload interface{}, alg Algorithm, opts ...VerifyOption) (Header, error) { + rt := &RawToken{ + alg: alg, + } sep1 := bytes.IndexByte(token, '.') if sep1 < 0 { - return ErrMalformed + return rt.hd, ErrMalformed } cbytes := token[sep1+1:] sep2 := bytes.IndexByte(cbytes, '.') if sep2 < 0 { - return ErrMalformed + return rt.hd, ErrMalformed } rt.setToken(token, sep1, sep2) var err error for _, opt := range opts { if err = opt(rt); err != nil { - return err + return rt.hd, err } } if err = alg.Verify(rt.headerPayload(), rt.sig()); err != nil { - return err - } - if rt.payloadAddr != nil { - if err = rt.decode(); err != nil { - return err - } + return rt.hd, err } - return nil + return rt.hd, rt.decode(payload) } -// DecodeHeader decodes into hd. If validate is truthy, hd is also validated. -func DecodeHeader(hd *Header, validate bool) VerifyOption { - return func(rt *RawToken) error { - if rt.hdAddr == nil { - var hd Header - if err := internal.Decode(rt.header(), &hd); err != nil { - return err - } - rt.hdAddr = &hd - } - *hd = *rt.hdAddr - if validate && rt.alg.Name() != rt.hdAddr.Algorithm { - return internal.Errorf("jwt: unexpected algorithm %q: %w", hd.Algorithm, ErrAlgValidation) - } - return nil +// ValidateHeader checks whether the algorithm contained +// in the JOSE header is the same used by the algorithm. +func ValidateHeader(rt *RawToken) error { + if rt.alg.Name() != rt.hd.Algorithm { + return internal.Errorf("jwt: unexpected algorithm %q: %w", rt.hd.Algorithm, ErrAlgValidation) } + return nil } // DecodePayload flags a payload to be decoded. After decoding, validators is iterated and @@ -74,14 +62,5 @@ func DecodePayload(payload interface{}, validators ...ValidatorFunc) VerifyOptio } } -// ValidateHeader checks whether the algorithm contained -// in the JOSE header is the same used by the algorithm. -func ValidateHeader(rt *RawToken) error { - var hd Header - return DecodeHeader(&hd, true)(rt) -} - // Compile-time checks. var _ VerifyOption = ValidateHeader -var _ VerifyOption = DecodeHeader(nil, false) -var _ VerifyOption = DecodePayload(nil) From 20a14c2806eded8a4a5c4a10081d117bec3dfe5e Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:08:13 -0300 Subject: [PATCH 78/99] verify: add support for resolving before verifying --- resolver.go | 7 +++++++ verify.go | 8 ++++++++ 2 files changed, 15 insertions(+) create mode 100644 resolver.go diff --git a/resolver.go b/resolver.go new file mode 100644 index 0000000..08f7767 --- /dev/null +++ b/resolver.go @@ -0,0 +1,7 @@ +package jwt + +// Resolver is an Algorithm that needs to set some variables +// based on a Header before performing signing and verification. +type Resolver interface { + Resolve(Header) error +} diff --git a/verify.go b/verify.go index 3218529..09ac307 100644 --- a/verify.go +++ b/verify.go @@ -32,6 +32,14 @@ func Verify(token []byte, payload interface{}, alg Algorithm, opts ...VerifyOpti rt.setToken(token, sep1, sep2) var err error + if err = rt.decodeHeader(); err != nil { + return rt.hd, err + } + if rv, ok := alg.(Resolver); ok { + if err = rv.Resolve(rt.hd); err != nil { + return rt.hd, err + } + } for _, opt := range opts { if err = opt(rt); err != nil { return rt.hd, err From 6b67cf3bfdb34927a0b5454a46671a059014d4d5 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:09:21 -0300 Subject: [PATCH 79/99] verify: add option to validate a Payload --- verify.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/verify.go b/verify.go index 09ac307..9930b7b 100644 --- a/verify.go +++ b/verify.go @@ -60,12 +60,11 @@ func ValidateHeader(rt *RawToken) error { return nil } -// DecodePayload flags a payload to be decoded. After decoding, validators is iterated and -// each validator in it is run. -func DecodePayload(payload interface{}, validators ...ValidatorFunc) VerifyOption { - return func(rt *RawToken) (err error) { - rt.payloadAddr = payload - rt.payloadValidators = validators +// ValidatePayload runs validators against a Payload after it's been decoded. +func ValidatePayload(pl *Payload, vds ...ValidatorFunc) VerifyOption { + return func(rt *RawToken) error { + rt.pl = pl + rt.vds = vds return nil } } From d00463849bd971014ef690f42b8f06035e6ddde8 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:09:53 -0300 Subject: [PATCH 80/99] sign: add support for resolving before signing --- sign.go | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/sign.go b/sign.go index 272625e..6db5b36 100644 --- a/sign.go +++ b/sign.go @@ -8,12 +8,31 @@ import ( // SignOption is a functional option for signing. type SignOption func(*Header) +// ContentType sets the "cty" claim for a Header before signing. +func ContentType(cty string) SignOption { + return func(hd *Header) { + hd.ContentType = cty + } +} + +// KeyID sets the "kid" claim for a Header before signing. +func KeyID(kid string) SignOption { + return func(hd *Header) { + hd.KeyID = kid + } +} + // Sign signs a payload with alg. func Sign(payload interface{}, alg Algorithm, opts ...SignOption) ([]byte, error) { var hd Header for _, opt := range opts { opt(&hd) } + if rv, ok := alg.(Resolver); ok { + if err := rv.Resolve(hd); err != nil { + return nil, err + } + } // Override some values or set them if empty. hd.Algorithm = alg.Name() hd.Type = "JWT" @@ -49,17 +68,3 @@ func Sign(payload interface{}, alg Algorithm, opts ...SignOption) ([]byte, error enc.Encode(token[h64len+1+p64len+1:], sig) return token, nil } - -// ContentType sets the "cty" claim for a Header before signing. -func ContentType(cty string) SignOption { - return func(hd *Header) { - hd.ContentType = cty - } -} - -// KeyID sets the "kid" claim for a Header before signing. -func KeyID(kid string) SignOption { - return func(hd *Header) { - hd.KeyID = kid - } -} From 5a74e88a88b67661e52d76ae8d793c1d8c99f906 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:10:33 -0300 Subject: [PATCH 81/99] sign: update verification test --- sign_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sign_test.go b/sign_test.go index 2a01d2c..14f647b 100644 --- a/sign_test.go +++ b/sign_test.go @@ -360,9 +360,7 @@ func TestSign(t *testing.T) { hd jwt.Header payload testPayload ) - err = jwt.Verify(token, tc.verifyAlg, - jwt.DecodeHeader(&hd, false), - jwt.DecodePayload(&payload)) + hd, err = jwt.Verify(token, &payload, tc.verifyAlg) if want, got := tc.verifyErr, err; !internal.ErrorIs(got, want) { t.Fatalf("want %v, got %v", want, got) } From f96a66888003f0c6bf706a07eea460c6c3c01ff8 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:11:02 -0300 Subject: [PATCH 82/99] validators: make validators functions again --- validators.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/validators.go b/validators.go index 9524bb2..5fe7b9d 100644 --- a/validators.go +++ b/validators.go @@ -24,12 +24,12 @@ var ( // ValidatorFunc is a function for running extra // validators when parsing a Payload string. -type ValidatorFunc func() error +type ValidatorFunc func(*Payload) error // AudienceValidator validates the "aud" claim. // It checks if at least one of the audiences in the JWT's payload is listed in aud. -func (pl *Payload) AudienceValidator(aud Audience) ValidatorFunc { - return func() error { +func AudienceValidator(aud Audience) ValidatorFunc { + return func(pl *Payload) error { for _, serverAud := range aud { for _, clientAud := range pl.Audience { if clientAud == serverAud { @@ -42,8 +42,8 @@ func (pl *Payload) AudienceValidator(aud Audience) ValidatorFunc { } // ExpirationTimeValidator validates the "exp" claim. -func (pl *Payload) ExpirationTimeValidator(now time.Time) ValidatorFunc { - return func() error { +func ExpirationTimeValidator(now time.Time) ValidatorFunc { + return func(pl *Payload) error { if pl.ExpirationTime == nil || NumericDate(now).After(pl.ExpirationTime.Time) { return ErrExpValidation } @@ -52,8 +52,8 @@ func (pl *Payload) ExpirationTimeValidator(now time.Time) ValidatorFunc { } // IssuedAtValidator validates the "iat" claim. -func (pl *Payload) IssuedAtValidator(now time.Time) ValidatorFunc { - return func() error { +func IssuedAtValidator(now time.Time) ValidatorFunc { + return func(pl *Payload) error { if pl.IssuedAt != nil && NumericDate(now).Before(pl.IssuedAt.Time) { return ErrIatValidation } @@ -62,8 +62,8 @@ func (pl *Payload) IssuedAtValidator(now time.Time) ValidatorFunc { } // IssuerValidator validates the "iss" claim. -func (pl *Payload) IssuerValidator(iss string) ValidatorFunc { - return func() error { +func IssuerValidator(iss string) ValidatorFunc { + return func(pl *Payload) error { if pl.Issuer != iss { return ErrIssValidation } @@ -71,9 +71,9 @@ func (pl *Payload) IssuerValidator(iss string) ValidatorFunc { } } -// JWTIDValidator validates the "jti" claim. -func (pl *Payload) JWTIDValidator(jti string) ValidatorFunc { - return func() error { +// IDValidator validates the "jti" claim. +func IDValidator(jti string) ValidatorFunc { + return func(pl *Payload) error { if pl.JWTID != jti { return ErrJtiValidation } @@ -82,8 +82,8 @@ func (pl *Payload) JWTIDValidator(jti string) ValidatorFunc { } // NotBeforeValidator validates the "nbf" claim. -func (pl *Payload) NotBeforeValidator(now time.Time) ValidatorFunc { - return func() error { +func NotBeforeValidator(now time.Time) ValidatorFunc { + return func(pl *Payload) error { if pl.NotBefore != nil && NumericDate(now).Before(pl.NotBefore.Time) { return ErrNbfValidation } @@ -92,8 +92,8 @@ func (pl *Payload) NotBeforeValidator(now time.Time) ValidatorFunc { } // SubjectValidator validates the "sub" claim. -func (pl *Payload) SubjectValidator(sub string) ValidatorFunc { - return func() error { +func SubjectValidator(sub string) ValidatorFunc { + return func(pl *Payload) error { if pl.Subject != sub { return ErrSubValidation } From 8a4aebdebf55c9e7efb4e4d5b5c6b414e56b3e58 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:14:37 -0300 Subject: [PATCH 83/99] validators: rename from ValidatorFunc to Validator --- raw_token.go | 2 +- validators.go | 19 +++++++++---------- verify.go | 2 +- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/raw_token.go b/raw_token.go index d9e7e32..0f40dba 100644 --- a/raw_token.go +++ b/raw_token.go @@ -14,7 +14,7 @@ type RawToken struct { alg Algorithm pl *Payload - vds []ValidatorFunc + vds []Validator } func (rt *RawToken) header() []byte { return rt.token[:rt.sep1] } diff --git a/validators.go b/validators.go index 5fe7b9d..f16b90f 100644 --- a/validators.go +++ b/validators.go @@ -22,13 +22,12 @@ var ( ErrSubValidation = errors.New("jwt: sub claim is invalid") ) -// ValidatorFunc is a function for running extra -// validators when parsing a Payload string. -type ValidatorFunc func(*Payload) error +// Validator is a function that validates a Payload pointer. +type Validator func(*Payload) error // AudienceValidator validates the "aud" claim. // It checks if at least one of the audiences in the JWT's payload is listed in aud. -func AudienceValidator(aud Audience) ValidatorFunc { +func AudienceValidator(aud Audience) Validator { return func(pl *Payload) error { for _, serverAud := range aud { for _, clientAud := range pl.Audience { @@ -42,7 +41,7 @@ func AudienceValidator(aud Audience) ValidatorFunc { } // ExpirationTimeValidator validates the "exp" claim. -func ExpirationTimeValidator(now time.Time) ValidatorFunc { +func ExpirationTimeValidator(now time.Time) Validator { return func(pl *Payload) error { if pl.ExpirationTime == nil || NumericDate(now).After(pl.ExpirationTime.Time) { return ErrExpValidation @@ -52,7 +51,7 @@ func ExpirationTimeValidator(now time.Time) ValidatorFunc { } // IssuedAtValidator validates the "iat" claim. -func IssuedAtValidator(now time.Time) ValidatorFunc { +func IssuedAtValidator(now time.Time) Validator { return func(pl *Payload) error { if pl.IssuedAt != nil && NumericDate(now).Before(pl.IssuedAt.Time) { return ErrIatValidation @@ -62,7 +61,7 @@ func IssuedAtValidator(now time.Time) ValidatorFunc { } // IssuerValidator validates the "iss" claim. -func IssuerValidator(iss string) ValidatorFunc { +func IssuerValidator(iss string) Validator { return func(pl *Payload) error { if pl.Issuer != iss { return ErrIssValidation @@ -72,7 +71,7 @@ func IssuerValidator(iss string) ValidatorFunc { } // IDValidator validates the "jti" claim. -func IDValidator(jti string) ValidatorFunc { +func IDValidator(jti string) Validator { return func(pl *Payload) error { if pl.JWTID != jti { return ErrJtiValidation @@ -82,7 +81,7 @@ func IDValidator(jti string) ValidatorFunc { } // NotBeforeValidator validates the "nbf" claim. -func NotBeforeValidator(now time.Time) ValidatorFunc { +func NotBeforeValidator(now time.Time) Validator { return func(pl *Payload) error { if pl.NotBefore != nil && NumericDate(now).Before(pl.NotBefore.Time) { return ErrNbfValidation @@ -92,7 +91,7 @@ func NotBeforeValidator(now time.Time) ValidatorFunc { } // SubjectValidator validates the "sub" claim. -func SubjectValidator(sub string) ValidatorFunc { +func SubjectValidator(sub string) Validator { return func(pl *Payload) error { if pl.Subject != sub { return ErrSubValidation diff --git a/verify.go b/verify.go index 9930b7b..30835fb 100644 --- a/verify.go +++ b/verify.go @@ -61,7 +61,7 @@ func ValidateHeader(rt *RawToken) error { } // ValidatePayload runs validators against a Payload after it's been decoded. -func ValidatePayload(pl *Payload, vds ...ValidatorFunc) VerifyOption { +func ValidatePayload(pl *Payload, vds ...Validator) VerifyOption { return func(rt *RawToken) error { rt.pl = pl rt.vds = vds From b72a71ecaa2303bddf7519ddbb6783a5ac622ff0 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:16:02 -0300 Subject: [PATCH 84/99] validators: update tests --- validators_test.go | 53 +++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/validators_test.go b/validators_test.go index 533f179..69ddee1 100644 --- a/validators_test.go +++ b/validators_test.go @@ -18,37 +18,38 @@ func TestValidators(t *testing.T) { iss := "iss" testCases := []struct { claim string - vl jwt.ValidatorFunc + pl *jwt.Payload + vl jwt.Validator err error }{ - {"iss", (&jwt.Payload{Issuer: iss}).IssuerValidator("iss"), nil}, - {"iss", (&jwt.Payload{Issuer: iss}).IssuerValidator("not_iss"), jwt.ErrIssValidation}, - {"sub", (&jwt.Payload{Subject: sub}).SubjectValidator("sub"), nil}, - {"sub", (&jwt.Payload{Subject: sub}).SubjectValidator("not_sub"), jwt.ErrSubValidation}, - {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"aud"}), nil}, - {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"foo", "aud1"}), nil}, - {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"bar", "aud2"}), nil}, - {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"baz", "aud3"}), nil}, - {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"qux", "aud4"}), jwt.ErrAudValidation}, - {"aud", (&jwt.Payload{Audience: aud}).AudienceValidator(jwt.Audience{"not_aud"}), jwt.ErrAudValidation}, - {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(now), nil}, - {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0)), nil}, - {"exp", (&jwt.Payload{ExpirationTime: exp}).ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0)), jwt.ErrExpValidation}, - {"exp", (&jwt.Payload{}).ExpirationTimeValidator(time.Now()), jwt.ErrExpValidation}, - {"nbf", (&jwt.Payload{NotBefore: nbf}).NotBeforeValidator(now), jwt.ErrNbfValidation}, - {"nbf", (&jwt.Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()+int64(15*time.Second), 0)), nil}, - {"nbf", (&jwt.Payload{NotBefore: nbf}).NotBeforeValidator(time.Unix(now.Unix()-int64(15*time.Second), 0)), jwt.ErrNbfValidation}, - {"nbf", (&jwt.Payload{}).NotBeforeValidator(time.Now()), nil}, - {"iat", (&jwt.Payload{IssuedAt: iat}).IssuedAtValidator(now), nil}, - {"iat", (&jwt.Payload{IssuedAt: iat}).IssuedAtValidator(time.Unix(now.Unix()+1, 0)), nil}, - {"iat", (&jwt.Payload{IssuedAt: iat}).IssuedAtValidator(time.Unix(now.Unix()-1, 0)), jwt.ErrIatValidation}, - {"iat", (&jwt.Payload{}).IssuedAtValidator(time.Now()), nil}, - {"jti", (&jwt.Payload{JWTID: jti}).JWTIDValidator("jti"), nil}, - {"jti", (&jwt.Payload{JWTID: jti}).JWTIDValidator("not_jti"), jwt.ErrJtiValidation}, + {"iss", &jwt.Payload{Issuer: iss}, jwt.IssuerValidator("iss"), nil}, + {"iss", &jwt.Payload{Issuer: iss}, jwt.IssuerValidator("not_iss"), jwt.ErrIssValidation}, + {"sub", &jwt.Payload{Subject: sub}, jwt.SubjectValidator("sub"), nil}, + {"sub", &jwt.Payload{Subject: sub}, jwt.SubjectValidator("not_sub"), jwt.ErrSubValidation}, + {"aud", &jwt.Payload{Audience: aud}, jwt.AudienceValidator(jwt.Audience{"aud"}), nil}, + {"aud", &jwt.Payload{Audience: aud}, jwt.AudienceValidator(jwt.Audience{"foo", "aud1"}), nil}, + {"aud", &jwt.Payload{Audience: aud}, jwt.AudienceValidator(jwt.Audience{"bar", "aud2"}), nil}, + {"aud", &jwt.Payload{Audience: aud}, jwt.AudienceValidator(jwt.Audience{"baz", "aud3"}), nil}, + {"aud", &jwt.Payload{Audience: aud}, jwt.AudienceValidator(jwt.Audience{"qux", "aud4"}), jwt.ErrAudValidation}, + {"aud", &jwt.Payload{Audience: aud}, jwt.AudienceValidator(jwt.Audience{"not_aud"}), jwt.ErrAudValidation}, + {"exp", &jwt.Payload{ExpirationTime: exp}, jwt.ExpirationTimeValidator(now), nil}, + {"exp", &jwt.Payload{ExpirationTime: exp}, jwt.ExpirationTimeValidator(time.Unix(now.Unix()-int64(24*time.Hour), 0)), nil}, + {"exp", &jwt.Payload{ExpirationTime: exp}, jwt.ExpirationTimeValidator(time.Unix(now.Unix()+int64(24*time.Hour), 0)), jwt.ErrExpValidation}, + {"exp", &jwt.Payload{}, jwt.ExpirationTimeValidator(time.Now()), jwt.ErrExpValidation}, + {"nbf", &jwt.Payload{NotBefore: nbf}, jwt.NotBeforeValidator(now), jwt.ErrNbfValidation}, + {"nbf", &jwt.Payload{NotBefore: nbf}, jwt.NotBeforeValidator(time.Unix(now.Unix()+int64(15*time.Second), 0)), nil}, + {"nbf", &jwt.Payload{NotBefore: nbf}, jwt.NotBeforeValidator(time.Unix(now.Unix()-int64(15*time.Second), 0)), jwt.ErrNbfValidation}, + {"nbf", &jwt.Payload{}, jwt.NotBeforeValidator(time.Now()), nil}, + {"iat", &jwt.Payload{IssuedAt: iat}, jwt.IssuedAtValidator(now), nil}, + {"iat", &jwt.Payload{IssuedAt: iat}, jwt.IssuedAtValidator(time.Unix(now.Unix()+1, 0)), nil}, + {"iat", &jwt.Payload{IssuedAt: iat}, jwt.IssuedAtValidator(time.Unix(now.Unix()-1, 0)), jwt.ErrIatValidation}, + {"iat", &jwt.Payload{}, jwt.IssuedAtValidator(time.Now()), nil}, + {"jti", &jwt.Payload{JWTID: jti}, jwt.IDValidator("jti"), nil}, + {"jti", &jwt.Payload{JWTID: jti}, jwt.IDValidator("not_jti"), jwt.ErrJtiValidation}, } for _, tc := range testCases { t.Run(tc.claim, func(t *testing.T) { - if want, got := tc.err, tc.vl(); want != got { + if want, got := tc.err, tc.vl(tc.pl); want != got { t.Errorf("want %v, got %v", want, got) } }) From d7a203acbd69becc73c6f0130fb8feec9f402061 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:27:56 -0300 Subject: [PATCH 85/99] Remove xerrors dependency (again) Supporting Go 1.10 with `vgo` is worth not using `xerrors`. --- ed25519_go1_12.go | 8 +++++--- go.mod | 7 ++----- go.sum | 2 -- internal/decode_test.go | 9 ++++----- internal/error_go1_12.go | 17 ----------------- raw_token.go | 8 ++++++-- sign_test.go | 5 ++--- verify.go | 7 +++---- 8 files changed, 22 insertions(+), 41 deletions(-) delete mode 100644 internal/error_go1_12.go diff --git a/ed25519_go1_12.go b/ed25519_go1_12.go index b28007d..557a41b 100644 --- a/ed25519_go1_12.go +++ b/ed25519_go1_12.go @@ -3,17 +3,19 @@ package jwt import ( + "errors" + "github.com/gbrlsnchs/jwt/v3/internal" "golang.org/x/crypto/ed25519" ) var ( // ErrEd25519PrivKey is the error for trying to sign a JWT with a nil private key. - ErrEd25519PrivKey = internal.NewError("jwt: Ed25519 private key is nil") + ErrEd25519PrivKey = errors.New("jwt: Ed25519 private key is nil") // ErrEd25519PubKey is the error for trying to verify a JWT with a nil public key. - ErrEd25519PubKey = internal.NewError("jwt: Ed25519 public key is nil") + ErrEd25519PubKey = errors.New("jwt: Ed25519 public key is nil") // ErrEd25519Verification is the error for when verification with edDSA fails. - ErrEd25519Verification = internal.NewError("jwt: Ed25519 verification failed") + ErrEd25519Verification = errors.New("jwt: Ed25519 verification failed") _ Algorithm = new(edDSA) ) diff --git a/go.mod b/go.mod index 6798f2d..f93a627 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,5 @@ module github.com/gbrlsnchs/jwt/v3 -require ( - golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 - golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 -) +require golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 -go 1.11 +go 1.10 diff --git a/go.sum b/go.sum index 3811b06..ab4e508 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,2 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67 h1:ng3VDlRp5/DHpSWl02R4rM9I+8M2rhmsuLwAMmkLQWE= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373 h1:PPwnA7z1Pjf7XYaBP9GL1VAMZmcIWyFz7QCMSIIa3Bg= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/decode_test.go b/internal/decode_test.go index 1db91ef..6f172d7 100644 --- a/internal/decode_test.go +++ b/internal/decode_test.go @@ -26,7 +26,7 @@ func TestDecode(t *testing.T) { {rawURLEnc, "{}", "", false}, {rawURLEnc, `{"x":"test"}`, "test", false}, {stdEnc, "{}", "", true}, - {stdEnc, `{"x":"test"}`, "test", false}, + {stdEnc, `{"x":"test"}`, "test", false}, // the output is the same as with RawURLEncoding {nil, "{}", "", true}, {nil, `{"x":"test"}`, "", true}, } @@ -38,11 +38,10 @@ func TestDecode(t *testing.T) { } t.Logf("b64: %s", b64) var ( - dt decodeTest - err = internal.Decode([]byte(b64), &dt) - b64err = new(base64.CorruptInputError) + dt decodeTest + err = internal.Decode([]byte(b64), &dt) ) - if want, got := tc.errors, internal.ErrorAs(err, b64err); want != got { + if want, got := tc.errors, err != nil; want != got { t.Fatalf("want %t, got %t: %v", want, got, err) } if want, got := tc.expected, dt.X; want != got { diff --git a/internal/error_go1_12.go b/internal/error_go1_12.go deleted file mode 100644 index 0d25232..0000000 --- a/internal/error_go1_12.go +++ /dev/null @@ -1,17 +0,0 @@ -// +build !go1.13 - -package internal - -import "golang.org/x/xerrors" - -// Errorf is a wrapper for xerrors.Errorf. -func Errorf(format string, a ...interface{}) error { return xerrors.Errorf(format, a...) } - -// ErrorAs is a wrapper for xerrors.As. -func ErrorAs(err error, target interface{}) bool { return xerrors.As(err, target) } - -// ErrorIs is a wrapper for xerrors.Is. -func ErrorIs(err, target error) bool { return xerrors.Is(err, target) } - -// NewError is a wrapper for xerrors.New. -func NewError(text string) error { return xerrors.New(text) } diff --git a/raw_token.go b/raw_token.go index 0f40dba..e393350 100644 --- a/raw_token.go +++ b/raw_token.go @@ -1,9 +1,13 @@ package jwt -import "github.com/gbrlsnchs/jwt/v3/internal" +import ( + "errors" + + "github.com/gbrlsnchs/jwt/v3/internal" +) // ErrMalformed indicates a token doesn't have a valid format, as per the RFC 7519. -var ErrMalformed = internal.NewError("jwt: malformed token") +var ErrMalformed = errors.New("jwt: malformed token") // RawToken is a representation of a parsed JWT string. type RawToken struct { diff --git a/sign_test.go b/sign_test.go index 14f647b..1c00ffe 100644 --- a/sign_test.go +++ b/sign_test.go @@ -6,7 +6,6 @@ import ( "time" "github.com/gbrlsnchs/jwt/v3" - "github.com/gbrlsnchs/jwt/v3/internal" ) type testPayload struct { @@ -349,7 +348,7 @@ func TestSign(t *testing.T) { for _, tc := range v { t.Run(tc.alg.Name(), func(t *testing.T) { token, err := jwt.Sign(tc.payload, tc.alg) - if want, got := tc.signErr, err; !internal.ErrorIs(got, want) { + if want, got := tc.signErr, err; got != want { t.Fatalf("want %v, got %v", want, got) } if err != nil { @@ -361,7 +360,7 @@ func TestSign(t *testing.T) { payload testPayload ) hd, err = jwt.Verify(token, &payload, tc.verifyAlg) - if want, got := tc.verifyErr, err; !internal.ErrorIs(got, want) { + if want, got := tc.verifyErr, err; got != want { t.Fatalf("want %v, got %v", want, got) } if want, got := tc.wantHeader, hd; !reflect.DeepEqual(got, want) { diff --git a/verify.go b/verify.go index 30835fb..ab02a0d 100644 --- a/verify.go +++ b/verify.go @@ -2,12 +2,11 @@ package jwt import ( "bytes" - - "github.com/gbrlsnchs/jwt/v3/internal" + "errors" ) // ErrAlgValidation indicates an incoming JWT's "alg" field mismatches the Validator's. -var ErrAlgValidation = internal.NewError(`"alg" field mismatch`) +var ErrAlgValidation = errors.New(`"alg" field mismatch`) // VerifyOption is a functional option for verifying. type VerifyOption func(*RawToken) error @@ -55,7 +54,7 @@ func Verify(token []byte, payload interface{}, alg Algorithm, opts ...VerifyOpti // in the JOSE header is the same used by the algorithm. func ValidateHeader(rt *RawToken) error { if rt.alg.Name() != rt.hd.Algorithm { - return internal.Errorf("jwt: unexpected algorithm %q: %w", rt.hd.Algorithm, ErrAlgValidation) + return ErrAlgValidation } return nil } From e76d1b6cc68086add5b85a583595633a39b8e8d8 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:34:10 -0300 Subject: [PATCH 86/99] README: add badge and instructions for vgo --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index c82db47..6b1cceb 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ ## Compatibility [![Version Compatibility](https://img.shields.io/badge/go%20modules-go1.11+-5272b4.svg)](https://github.com/gbrlsnchs/jwt#installing) +[![vgo](https://img.shields.io/badge/vgo-go1.10-5272b4.svg)](https://github.com/gbrlsnchs/jwt#installing) [![go get](https://img.shields.io/badge/go%20get-go1.9.7+,%20go1.10.3+%20and%20go1.11-5272b4.svg)](https://github.com/gbrlsnchs/jwt#installing) ## About @@ -54,6 +55,16 @@ $ GO111MODULE=on go get -u github.com/gbrlsnchs/jwt/v3

+
Go 1.10 with vgo +

+ +```sh +$ vgo get -u github.com/gbrlsnchs/jwt/v3 +``` + +

+
+
Go 1.9.7+, Go 1.10.3+ (without vgo) and Go 1.11 (when GO111MODULE=off)

From 4b56c1d55972bc95ef198bd36ba79ef6d347995f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:45:44 -0300 Subject: [PATCH 87/99] README: update examples --- README.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6b1cceb..fcc17ed 100644 --- a/README.md +++ b/README.md @@ -119,7 +119,7 @@ func main() { } ``` -### Verifying (and decoding) +### Verifying ```go import "github.com/gbrlsnchs/jwt/v3" @@ -135,7 +135,8 @@ func main() { // ... var pl CustomPayload - if err := jwt.Verify(token, hs, jwt.DecodePayload(&pl)); err != nil { + hd, err := jwt.Verify(token, hs, &pl) + if err != nil { // ... } @@ -143,10 +144,11 @@ func main() { } ``` -### Other examples +### Other use case examples

Setting "cty" and "kid" claims

+The "cty" and "kid" claims can be set by passing options to the `jwt.Sign` function: ```go import ( "time" @@ -178,7 +180,7 @@ func main() {

Validating "alg" before verifying

-#### Without decoding the header +For validating the "alg" field in a JOSE header **before** verification, the `jwt.ValidateHeader` option must be passed to `jwt.Verify`. ```go import "github.com/gbrlsnchs/jwt/v3" @@ -187,7 +189,8 @@ var hs = jwt.NewHS256([]byte("secret")) func main() { // ... - if err := jwt.Verify(token, hs, jwt.ValidateHeader); err != nil { + var pl jwt.Payload + if _, err := jwt.Verify(token, hs, &pl, jwt.ValidateHeader); err != nil { // ... } @@ -195,7 +198,9 @@ func main() { } ``` -#### Decoding the header +

+
+ ```go import "github.com/gbrlsnchs/jwt/v3" From a6c2004bef27b7a4da2b4a37c307a9931e5721c1 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 17:46:00 -0300 Subject: [PATCH 88/99] Update verification benchmarks --- bench_test.go | 50 +------------------------------------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/bench_test.go b/bench_test.go index 7059b8b..e0c6314 100644 --- a/bench_test.go +++ b/bench_test.go @@ -69,58 +69,10 @@ func BenchmarkVerify(b *testing.B) { err error ) b.Run("Default", func(b *testing.B) { - b.ReportAllocs() - for n := 0; n < b.N; n++ { - if err = jwt.Verify(token, benchHS256); err != nil { - b.Fatal(err) - } - } - }) - - b.Run("ValidateHeader", func(b *testing.B) { - b.ReportAllocs() - for n := 0; n < b.N; n++ { - if err = jwt.Verify(token, benchHS256, jwt.ValidateHeader); err != nil { - b.Fatal(err) - } - } - }) - - b.Run("DecodePayload", func(b *testing.B) { - b.ReportAllocs() - for n := 0; n < b.N; n++ { - var pl jwt.Payload - if err = jwt.Verify(token, benchHS256, jwt.DecodePayload(&pl)); err != nil { - b.Fatal(err) - } - } - }) - b.Run("Full decode", func(b *testing.B) { - b.ReportAllocs() - for n := 0; n < b.N; n++ { - var ( - hd jwt.Header - pl jwt.Payload - ) - if err = jwt.Verify(token, benchHS256, - jwt.DecodeHeader(&hd, false), jwt.DecodePayload(&pl)); err != nil { - b.Fatal(err) - } - } - }) - - b.Run("Payload validator", func(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { var pl jwt.Payload - audValidator := pl.AudienceValidator(jwt.Audience{ - "https://golang.org", - "https://jwt.io", - "https://google.com", - "https://reddit.com", - }) - if err = jwt.Verify(token, benchHS256, - jwt.DecodePayload(&pl, audValidator)); err != nil { + if _, err = jwt.Verify(token, &pl, benchHS256); err != nil { b.Fatal(err) } } From 6f90d39c6437e2dc70a26cc9aff696635d0704e1 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 18:08:26 -0300 Subject: [PATCH 89/99] Fix Verify signature --- bench_test.go | 2 +- sign_test.go | 25 +------------------------ verify.go | 2 +- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/bench_test.go b/bench_test.go index e0c6314..572d643 100644 --- a/bench_test.go +++ b/bench_test.go @@ -72,7 +72,7 @@ func BenchmarkVerify(b *testing.B) { b.ReportAllocs() for n := 0; n < b.N; n++ { var pl jwt.Payload - if _, err = jwt.Verify(token, &pl, benchHS256); err != nil { + if _, err = jwt.Verify(token, benchHS256, &pl); err != nil { b.Fatal(err) } } diff --git a/sign_test.go b/sign_test.go index 1c00ffe..2ef3eb5 100644 --- a/sign_test.go +++ b/sign_test.go @@ -34,7 +34,6 @@ var ( func TestSign(t *testing.T) { type testCase struct { alg jwt.Algorithm - hd jwt.Header payload interface{} verifyAlg jwt.Algorithm @@ -49,7 +48,6 @@ func TestSign(t *testing.T) { "HMAC": []testCase{ { alg: jwt.NewHS256(hmacKey1), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewHS256(hmacKey1), wantHeader: jwt.Header{ @@ -62,7 +60,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewHS384(hmacKey1), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewHS384(hmacKey1), wantHeader: jwt.Header{ @@ -75,7 +72,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewHS512(hmacKey1), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewHS512(hmacKey1), wantHeader: jwt.Header{ @@ -90,7 +86,6 @@ func TestSign(t *testing.T) { "RSA": []testCase{ { alg: jwt.NewRS256(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), wantHeader: jwt.Header{ @@ -103,7 +98,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewRS256(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewRS256(nil, rsaPublicKey1), wantHeader: jwt.Header{ @@ -116,7 +110,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewRS384(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), wantHeader: jwt.Header{ @@ -129,7 +122,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewRS384(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewRS384(nil, rsaPublicKey1), wantHeader: jwt.Header{ @@ -142,7 +134,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewRS512(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewRS512(rsaPrivateKey1, nil), wantHeader: jwt.Header{ @@ -155,7 +146,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewRS512(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewRS512(nil, rsaPublicKey1), wantHeader: jwt.Header{ @@ -170,7 +160,6 @@ func TestSign(t *testing.T) { "RSA-PSS": []testCase{ { alg: jwt.NewPS256(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewPS256(rsaPrivateKey1, nil), wantHeader: jwt.Header{ @@ -183,7 +172,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewPS256(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewPS256(nil, rsaPublicKey1), wantHeader: jwt.Header{ @@ -196,7 +184,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewPS384(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), wantHeader: jwt.Header{ @@ -209,7 +196,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewPS384(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewPS384(nil, rsaPublicKey1), wantHeader: jwt.Header{ @@ -222,7 +208,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewPS512(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewPS512(rsaPrivateKey1, nil), wantHeader: jwt.Header{ @@ -235,7 +220,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewPS512(rsaPrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewPS512(nil, rsaPublicKey1), wantHeader: jwt.Header{ @@ -250,7 +234,6 @@ func TestSign(t *testing.T) { "ECDSA": []testCase{ { alg: jwt.NewES256(es256PrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewES256(nil, es256PublicKey1), wantHeader: jwt.Header{ @@ -263,7 +246,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewES256(es256PrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewES256(es256PrivateKey1, nil), wantHeader: jwt.Header{ @@ -276,7 +258,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewES384(es384PrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewES384(nil, es384PublicKey1), wantHeader: jwt.Header{ @@ -289,7 +270,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewES384(es384PrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewES384(es384PrivateKey1, nil), wantHeader: jwt.Header{ @@ -302,7 +282,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewES512(es512PrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewES512(nil, es512PublicKey1), wantHeader: jwt.Header{ @@ -315,7 +294,6 @@ func TestSign(t *testing.T) { }, { alg: jwt.NewES512(es512PrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewES512(es512PrivateKey1, nil), wantHeader: jwt.Header{ @@ -330,7 +308,6 @@ func TestSign(t *testing.T) { "Ed25519": []testCase{ { alg: jwt.NewEd25519(ed25519PrivateKey1, nil), - hd: jwt.Header{}, payload: tp, verifyAlg: jwt.NewEd25519(ed25519PrivateKey1, nil), wantHeader: jwt.Header{ @@ -359,7 +336,7 @@ func TestSign(t *testing.T) { hd jwt.Header payload testPayload ) - hd, err = jwt.Verify(token, &payload, tc.verifyAlg) + hd, err = jwt.Verify(token, tc.verifyAlg, &payload) if want, got := tc.verifyErr, err; got != want { t.Fatalf("want %v, got %v", want, got) } diff --git a/verify.go b/verify.go index ab02a0d..ecbd9a8 100644 --- a/verify.go +++ b/verify.go @@ -13,7 +13,7 @@ type VerifyOption func(*RawToken) error // Verify verifies a token's signature using alg. Before verification, opts is iterated and // each option in it is run. -func Verify(token []byte, payload interface{}, alg Algorithm, opts ...VerifyOption) (Header, error) { +func Verify(token []byte, alg Algorithm, payload interface{}, opts ...VerifyOption) (Header, error) { rt := &RawToken{ alg: alg, } From 70faa8b10a9477fdbcd8456e2f6c85c70bb26466 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 18:15:06 -0300 Subject: [PATCH 90/99] verify: add tests :tada: --- verify_test.go | 322 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 322 insertions(+) create mode 100644 verify_test.go diff --git a/verify_test.go b/verify_test.go new file mode 100644 index 0000000..3eea16a --- /dev/null +++ b/verify_test.go @@ -0,0 +1,322 @@ +package jwt_test + +import ( + "reflect" + "testing" + + "github.com/gbrlsnchs/jwt/v3" +) + +func TestVerify(t *testing.T) { + type testCase struct { + alg jwt.Algorithm + payload interface{} + + verifyAlg jwt.Algorithm + opts []func(*jwt.RawToken) + wantHeader jwt.Header + wantPayload testPayload + + signErr error + verifyErr error + } + testCases := map[string][]testCase{ + "HMAC": []testCase{ + { + alg: jwt.NewHS256(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS256(hmacKey1), + wantHeader: jwt.Header{ + Algorithm: "HS256", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewHS384(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS384(hmacKey1), + wantHeader: jwt.Header{ + Algorithm: "HS384", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewHS512(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS512(hmacKey1), + wantHeader: jwt.Header{ + Algorithm: "HS512", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + }, + "RSA": []testCase{ + { + alg: jwt.NewRS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "RS256", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewRS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(nil, rsaPublicKey1), + wantHeader: jwt.Header{ + Algorithm: "RS256", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewRS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "RS384", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewRS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(nil, rsaPublicKey1), + wantHeader: jwt.Header{ + Algorithm: "RS384", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewRS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS512(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "RS512", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewRS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS512(nil, rsaPublicKey1), + wantHeader: jwt.Header{ + Algorithm: "RS512", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + }, + "RSA-PSS": []testCase{ + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS256(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS256(nil, rsaPublicKey1), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(nil, rsaPublicKey1), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS512(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS512(nil, rsaPublicKey1), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + }, + "ECDSA": []testCase{ + { + alg: jwt.NewES256(es256PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES256(nil, es256PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES256(es256PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES256(es256PrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(nil, es384PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(es384PrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES512(nil, es512PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES512(es512PrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + }, + "Ed25519": []testCase{ + { + alg: jwt.NewEd25519(ed25519PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewEd25519(ed25519PrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "Ed25519", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + }, + } + for k, v := range testCases { + t.Run(k, func(t *testing.T) { + for _, tc := range v { + t.Run(tc.verifyAlg.Name(), func(t *testing.T) { + token, err := jwt.Sign(tc.payload, tc.alg) + if err != nil { + t.Fatal(err) + } + var pl testPayload + hd, err := jwt.Verify(token, tc.verifyAlg, &pl) + if want, got := tc.verifyErr, err; got != want { + t.Errorf("want %v, got %v", want, got) + } + if want, got := tc.wantHeader, hd; !reflect.DeepEqual(got, want) { + t.Errorf("want %#+v, got %#+v", want, got) + } + if want, got := tc.wantPayload, pl; !reflect.DeepEqual(got, want) { + t.Errorf("want %#+v, got %#+v", want, got) + } + }) + } + }) + } +} From 8c06269551d0fc9edf9603573a29fa23ef400937 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 19:09:38 -0300 Subject: [PATCH 91/99] rsa_sha: return custom error on verification error --- rsa_sha.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/rsa_sha.go b/rsa_sha.go index d20fae6..4bc0944 100644 --- a/rsa_sha.go +++ b/rsa_sha.go @@ -14,6 +14,8 @@ var ( ErrRSANilPrivKey = errors.New("jwt: RSA private key is nil") // ErrRSANilPubKey is the error for trying to verify a JWT with a nil public key. ErrRSANilPubKey = errors.New("jwt: RSA public key is nil") + // ErrRSAVerification is the error for an invalid RSA signature. + ErrRSAVerification = errors.New("jwt: RSA verification failed") _ Algorithm = new(rsaSHA) ) @@ -117,7 +119,12 @@ func (rs *rsaSHA) Verify(headerPayload, sig []byte) (err error) { return err } if rs.opts != nil { - return rsa.VerifyPSS(rs.pub, rs.sha, sum, sig, rs.opts) + err = rsa.VerifyPSS(rs.pub, rs.sha, sum, sig, rs.opts) + } else { + err = rsa.VerifyPKCS1v15(rs.pub, rs.sha, sum, sig) } - return rsa.VerifyPKCS1v15(rs.pub, rs.sha, sum, sig) + if err != nil { + return ErrRSAVerification + } + return nil } From 19adabd1a8c9c36e3d8f338c43c92918776b9b50 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 19:10:14 -0300 Subject: [PATCH 92/99] ecdsa_sha: update documentation --- ecdsa_sha.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ecdsa_sha.go b/ecdsa_sha.go index 0699b3f..1e12db3 100644 --- a/ecdsa_sha.go +++ b/ecdsa_sha.go @@ -15,7 +15,7 @@ var ( ErrECDSANilPrivKey = errors.New("jwt: ECDSA private key is nil") // ErrECDSANilPubKey is the error for trying to verify a JWT with a nil public key. ErrECDSANilPubKey = errors.New("jwt: ECDSA public key is nil") - // ErrECDSAVerification is the error for an invalid signature. + // ErrECDSAVerification is the error for an invalid ECDSA signature. ErrECDSAVerification = errors.New("jwt: ECDSA verification failed") _ Algorithm = new(ecdsaSHA) From 5defd5822983c0c683f48eec711aed311be0fb59 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 19:20:32 -0300 Subject: [PATCH 93/99] sign: add failure test cases --- sign_test.go | 444 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 444 insertions(+) diff --git a/sign_test.go b/sign_test.go index 2ef3eb5..da4a4b1 100644 --- a/sign_test.go +++ b/sign_test.go @@ -58,6 +58,30 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewHS256(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS256(hmacKey2), + wantHeader: jwt.Header{ + Algorithm: "HS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, + { + alg: jwt.NewHS256(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS384(hmacKey1), + wantHeader: jwt.Header{ + Algorithm: "HS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, { alg: jwt.NewHS384(hmacKey1), payload: tp, @@ -70,6 +94,30 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewHS384(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS384(hmacKey2), + wantHeader: jwt.Header{ + Algorithm: "HS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, + { + alg: jwt.NewHS384(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS256(hmacKey1), + wantHeader: jwt.Header{ + Algorithm: "HS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, { alg: jwt.NewHS512(hmacKey1), payload: tp, @@ -82,6 +130,30 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewHS512(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS512(hmacKey2), + wantHeader: jwt.Header{ + Algorithm: "HS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, + { + alg: jwt.NewHS512(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS256(hmacKey1), + wantHeader: jwt.Header{ + Algorithm: "HS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, }, "RSA": []testCase{ { @@ -96,6 +168,30 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "RS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewRS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "RS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS256(rsaPrivateKey1, nil), payload: tp, @@ -108,6 +204,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "RS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS384(rsaPrivateKey1, nil), payload: tp, @@ -120,6 +228,30 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "RS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewRS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "RS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS384(rsaPrivateKey1, nil), payload: tp, @@ -132,6 +264,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "RS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS512(rsaPrivateKey1, nil), payload: tp, @@ -144,6 +288,30 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "RS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewRS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS512(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "RS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS512(rsaPrivateKey1, nil), payload: tp, @@ -156,6 +324,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS512(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "RS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, }, "RSA-PSS": []testCase{ { @@ -170,6 +350,42 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS256(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS256(rsaPrivateKey1, nil), payload: tp, @@ -182,6 +398,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS256(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS384(rsaPrivateKey1, nil), payload: tp, @@ -194,6 +422,42 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS256(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS384(rsaPrivateKey1, nil), payload: tp, @@ -206,6 +470,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS512(rsaPrivateKey1, nil), payload: tp, @@ -218,6 +494,42 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS512(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS512(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS512(rsaPrivateKey1, nil), payload: tp, @@ -230,6 +542,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS512(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, }, "ECDSA": []testCase{ { @@ -244,6 +568,30 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES256(es256PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(nil, es256PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, + { + alg: jwt.NewES256(es256PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES256(nil, es256PublicKey2), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES256(es256PrivateKey1, nil), payload: tp, @@ -256,6 +604,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES256(es256PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES256(es256PrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES384(es384PrivateKey1, nil), payload: tp, @@ -268,6 +628,30 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES256(nil, es384PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(nil, es384PublicKey2), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES384(es384PrivateKey1, nil), payload: tp, @@ -280,6 +664,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(es384PrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES512(es512PrivateKey1, nil), payload: tp, @@ -292,6 +688,30 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(nil, es512PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES512(nil, es512PublicKey2), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES512(es512PrivateKey1, nil), payload: tp, @@ -304,6 +724,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES512(es512PrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, }, "Ed25519": []testCase{ { @@ -318,6 +750,18 @@ func TestSign(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewEd25519(ed25519PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewEd25519(ed25519PrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "Ed25519", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrEd25519Verification, + }, }, } for k, v := range testCases { From 4c9d099424c94b8ce8aea2357d9f7c6e2f1ca847 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 19:22:16 -0300 Subject: [PATCH 94/99] verify: copy test cases from sign_test.go --- verify_test.go | 444 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 444 insertions(+) diff --git a/verify_test.go b/verify_test.go index 3eea16a..fe8277c 100644 --- a/verify_test.go +++ b/verify_test.go @@ -34,6 +34,30 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewHS256(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS256(hmacKey2), + wantHeader: jwt.Header{ + Algorithm: "HS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, + { + alg: jwt.NewHS256(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS384(hmacKey1), + wantHeader: jwt.Header{ + Algorithm: "HS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, { alg: jwt.NewHS384(hmacKey1), payload: tp, @@ -46,6 +70,30 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewHS384(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS384(hmacKey2), + wantHeader: jwt.Header{ + Algorithm: "HS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, + { + alg: jwt.NewHS384(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS256(hmacKey1), + wantHeader: jwt.Header{ + Algorithm: "HS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, { alg: jwt.NewHS512(hmacKey1), payload: tp, @@ -58,6 +106,30 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewHS512(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS512(hmacKey2), + wantHeader: jwt.Header{ + Algorithm: "HS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, + { + alg: jwt.NewHS512(hmacKey1), + payload: tp, + verifyAlg: jwt.NewHS256(hmacKey1), + wantHeader: jwt.Header{ + Algorithm: "HS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrHMACVerification, + }, }, "RSA": []testCase{ { @@ -72,6 +144,30 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "RS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewRS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "RS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS256(rsaPrivateKey1, nil), payload: tp, @@ -84,6 +180,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "RS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS384(rsaPrivateKey1, nil), payload: tp, @@ -96,6 +204,30 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "RS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewRS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "RS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS384(rsaPrivateKey1, nil), payload: tp, @@ -108,6 +240,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "RS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS512(rsaPrivateKey1, nil), payload: tp, @@ -120,6 +264,30 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "RS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewRS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS512(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "RS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewRS512(rsaPrivateKey1, nil), payload: tp, @@ -132,6 +300,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewRS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS512(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "RS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, }, "RSA-PSS": []testCase{ { @@ -146,6 +326,42 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS256(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS256(rsaPrivateKey1, nil), payload: tp, @@ -158,6 +374,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS256(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS256(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "PS256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS384(rsaPrivateKey1, nil), payload: tp, @@ -170,6 +398,42 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS256(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS384(rsaPrivateKey1, nil), payload: tp, @@ -182,6 +446,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS384(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "PS384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS512(rsaPrivateKey1, nil), payload: tp, @@ -194,6 +470,42 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewRS512(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS512(rsaPrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, { alg: jwt.NewPS512(rsaPrivateKey1, nil), payload: tp, @@ -206,6 +518,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewPS512(rsaPrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewPS512(nil, rsaPublicKey2), + wantHeader: jwt.Header{ + Algorithm: "PS512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrRSAVerification, + }, }, "ECDSA": []testCase{ { @@ -220,6 +544,30 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES256(es256PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(nil, es256PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, + { + alg: jwt.NewES256(es256PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES256(nil, es256PublicKey2), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES256(es256PrivateKey1, nil), payload: tp, @@ -232,6 +580,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES256(es256PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES256(es256PrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "ES256", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES384(es384PrivateKey1, nil), payload: tp, @@ -244,6 +604,30 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES256(nil, es384PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(nil, es384PublicKey2), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES384(es384PrivateKey1, nil), payload: tp, @@ -256,6 +640,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES384(es384PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(es384PrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "ES384", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES512(es512PrivateKey1, nil), payload: tp, @@ -268,6 +664,30 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES384(nil, es512PublicKey1), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES512(nil, es512PublicKey2), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, { alg: jwt.NewES512(es512PrivateKey1, nil), payload: tp, @@ -280,6 +700,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewES512(es512PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewES512(es512PrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "ES512", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrECDSAVerification, + }, }, "Ed25519": []testCase{ { @@ -294,6 +726,18 @@ func TestVerify(t *testing.T) { signErr: nil, verifyErr: nil, }, + { + alg: jwt.NewEd25519(ed25519PrivateKey1, nil), + payload: tp, + verifyAlg: jwt.NewEd25519(ed25519PrivateKey2, nil), + wantHeader: jwt.Header{ + Algorithm: "Ed25519", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrEd25519Verification, + }, }, } for k, v := range testCases { From 8e34148e1c03516e26184bdf3a0e282071f5578a Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 19:47:50 -0300 Subject: [PATCH 95/99] rsa_sha: use functional options to set keys --- rsa_sha.go | 70 ++++++++++++++++---------- sign_test.go | 132 ++++++++++++++++++++++++------------------------- verify_test.go | 132 ++++++++++++++++++++++++------------------------- 3 files changed, 175 insertions(+), 159 deletions(-) diff --git a/rsa_sha.go b/rsa_sha.go index 4bc0944..792fd34 100644 --- a/rsa_sha.go +++ b/rsa_sha.go @@ -17,10 +17,25 @@ var ( // ErrRSAVerification is the error for an invalid RSA signature. ErrRSAVerification = errors.New("jwt: RSA verification failed") - _ Algorithm = new(rsaSHA) + _ Algorithm = new(RSASHA) ) -type rsaSHA struct { +// RSAPrivateKey is an option to set a private key to the RSA-SHA algorithm. +func RSAPrivateKey(priv *rsa.PrivateKey) func(*RSASHA) { + return func(rs *RSASHA) { + rs.priv = priv + } +} + +// RSAPublicKey is an option to set a public key to the RSA-SHA algorithm. +func RSAPublicKey(pub *rsa.PublicKey) func(*RSASHA) { + return func(rs *RSASHA) { + rs.pub = pub + } +} + +// RSASHA is an algorithm that uses RSA to sign SHA hashes. +type RSASHA struct { name string priv *rsa.PrivateKey pub *rsa.PublicKey @@ -30,64 +45,65 @@ type rsaSHA struct { opts *rsa.PSSOptions } -func newRSASHA(name string, priv *rsa.PrivateKey, pub *rsa.PublicKey, sha crypto.Hash, pss bool) *rsaSHA { - if pub == nil { - pub = &priv.PublicKey - } - rs := &rsaSHA{ +func newRSASHA(name string, opts []func(*RSASHA), sha crypto.Hash, pss bool) *RSASHA { + rs := RSASHA{ name: name, // cache name - priv: priv, - pub: pub, sha: sha, - size: internal.RSASignatureSize(pub), // cache size pool: newHashPool(sha.New), } + for _, opt := range opts { + opt(&rs) + } + if rs.pub == nil { + rs.pub = &rs.priv.PublicKey + } + rs.size = internal.RSASignatureSize(rs.pub) // cache size if pss { rs.opts = &rsa.PSSOptions{ SaltLength: rsa.PSSSaltLengthAuto, Hash: sha, } } - return rs + return &rs } // NewRS256 creates a new algorithm using RSA and SHA-256. -func NewRS256(priv *rsa.PrivateKey, pub *rsa.PublicKey) Algorithm { - return newRSASHA("RS256", priv, pub, crypto.SHA256, false) +func NewRS256(opts ...func(*RSASHA)) *RSASHA { + return newRSASHA("RS256", opts, crypto.SHA256, false) } // NewRS384 creates a new algorithm using RSA and SHA-384. -func NewRS384(priv *rsa.PrivateKey, pub *rsa.PublicKey) Algorithm { - return newRSASHA("RS384", priv, pub, crypto.SHA384, false) +func NewRS384(opts ...func(*RSASHA)) *RSASHA { + return newRSASHA("RS384", opts, crypto.SHA384, false) } // NewRS512 creates a new algorithm using RSA and SHA-512. -func NewRS512(priv *rsa.PrivateKey, pub *rsa.PublicKey) Algorithm { - return newRSASHA("RS512", priv, pub, crypto.SHA512, false) +func NewRS512(opts ...func(*RSASHA)) *RSASHA { + return newRSASHA("RS512", opts, crypto.SHA512, false) } // NewPS256 creates a new algorithm using RSA-PSS and SHA-256. -func NewPS256(priv *rsa.PrivateKey, pub *rsa.PublicKey) Algorithm { - return newRSASHA("PS256", priv, pub, crypto.SHA256, true) +func NewPS256(opts ...func(*RSASHA)) *RSASHA { + return newRSASHA("PS256", opts, crypto.SHA256, true) } // NewPS384 creates a new algorithm using RSA-PSS and SHA-384. -func NewPS384(priv *rsa.PrivateKey, pub *rsa.PublicKey) Algorithm { - return newRSASHA("PS384", priv, pub, crypto.SHA384, true) +func NewPS384(opts ...func(*RSASHA)) *RSASHA { + return newRSASHA("PS384", opts, crypto.SHA384, true) } // NewPS512 creates a new algorithm using RSA-PSS and SHA-512. -func NewPS512(priv *rsa.PrivateKey, pub *rsa.PublicKey) Algorithm { - return newRSASHA("PS512", priv, pub, crypto.SHA512, true) +func NewPS512(opts ...func(*RSASHA)) *RSASHA { + return newRSASHA("PS512", opts, crypto.SHA512, true) } // Name returns the algorithm's name. -func (rs *rsaSHA) Name() string { +func (rs *RSASHA) Name() string { return rs.name } // Sign signs headerPayload using either RSA-SHA or RSA-PSS-SHA algorithms. -func (rs *rsaSHA) Sign(headerPayload []byte) ([]byte, error) { +func (rs *RSASHA) Sign(headerPayload []byte) ([]byte, error) { if rs.priv == nil { return nil, ErrRSANilPrivKey } @@ -102,12 +118,12 @@ func (rs *rsaSHA) Sign(headerPayload []byte) ([]byte, error) { } // Size returns the signature's byte size. -func (rs *rsaSHA) Size() int { +func (rs *RSASHA) Size() int { return rs.size } // Verify verifies a signature based on headerPayload using either RSA-SHA or RSA-PSS-SHA. -func (rs *rsaSHA) Verify(headerPayload, sig []byte) (err error) { +func (rs *RSASHA) Verify(headerPayload, sig []byte) (err error) { if rs.pub == nil { return ErrRSANilPubKey } diff --git a/sign_test.go b/sign_test.go index da4a4b1..a3bd45c 100644 --- a/sign_test.go +++ b/sign_test.go @@ -157,9 +157,9 @@ func TestSign(t *testing.T) { }, "RSA": []testCase{ { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -169,9 +169,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -181,9 +181,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(rsaPrivateKey2, nil), + verifyAlg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -193,9 +193,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(nil, rsaPublicKey1), + verifyAlg: jwt.NewRS256(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -205,9 +205,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(nil, rsaPublicKey2), + verifyAlg: jwt.NewRS256(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -217,9 +217,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -229,9 +229,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -241,9 +241,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey2, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -253,9 +253,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(nil, rsaPublicKey1), + verifyAlg: jwt.NewRS384(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -265,9 +265,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(nil, rsaPublicKey2), + verifyAlg: jwt.NewRS384(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -277,9 +277,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -289,9 +289,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -301,9 +301,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(rsaPrivateKey2, nil), + verifyAlg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -313,9 +313,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(nil, rsaPublicKey1), + verifyAlg: jwt.NewRS512(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -325,9 +325,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(nil, rsaPublicKey2), + verifyAlg: jwt.NewRS512(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -339,9 +339,9 @@ func TestSign(t *testing.T) { }, "RSA-PSS": []testCase{ { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -351,9 +351,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -363,9 +363,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -375,9 +375,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(rsaPrivateKey2, nil), + verifyAlg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -387,9 +387,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(nil, rsaPublicKey1), + verifyAlg: jwt.NewPS256(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -399,9 +399,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(nil, rsaPublicKey2), + verifyAlg: jwt.NewPS256(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -411,9 +411,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -423,9 +423,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -435,9 +435,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -447,9 +447,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(rsaPrivateKey2, nil), + verifyAlg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -459,9 +459,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(nil, rsaPublicKey1), + verifyAlg: jwt.NewPS384(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -471,9 +471,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(nil, rsaPublicKey2), + verifyAlg: jwt.NewPS384(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -483,9 +483,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS512(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -495,9 +495,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -507,9 +507,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -519,9 +519,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS512(rsaPrivateKey2, nil), + verifyAlg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -531,9 +531,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS512(nil, rsaPublicKey1), + verifyAlg: jwt.NewPS512(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -543,9 +543,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS512(nil, rsaPublicKey2), + verifyAlg: jwt.NewPS512(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", diff --git a/verify_test.go b/verify_test.go index fe8277c..5b1908a 100644 --- a/verify_test.go +++ b/verify_test.go @@ -133,9 +133,9 @@ func TestVerify(t *testing.T) { }, "RSA": []testCase{ { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -145,9 +145,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -157,9 +157,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(rsaPrivateKey2, nil), + verifyAlg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -169,9 +169,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(nil, rsaPublicKey1), + verifyAlg: jwt.NewRS256(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -181,9 +181,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS256(rsaPrivateKey1, nil), + alg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(nil, rsaPublicKey2), + verifyAlg: jwt.NewRS256(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "RS256", Type: "JWT", @@ -193,9 +193,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -205,9 +205,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -217,9 +217,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey2, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -229,9 +229,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(nil, rsaPublicKey1), + verifyAlg: jwt.NewRS384(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -241,9 +241,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS384(rsaPrivateKey1, nil), + alg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(nil, rsaPublicKey2), + verifyAlg: jwt.NewRS384(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "RS384", Type: "JWT", @@ -253,9 +253,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -265,9 +265,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -277,9 +277,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(rsaPrivateKey2, nil), + verifyAlg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -289,9 +289,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(nil, rsaPublicKey1), + verifyAlg: jwt.NewRS512(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -301,9 +301,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewRS512(rsaPrivateKey1, nil), + alg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(nil, rsaPublicKey2), + verifyAlg: jwt.NewRS512(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "RS512", Type: "JWT", @@ -315,9 +315,9 @@ func TestVerify(t *testing.T) { }, "RSA-PSS": []testCase{ { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -327,9 +327,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -339,9 +339,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -351,9 +351,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(rsaPrivateKey2, nil), + verifyAlg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -363,9 +363,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(nil, rsaPublicKey1), + verifyAlg: jwt.NewPS256(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -375,9 +375,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS256(rsaPrivateKey1, nil), + alg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(nil, rsaPublicKey2), + verifyAlg: jwt.NewPS256(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "PS256", Type: "JWT", @@ -387,9 +387,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -399,9 +399,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -411,9 +411,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS256(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS256(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -423,9 +423,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(rsaPrivateKey2, nil), + verifyAlg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -435,9 +435,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(nil, rsaPublicKey1), + verifyAlg: jwt.NewPS384(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -447,9 +447,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS384(rsaPrivateKey1, nil), + alg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(nil, rsaPublicKey2), + verifyAlg: jwt.NewPS384(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "PS384", Type: "JWT", @@ -459,9 +459,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS512(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -471,9 +471,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewRS512(rsaPrivateKey1, nil), + verifyAlg: jwt.NewRS512(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -483,9 +483,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS384(rsaPrivateKey1, nil), + verifyAlg: jwt.NewPS384(jwt.RSAPrivateKey(rsaPrivateKey1)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -495,9 +495,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS512(rsaPrivateKey2, nil), + verifyAlg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey2)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -507,9 +507,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrRSAVerification, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS512(nil, rsaPublicKey1), + verifyAlg: jwt.NewPS512(jwt.RSAPublicKey(rsaPublicKey1)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", @@ -519,9 +519,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewPS512(rsaPrivateKey1, nil), + alg: jwt.NewPS512(jwt.RSAPrivateKey(rsaPrivateKey1)), payload: tp, - verifyAlg: jwt.NewPS512(nil, rsaPublicKey2), + verifyAlg: jwt.NewPS512(jwt.RSAPublicKey(rsaPublicKey2)), wantHeader: jwt.Header{ Algorithm: "PS512", Type: "JWT", From 58bb20f0807aab50dfdae3bb7b867b1320a8e0ce Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 19:50:41 -0300 Subject: [PATCH 96/99] ecdsa_sha: use functional options to set keys --- ecdsa_sha.go | 59 +++++++++++++++++++++++++++++++------------------ sign_test.go | 60 +++++++++++++++++++++++++------------------------- verify_test.go | 60 +++++++++++++++++++++++++------------------------- 3 files changed, 98 insertions(+), 81 deletions(-) diff --git a/ecdsa_sha.go b/ecdsa_sha.go index 1e12db3..3e87f16 100644 --- a/ecdsa_sha.go +++ b/ecdsa_sha.go @@ -18,9 +18,23 @@ var ( // ErrECDSAVerification is the error for an invalid ECDSA signature. ErrECDSAVerification = errors.New("jwt: ECDSA verification failed") - _ Algorithm = new(ecdsaSHA) + _ Algorithm = new(ECDSASHA) ) +// ECDSAPrivateKey is an option to set a private key to the ECDSA-SHA algorithm. +func ECDSAPrivateKey(priv *ecdsa.PrivateKey) func(*ECDSASHA) { + return func(es *ECDSASHA) { + es.priv = priv + } +} + +// ECDSAPublicKey is an option to set a public key to the ECDSA-SHA algorithm. +func ECDSAPublicKey(pub *ecdsa.PublicKey) func(*ECDSASHA) { + return func(es *ECDSASHA) { + es.pub = pub + } +} + func byteSize(bitSize int) int { byteSize := bitSize / 8 if bitSize%8 > 0 { @@ -29,7 +43,8 @@ func byteSize(bitSize int) int { return byteSize } -type ecdsaSHA struct { +// ECDSASHA is an algorithm that uses ECDSA to sign SHA hashes. +type ECDSASHA struct { name string priv *ecdsa.PrivateKey pub *ecdsa.PublicKey @@ -39,42 +54,44 @@ type ecdsaSHA struct { pool *hashPool } -func newECDSASHA(name string, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, sha crypto.Hash) *ecdsaSHA { - if pub == nil { - pub = &priv.PublicKey - } - return &ecdsaSHA{ +func newECDSASHA(name string, opts []func(*ECDSASHA), sha crypto.Hash) *ECDSASHA { + es := ECDSASHA{ name: name, - priv: priv, - pub: pub, sha: sha, - size: byteSize(pub.Params().BitSize) * 2, pool: newHashPool(sha.New), } + for _, opt := range opts { + opt(&es) + } + if es.pub == nil { + es.pub = &es.priv.PublicKey + } + es.size = byteSize(es.pub.Params().BitSize) * 2 + return &es } // NewES256 creates a new algorithm using ECDSA and SHA-256. -func NewES256(priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) Algorithm { - return newECDSASHA("ES256", priv, pub, crypto.SHA256) +func NewES256(opts ...func(*ECDSASHA)) *ECDSASHA { + return newECDSASHA("ES256", opts, crypto.SHA256) } // NewES384 creates a new algorithm using ECDSA and SHA-384. -func NewES384(priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) Algorithm { - return newECDSASHA("ES384", priv, pub, crypto.SHA384) +func NewES384(opts ...func(*ECDSASHA)) *ECDSASHA { + return newECDSASHA("ES384", opts, crypto.SHA384) } // NewES512 creates a new algorithm using ECDSA and SHA-512. -func NewES512(priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) Algorithm { - return newECDSASHA("ES512", priv, pub, crypto.SHA512) +func NewES512(opts ...func(*ECDSASHA)) *ECDSASHA { + return newECDSASHA("ES512", opts, crypto.SHA512) } // Name returns the algorithm's name. -func (es *ecdsaSHA) Name() string { +func (es *ECDSASHA) Name() string { return es.name } // Sign signs headerPayload using the ECDSA-SHA algorithm. -func (es *ecdsaSHA) Sign(headerPayload []byte) ([]byte, error) { +func (es *ECDSASHA) Sign(headerPayload []byte) ([]byte, error) { if es.priv == nil { return nil, ErrECDSANilPrivKey } @@ -82,12 +99,12 @@ func (es *ecdsaSHA) Sign(headerPayload []byte) ([]byte, error) { } // Size returns the signature's byte size. -func (es *ecdsaSHA) Size() int { +func (es *ECDSASHA) Size() int { return es.size } // Verify verifies a signature based on headerPayload using ECDSA-SHA. -func (es *ecdsaSHA) Verify(headerPayload, sig []byte) (err error) { +func (es *ECDSASHA) Verify(headerPayload, sig []byte) (err error) { if es.pub == nil { return ErrECDSANilPubKey } @@ -111,7 +128,7 @@ func (es *ecdsaSHA) Verify(headerPayload, sig []byte) (err error) { return nil } -func (es *ecdsaSHA) sign(headerPayload []byte) ([]byte, error) { +func (es *ECDSASHA) sign(headerPayload []byte) ([]byte, error) { sum, err := es.pool.sign(headerPayload) if err != nil { return nil, err diff --git a/sign_test.go b/sign_test.go index a3bd45c..de8c4e9 100644 --- a/sign_test.go +++ b/sign_test.go @@ -557,9 +557,9 @@ func TestSign(t *testing.T) { }, "ECDSA": []testCase{ { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(nil, es256PublicKey1), + verifyAlg: jwt.NewES256(jwt.ECDSAPublicKey(es256PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -569,9 +569,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(nil, es256PublicKey1), + verifyAlg: jwt.NewES384(jwt.ECDSAPublicKey(es256PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -581,9 +581,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(nil, es256PublicKey2), + verifyAlg: jwt.NewES256(jwt.ECDSAPublicKey(es256PublicKey2)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -593,9 +593,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(es256PrivateKey1, nil), + verifyAlg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -605,9 +605,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(es256PrivateKey2, nil), + verifyAlg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey2)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -617,9 +617,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(nil, es384PublicKey1), + verifyAlg: jwt.NewES384(jwt.ECDSAPublicKey(es384PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -629,9 +629,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(nil, es384PublicKey1), + verifyAlg: jwt.NewES256(jwt.ECDSAPublicKey(es384PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -641,9 +641,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(nil, es384PublicKey2), + verifyAlg: jwt.NewES384(jwt.ECDSAPublicKey(es384PublicKey2)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -653,9 +653,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(es384PrivateKey1, nil), + verifyAlg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -665,9 +665,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(es384PrivateKey2, nil), + verifyAlg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey2)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -677,9 +677,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES512(nil, es512PublicKey1), + verifyAlg: jwt.NewES512(jwt.ECDSAPublicKey(es512PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", @@ -689,9 +689,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(nil, es512PublicKey1), + verifyAlg: jwt.NewES384(jwt.ECDSAPublicKey(es512PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", @@ -701,9 +701,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES512(nil, es512PublicKey2), + verifyAlg: jwt.NewES512(jwt.ECDSAPublicKey(es512PublicKey2)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", @@ -713,9 +713,9 @@ func TestSign(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES512(es512PrivateKey1, nil), + verifyAlg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", @@ -725,9 +725,9 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES512(es512PrivateKey2, nil), + verifyAlg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey2)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", diff --git a/verify_test.go b/verify_test.go index 5b1908a..c8b2955 100644 --- a/verify_test.go +++ b/verify_test.go @@ -533,9 +533,9 @@ func TestVerify(t *testing.T) { }, "ECDSA": []testCase{ { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(nil, es256PublicKey1), + verifyAlg: jwt.NewES256(jwt.ECDSAPublicKey(es256PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -545,9 +545,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(nil, es256PublicKey1), + verifyAlg: jwt.NewES384(jwt.ECDSAPublicKey(es256PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -557,9 +557,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(nil, es256PublicKey2), + verifyAlg: jwt.NewES256(jwt.ECDSAPublicKey(es256PublicKey2)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -569,9 +569,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(es256PrivateKey1, nil), + verifyAlg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -581,9 +581,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES256(es256PrivateKey1, nil), + alg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(es256PrivateKey2, nil), + verifyAlg: jwt.NewES256(jwt.ECDSAPrivateKey(es256PrivateKey2)), wantHeader: jwt.Header{ Algorithm: "ES256", Type: "JWT", @@ -593,9 +593,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(nil, es384PublicKey1), + verifyAlg: jwt.NewES384(jwt.ECDSAPublicKey(es384PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -605,9 +605,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES256(nil, es384PublicKey1), + verifyAlg: jwt.NewES256(jwt.ECDSAPublicKey(es384PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -617,9 +617,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(nil, es384PublicKey2), + verifyAlg: jwt.NewES384(jwt.ECDSAPublicKey(es384PublicKey2)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -629,9 +629,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(es384PrivateKey1, nil), + verifyAlg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -641,9 +641,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES384(es384PrivateKey1, nil), + alg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(es384PrivateKey2, nil), + verifyAlg: jwt.NewES384(jwt.ECDSAPrivateKey(es384PrivateKey2)), wantHeader: jwt.Header{ Algorithm: "ES384", Type: "JWT", @@ -653,9 +653,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES512(nil, es512PublicKey1), + verifyAlg: jwt.NewES512(jwt.ECDSAPublicKey(es512PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", @@ -665,9 +665,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES384(nil, es512PublicKey1), + verifyAlg: jwt.NewES384(jwt.ECDSAPublicKey(es512PublicKey1)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", @@ -677,9 +677,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES512(nil, es512PublicKey2), + verifyAlg: jwt.NewES512(jwt.ECDSAPublicKey(es512PublicKey2)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", @@ -689,9 +689,9 @@ func TestVerify(t *testing.T) { verifyErr: jwt.ErrECDSAVerification, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES512(es512PrivateKey1, nil), + verifyAlg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", @@ -701,9 +701,9 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewES512(es512PrivateKey1, nil), + alg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey1)), payload: tp, - verifyAlg: jwt.NewES512(es512PrivateKey2, nil), + verifyAlg: jwt.NewES512(jwt.ECDSAPrivateKey(es512PrivateKey2)), wantHeader: jwt.Header{ Algorithm: "ES512", Type: "JWT", From 188ea6294260db993cd116c403b43c869278722c Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 20:02:06 -0300 Subject: [PATCH 97/99] ed25519: use functional options to set keys --- ed25519_go1_12.go | 49 ++++++++++++++++++++++++++++++++--------------- sign_test.go | 32 +++++++++++++++++++++++++++---- verify_test.go | 32 +++++++++++++++++++++++++++---- 3 files changed, 90 insertions(+), 23 deletions(-) diff --git a/ed25519_go1_12.go b/ed25519_go1_12.go index 557a41b..070aeb1 100644 --- a/ed25519_go1_12.go +++ b/ed25519_go1_12.go @@ -14,52 +14,71 @@ var ( ErrEd25519PrivKey = errors.New("jwt: Ed25519 private key is nil") // ErrEd25519PubKey is the error for trying to verify a JWT with a nil public key. ErrEd25519PubKey = errors.New("jwt: Ed25519 public key is nil") - // ErrEd25519Verification is the error for when verification with edDSA fails. + // ErrEd25519Verification is the error for when verification with Ed25519 fails. ErrEd25519Verification = errors.New("jwt: Ed25519 verification failed") - _ Algorithm = new(edDSA) + _ Algorithm = new(Ed25519) ) -type edDSA struct { +// Ed25519PrivateKey is an option to set a private key to the Ed25519 algorithm. +func Ed25519PrivateKey(priv ed25519.PrivateKey) func(*Ed25519) { + return func(ed *Ed25519) { + ed.priv = priv + } +} + +// Ed25519PublicKey is an option to set a public key to the Ed25519 algorithm. +func Ed25519PublicKey(pub ed25519.PublicKey) func(*Ed25519) { + return func(ed *Ed25519) { + ed.pub = pub + } +} + +// Ed25519 is an algorithm that uses EdDSA to sign SHA-512 hashes. +type Ed25519 struct { priv ed25519.PrivateKey pub ed25519.PublicKey } // NewEd25519 creates a new algorithm using EdDSA and SHA-512. -func NewEd25519(priv ed25519.PrivateKey, pub ed25519.PublicKey) Algorithm { - if pub == nil { - pub = priv.Public().(ed25519.PublicKey) +func NewEd25519(opts ...func(*Ed25519)) *Ed25519 { + var ed Ed25519 + for _, opt := range opts { + opt(&ed) + } + if ed.pub == nil { + ed.pub = ed.priv.Public().(ed25519.PublicKey) } - return &edDSA{priv: priv, pub: pub} + return &ed } // Name returns the algorithm's name. -func (*edDSA) Name() string { +func (*Ed25519) Name() string { return "Ed25519" } // Sign signs headerPayload using the Ed25519 algorithm. -func (e *edDSA) Sign(headerPayload []byte) ([]byte, error) { - if e.priv == nil { +func (ed *Ed25519) Sign(headerPayload []byte) ([]byte, error) { + if ed.priv == nil { return nil, ErrEd25519PrivKey } - return ed25519.Sign(e.priv, headerPayload), nil + return ed25519.Sign(ed.priv, headerPayload), nil } // Size returns the signature byte size. -func (*edDSA) Size() int { +func (*Ed25519) Size() int { return ed25519.SignatureSize } // Verify verifies a payload and a signature. -func (e *edDSA) Verify(payload, sig []byte) (err error) { - if e.pub == nil { +func (ed *Ed25519) Verify(payload, sig []byte) (err error) { + if ed.pub == nil { return ErrEd25519PubKey } if sig, err = internal.DecodeToBytes(sig); err != nil { return err } - if !ed25519.Verify(e.pub, payload, sig) { + if !ed25519.Verify(ed.pub, payload, sig) { return ErrEd25519Verification } return nil diff --git a/sign_test.go b/sign_test.go index de8c4e9..1a4976b 100644 --- a/sign_test.go +++ b/sign_test.go @@ -739,9 +739,9 @@ func TestSign(t *testing.T) { }, "Ed25519": []testCase{ { - alg: jwt.NewEd25519(ed25519PrivateKey1, nil), + alg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), payload: tp, - verifyAlg: jwt.NewEd25519(ed25519PrivateKey1, nil), + verifyAlg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), wantHeader: jwt.Header{ Algorithm: "Ed25519", Type: "JWT", @@ -751,9 +751,33 @@ func TestSign(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewEd25519(ed25519PrivateKey1, nil), + alg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), payload: tp, - verifyAlg: jwt.NewEd25519(ed25519PrivateKey2, nil), + verifyAlg: jwt.NewEd25519(jwt.Ed25519PublicKey(ed25519PublicKey1)), + wantHeader: jwt.Header{ + Algorithm: "Ed25519", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), + payload: tp, + verifyAlg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey2)), + wantHeader: jwt.Header{ + Algorithm: "Ed25519", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrEd25519Verification, + }, + { + alg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), + payload: tp, + verifyAlg: jwt.NewEd25519(jwt.Ed25519PublicKey(ed25519PublicKey2)), wantHeader: jwt.Header{ Algorithm: "Ed25519", Type: "JWT", diff --git a/verify_test.go b/verify_test.go index c8b2955..0bd4cee 100644 --- a/verify_test.go +++ b/verify_test.go @@ -715,9 +715,9 @@ func TestVerify(t *testing.T) { }, "Ed25519": []testCase{ { - alg: jwt.NewEd25519(ed25519PrivateKey1, nil), + alg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), payload: tp, - verifyAlg: jwt.NewEd25519(ed25519PrivateKey1, nil), + verifyAlg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), wantHeader: jwt.Header{ Algorithm: "Ed25519", Type: "JWT", @@ -727,9 +727,33 @@ func TestVerify(t *testing.T) { verifyErr: nil, }, { - alg: jwt.NewEd25519(ed25519PrivateKey1, nil), + alg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), payload: tp, - verifyAlg: jwt.NewEd25519(ed25519PrivateKey2, nil), + verifyAlg: jwt.NewEd25519(jwt.Ed25519PublicKey(ed25519PublicKey1)), + wantHeader: jwt.Header{ + Algorithm: "Ed25519", + Type: "JWT", + }, + wantPayload: tp, + signErr: nil, + verifyErr: nil, + }, + { + alg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), + payload: tp, + verifyAlg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey2)), + wantHeader: jwt.Header{ + Algorithm: "Ed25519", + Type: "JWT", + }, + wantPayload: testPayload{}, + signErr: nil, + verifyErr: jwt.ErrEd25519Verification, + }, + { + alg: jwt.NewEd25519(jwt.Ed25519PrivateKey(ed25519PrivateKey1)), + payload: tp, + verifyAlg: jwt.NewEd25519(jwt.Ed25519PublicKey(ed25519PublicKey2)), wantHeader: jwt.Header{ Algorithm: "Ed25519", Type: "JWT", From 5ef2698c4cbd11224f796dddae2652896f54445f Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 20:07:04 -0300 Subject: [PATCH 98/99] hmac_sha: return HMACSHA pointer from constructors --- hmac_sha.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/hmac_sha.go b/hmac_sha.go index c9cd7ff..91c54d2 100644 --- a/hmac_sha.go +++ b/hmac_sha.go @@ -15,10 +15,11 @@ var ( // ErrHMACVerification is the error for an invalid signature. ErrHMACVerification = errors.New("jwt: HMAC verification failed") - _ Algorithm = new(hmacSHA) + _ Algorithm = new(HMACSHA) ) -type hmacSHA struct { +// HMACSHA is an algorithm that uses HMAC to sign SHA hashes. +type HMACSHA struct { name string key []byte sha crypto.Hash @@ -26,8 +27,8 @@ type hmacSHA struct { pool *hashPool } -func newHMACSHA(name string, key []byte, sha crypto.Hash) *hmacSHA { - return &hmacSHA{ +func newHMACSHA(name string, key []byte, sha crypto.Hash) *HMACSHA { + return &HMACSHA{ name: name, // cache name key: key, sha: sha, @@ -37,27 +38,27 @@ func newHMACSHA(name string, key []byte, sha crypto.Hash) *hmacSHA { } // NewHS256 creates a new algorithm using HMAC and SHA-256. -func NewHS256(key []byte) Algorithm { +func NewHS256(key []byte) *HMACSHA { return newHMACSHA("HS256", key, crypto.SHA256) } // NewHS384 creates a new algorithm using HMAC and SHA-384. -func NewHS384(key []byte) Algorithm { +func NewHS384(key []byte) *HMACSHA { return newHMACSHA("HS384", key, crypto.SHA384) } // NewHS512 creates a new algorithm using HMAC and SHA-512. -func NewHS512(key []byte) Algorithm { +func NewHS512(key []byte) *HMACSHA { return newHMACSHA("HS512", key, crypto.SHA512) } // Name returns the algorithm's name. -func (hs *hmacSHA) Name() string { +func (hs *HMACSHA) Name() string { return hs.name } // Sign signs headerPayload using the HMAC-SHA algorithm. -func (hs *hmacSHA) Sign(headerPayload []byte) ([]byte, error) { +func (hs *HMACSHA) Sign(headerPayload []byte) ([]byte, error) { if string(hs.key) == "" { return nil, ErrHMACMissingKey } @@ -65,12 +66,12 @@ func (hs *hmacSHA) Sign(headerPayload []byte) ([]byte, error) { } // Size returns the signature's byte size. -func (hs *hmacSHA) Size() int { +func (hs *HMACSHA) Size() int { return hs.size } // Verify verifies a signature based on headerPayload using HMAC-SHA. -func (hs *hmacSHA) Verify(headerPayload, sig []byte) (err error) { +func (hs *HMACSHA) Verify(headerPayload, sig []byte) (err error) { if sig, err = internal.DecodeToBytes(sig); err != nil { return err } From 844855e61e77d0037748ed420a4e0daea5060945 Mon Sep 17 00:00:00 2001 From: Gabriel Sanches Date: Tue, 9 Jul 2019 23:07:40 -0300 Subject: [PATCH 99/99] jwtutil: add an Algorithm resolver --- README.md | 31 ++++++++++++++++++++---- jwtutil/resolver.go | 46 +++++++++++++++++++++++++++++++++++ jwtutil/resolver_test.go | 52 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 5 deletions(-) create mode 100644 jwtutil/resolver.go create mode 100644 jwtutil/resolver_test.go diff --git a/README.md b/README.md index fcc17ed..bcf90f3 100644 --- a/README.md +++ b/README.md @@ -201,16 +201,37 @@ func main() {

+
Using an Algorithm resolver +

+ ```go -import "github.com/gbrlsnchs/jwt/v3" +import ( + "errors" -var hs = jwt.NewHS256([]byte("secret")) + "github.com/gbrlsnchs/jwt/v3" + "github.com/gbrlsnchs/jwt/v3/jwtutil" +) -func main() { +var ( // ... - var hd jwt.Header - if err := jwt.Verify(token, hs, jwt.DecodeHeader(&hd, true)); err != nil { + rs256 = jwt.NewRS256(jwt.RSAPublicKey(myRSAPublicKey)) + es256 = jwt.NewES256(jwt.ECDSAPublicKey(myECDSAPublicKey)) +) + +func main() { + rv := &jwtutil.Resolver{New: func(hd jwt.Header) { + switch hd.KeyID { + case "foo": + return rs256, nil + case "bar": + return es256, nil + default: + return nil, errors.New(`invalid "kid"`) + } + }} + var pl jwt.Payload + if _, err := jwt.Verify(token, rv, &pl); err != nil { // ... } diff --git a/jwtutil/resolver.go b/jwtutil/resolver.go new file mode 100644 index 0000000..748291a --- /dev/null +++ b/jwtutil/resolver.go @@ -0,0 +1,46 @@ +package jwtutil + +import ( + "errors" + + "github.com/gbrlsnchs/jwt/v3" +) + +// Resolver is an Algorithm resolver. +type Resolver struct { + New func(jwt.Header) (jwt.Algorithm, error) + alg jwt.Algorithm +} + +// Name returns an Algorithm's name. +func (rv *Resolver) Name() string { + return rv.alg.Name() +} + +// Resolve sets an Algorithm based on a JOSE Header. +func (rv *Resolver) Resolve(hd jwt.Header) error { + if rv.alg != nil { + return nil + } + alg, err := rv.New(hd) + if err != nil { + return err + } + rv.alg = alg + return nil +} + +// Sign returns an error since Resolver doesn't support signing. +func (rv *Resolver) Sign(_ []byte) ([]byte, error) { + return nil, errors.New("jwtutil: Resolver can only verify") +} + +// Size returns an Algorithm's size. +func (rv *Resolver) Size() int { + return rv.alg.Size() +} + +// Verify resolves and Algorithm and verifies using it. +func (rv *Resolver) Verify(headerPayload, sig []byte) error { + return rv.alg.Verify(headerPayload, sig) +} diff --git a/jwtutil/resolver_test.go b/jwtutil/resolver_test.go new file mode 100644 index 0000000..f5e7b0a --- /dev/null +++ b/jwtutil/resolver_test.go @@ -0,0 +1,52 @@ +package jwtutil_test + +import ( + "errors" + "testing" + + "github.com/gbrlsnchs/jwt/v3" + "github.com/gbrlsnchs/jwt/v3/jwtutil" +) + +var hs256 = jwt.NewHS256([]byte("resolver")) + +func TestResolver(t *testing.T) { + testCases := []struct { + signer jwt.Algorithm + signOpts []jwt.SignOption + verifier jwt.Algorithm + }{ + { + signer: hs256, + verifier: &jwtutil.Resolver{ + New: func(hd jwt.Header) (jwt.Algorithm, error) { + return hs256, nil + }, + }, + }, + { + signer: hs256, + signOpts: []jwt.SignOption{jwt.KeyID("test")}, + verifier: &jwtutil.Resolver{ + New: func(hd jwt.Header) (jwt.Algorithm, error) { + if hd.KeyID != "test" { + return nil, errors.New(`wrong "kid"`) + } + return hs256, nil + }, + }, + }, + } + for _, tc := range testCases { + t.Run("", func(t *testing.T) { + token, err := jwt.Sign(jwt.Payload{}, tc.signer, tc.signOpts...) + if err != nil { + t.Fatal(err) + } + var pl jwt.Payload + if _, err = jwt.Verify(token, tc.verifier, &pl); err != nil { + t.Fatal(err) + } + }) + } +}