diff --git a/README.md b/README.md index f000366..267ddc4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ JSON Web Token for Go [RFC 7519](https://tools.ietf.org/html/rfc7519), also see [jwt.io](https://jwt.io) for more. -The latest version is `v3`. +The latest version is `v4`. ## Rationale @@ -30,10 +30,10 @@ There are many JWT libraries, but many of them are hard to use (unclear or fixed ## Install -Go version 1.13+ +Go version 1.17+ ``` -GO111MODULE=on go get github.com/cristalhq/jwt/v3 +go get github.com/cristalhq/jwt/v4 ``` ## Example @@ -59,8 +59,8 @@ builder := jwt.NewBuilder(signer) token, err := builder.Build(claims) checkErr(err) -// here is token as byte slice -var _ []byte = token.Bytes() // or just token.String() for string +// here is token as a string +var _ string = token.String() ``` Parse and verify token: @@ -70,25 +70,25 @@ key := []byte(`secret`) verifier, err := jwt.NewVerifierHS(jwt.HS256, key) checkErr(err) -// parse a Token (by example received from a request) -tokenStr := `` -token, err := jwt.ParseString(tokenStr) +// parse and verify a token +tokenBytes := token.Bytes() +newToken, err := jwt.Parse(tokenBytes, verifier) checkErr(err) -// and verify it's signature -err = verifier.Verify(token.Payload(), token.Signature()) +// or just verify it's signature +err = verifier.Verify(newToken) checkErr(err) -// also you can parse and verify together -newToken, err := jwt.ParseAndVerifyString(tokenStr, verifier) -checkErr(err) - -// get standard claims -var newClaims jwt.StandardClaims -errClaims := json.Unmarshal(newToken.RawClaims(), &newClaims) +// get Registered claims +var newClaims jwt.RegisteredClaims +errClaims := json.Unmarshal(newToken.Claims(), &newClaims) checkErr(errClaims) -// verify claims as you +// or parse only claims +errParseClaims := jwt.ParseClaims(tokenBytes, verifier, &newClaims) +checkErr(errParseClaims) + +// verify claims as you wish var _ bool = newClaims.IsForAudience("admin") var _ bool = newClaims.IsValidAt(time.Now()) ``` @@ -105,8 +105,8 @@ See [these docs][pkg-url]. [build-img]: https://github.com/cristalhq/jwt/workflows/build/badge.svg [build-url]: https://github.com/cristalhq/jwt/actions -[pkg-img]: https://pkg.go.dev/badge/cristalhq/jwt/v3 -[pkg-url]: https://pkg.go.dev/github.com/cristalhq/jwt/v3 +[pkg-img]: https://pkg.go.dev/badge/cristalhq/jwt/v4 +[pkg-url]: https://pkg.go.dev/github.com/cristalhq/jwt/v4 [reportcard-img]: https://goreportcard.com/badge/cristalhq/jwt [reportcard-url]: https://goreportcard.com/report/cristalhq/jwt [coverage-img]: https://codecov.io/gh/cristalhq/jwt/branch/master/graph/badge.svg diff --git a/algo.go b/algo.go index c812a3c..3512f42 100644 --- a/algo.go +++ b/algo.go @@ -16,7 +16,7 @@ type Signer interface { // Verifier is used to verify tokens. type Verifier interface { Algorithm() Algorithm - Verify(payload, signature []byte) error + Verify(token *Token) error } // Algorithm for signing and verifying. @@ -24,26 +24,6 @@ type Algorithm string func (a Algorithm) String() string { return string(a) } -// keySize of the algorithm's key (if exist). Is similar to Signer.SignSize. -func (a Algorithm) keySize() int { return algsKeySize[a] } - -var algsKeySize = map[Algorithm]int{ - // for EdDSA private and public key have different sizes, so 0 - // for HS there is no limits for key size, so 0 - - RS256: 256, - RS384: 384, - RS512: 512, - - ES256: 64, - ES384: 96, - ES512: 132, - - PS256: 256, - PS384: 384, - PS512: 512, -} - // Algorithm names for signing and verifying. const ( EdDSA Algorithm = "EdDSA" @@ -68,8 +48,7 @@ const ( func hashPayload(hash crypto.Hash, payload []byte) ([]byte, error) { hasher := hash.New() - _, err := hasher.Write(payload) - if err != nil { + if _, err := hasher.Write(payload); err != nil { return nil, err } signed := hasher.Sum(nil) diff --git a/algo_eddsa.go b/algo_eddsa.go index 1af3d3f..3d9ebe8 100644 --- a/algo_eddsa.go +++ b/algo_eddsa.go @@ -5,59 +5,58 @@ import ( ) // NewSignerEdDSA returns a new ed25519-based signer. -func NewSignerEdDSA(key ed25519.PrivateKey) (Signer, error) { +func NewSignerEdDSA(key ed25519.PrivateKey) (*EdDSAAlg, error) { if len(key) == 0 { return nil, ErrNilKey } if len(key) != ed25519.PrivateKeySize { return nil, ErrInvalidKey } - return &edDSAAlg{ - alg: EdDSA, + return &EdDSAAlg{ + publicKey: nil, privateKey: key, }, nil } // NewVerifierEdDSA returns a new ed25519-based verifier. -func NewVerifierEdDSA(key ed25519.PublicKey) (Verifier, error) { +func NewVerifierEdDSA(key ed25519.PublicKey) (*EdDSAAlg, error) { if len(key) == 0 { return nil, ErrNilKey } if len(key) != ed25519.PublicKeySize { return nil, ErrInvalidKey } - return &edDSAAlg{ - alg: EdDSA, - publicKey: key, + return &EdDSAAlg{ + publicKey: key, + privateKey: nil, }, nil } -type edDSAAlg struct { - alg Algorithm +type EdDSAAlg struct { publicKey ed25519.PublicKey privateKey ed25519.PrivateKey } -func (ed *edDSAAlg) Algorithm() Algorithm { - return ed.alg +func (ed *EdDSAAlg) Algorithm() Algorithm { + return EdDSA } -func (ed *edDSAAlg) SignSize() int { +func (ed *EdDSAAlg) SignSize() int { return ed25519.SignatureSize } -func (ed *edDSAAlg) Sign(payload []byte) ([]byte, error) { +func (ed *EdDSAAlg) Sign(payload []byte) ([]byte, error) { return ed25519.Sign(ed.privateKey, payload), nil } -func (ed *edDSAAlg) VerifyToken(token *Token) error { - if constTimeAlgEqual(token.Header().Algorithm, ed.alg) { - return ed.Verify(token.Payload(), token.Signature()) +func (ed *EdDSAAlg) Verify(token *Token) error { + if !constTimeAlgEqual(token.Header().Algorithm, EdDSA) { + return ErrAlgorithmMismatch } - return ErrAlgorithmMismatch + return ed.verify(token.PayloadPart(), token.Signature()) } -func (ed *edDSAAlg) Verify(payload, signature []byte) error { +func (ed *EdDSAAlg) verify(payload, signature []byte) error { if !ed25519.Verify(ed.publicKey, payload, signature) { return ErrInvalidSignature } diff --git a/algo_eddsa_test.go b/algo_eddsa_test.go index 65a3b4a..f0406cf 100644 --- a/algo_eddsa_test.go +++ b/algo_eddsa_test.go @@ -2,52 +2,37 @@ package jwt import ( "crypto/ed25519" - "crypto/rand" "errors" "testing" ) -var ( - ed25519PrivateKey ed25519.PrivateKey - ed25519PublicKey ed25519.PublicKey - - ed25519PrivateKeyAnother ed25519.PrivateKey - ed25519PublicKeyAnother ed25519.PublicKey -) - -func init() { - f := func() (ed25519.PrivateKey, ed25519.PublicKey) { - pubKey, privKey, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - panic(err) - } - return privKey, pubKey - } - - ed25519PrivateKey, ed25519PublicKey = f() - ed25519PrivateKeyAnother, ed25519PublicKeyAnother = f() -} - func TestEdDSA(t *testing.T) { - f := func(privateKey ed25519.PrivateKey, publicKey ed25519.PublicKey, isCorrectSign bool) { + f := func(privateKey ed25519.PrivateKey, publicKey ed25519.PublicKey, wantErr error) { t.Helper() - const payload = `simple-string-payload` - - sign := ed25519Sign(t, privateKey, payload) + signer, errSigner := NewSignerEdDSA(privateKey) + if errSigner != nil { + t.Fatalf("NewSignerEdDSA %v", errSigner) + } + verifier, errVerifier := NewVerifierEdDSA(publicKey) + if errVerifier != nil { + t.Fatalf("NewVerifierEdDSA %v", errVerifier) + } - err := ed25519Verify(t, publicKey, payload, sign) - if err != nil && isCorrectSign { - t.Fatal(err) + token, err := NewBuilder(signer).Build(simplePayload) + if err != nil { + t.Fatalf("Build %v", errVerifier) } - if err == nil && !isCorrectSign { - t.Fatal("must be not nil") + + errVerify := verifier.Verify(token) + if !errors.Is(errVerify, wantErr) { + t.Errorf("want %v, got %v", wantErr, errVerify) } } - f(ed25519PrivateKey, ed25519PublicKey, true) - f(ed25519PrivateKey, ed25519PublicKeyAnother, false) - f(ed25519PrivateKeyAnother, ed25519PublicKey, false) + f(ed25519PrivateKey, ed25519PublicKey, nil) + f(ed25519PrivateKey, ed25519PublicKeyAnother, ErrInvalidSignature) + f(ed25519PrivateKeyAnother, ed25519PublicKey, ErrInvalidSignature) } func TestEdDSA_BadKeys(t *testing.T) { @@ -58,35 +43,30 @@ func TestEdDSA_BadKeys(t *testing.T) { } f(getSignerError(NewSignerEdDSA(nil)), ErrNilKey) + priv := ed25519.PrivateKey(make([]byte, 72)) f(getSignerError(NewSignerEdDSA(priv)), ErrInvalidKey) f(getVerifierError(NewVerifierEdDSA(nil)), ErrNilKey) + pub := ed25519.PublicKey(make([]byte, 72)) f(getVerifierError(NewVerifierEdDSA(pub)), ErrInvalidKey) } -func ed25519Sign(t *testing.T, privateKey ed25519.PrivateKey, payload string) []byte { - t.Helper() - - signer, errSigner := NewSignerEdDSA(privateKey) - if errSigner != nil { - t.Fatalf("NewSignerEdDSA %v", errSigner) - } - - sign, errSign := signer.Sign([]byte(payload)) - if errSign != nil { - t.Fatalf("SignEdDSA %v", errSign) - } - return sign -} - -func ed25519Verify(t *testing.T, publicKey ed25519.PublicKey, payload string, sign []byte) error { - t.Helper() - - verifier, errVerifier := NewVerifierEdDSA(publicKey) - if errVerifier != nil { - t.Fatalf("NewVerifierEdDSA %v", errVerifier) - } - return verifier.Verify([]byte(payload), sign) -} +var ( + // See: RFC 8037, appendix A.1 + ed25519PrivateKey = ed25519.PrivateKey([]byte{ + 0x9d, 0x61, 0xb1, 0x9d, 0xef, 0xfd, 0x5a, 0x60, + 0xba, 0x84, 0x4a, 0xf4, 0x92, 0xec, 0x2c, 0xc4, + 0x44, 0x49, 0xc5, 0x69, 0x7b, 0x32, 0x69, 0x19, + 0x70, 0x3b, 0xac, 0x03, 0x1c, 0xae, 0x7f, 0x60, + 0xd7, 0x5a, 0x98, 0x01, 0x82, 0xb1, 0x0a, 0xb7, + 0xd5, 0x4b, 0xfe, 0xd3, 0xc9, 0x64, 0x07, 0x3a, + 0x0e, 0xe1, 0x72, 0xf3, 0xda, 0xa6, 0x23, 0x25, + 0xaf, 0x02, 0x1a, 0x68, 0xf7, 0x07, 0x51, 0x1a, + }) + ed25519PublicKey = ed25519PrivateKey.Public().(ed25519.PublicKey) + + ed25519PrivateKeyAnother ed25519.PrivateKey = base64ToBytes("eJGvQDFFiaYHaZU2sfRhPrGKlgZcHBT8CPY3Fx2zhQEjlzQ5-3qTgKZ5wCmIRqL4sbNhWvpPx5Y_PqmSEg3oYg") + ed25519PublicKeyAnother ed25519.PublicKey = base64ToBytes("I5c0Oft6k4CmecApiEai-LGzYVr6T8eWPz6pkhIN6GI") +) diff --git a/algo_es.go b/algo_es.go index 2b3126c..ac803b9 100644 --- a/algo_es.go +++ b/algo_es.go @@ -8,7 +8,7 @@ import ( ) // NewSignerES returns a new ECDSA-based signer. -func NewSignerES(alg Algorithm, key *ecdsa.PrivateKey) (Signer, error) { +func NewSignerES(alg Algorithm, key *ecdsa.PrivateKey) (*ESAlg, error) { if key == nil { return nil, ErrNilKey } @@ -16,16 +16,17 @@ func NewSignerES(alg Algorithm, key *ecdsa.PrivateKey) (Signer, error) { if err != nil { return nil, err } - return &esAlg{ + return &ESAlg{ alg: alg, hash: hash, privateKey: key, + publicKey: nil, signSize: roundBytes(key.PublicKey.Params().BitSize) * 2, }, nil } // NewVerifierES returns a new ECDSA-based verifier. -func NewVerifierES(alg Algorithm, key *ecdsa.PublicKey) (Verifier, error) { +func NewVerifierES(alg Algorithm, key *ecdsa.PublicKey) (*ESAlg, error) { if key == nil { return nil, ErrNilKey } @@ -33,34 +34,37 @@ func NewVerifierES(alg Algorithm, key *ecdsa.PublicKey) (Verifier, error) { if err != nil { return nil, err } - return &esAlg{ - alg: alg, - hash: hash, - publicKey: key, - signSize: roundBytes(key.Params().BitSize) * 2, + return &ESAlg{ + alg: alg, + hash: hash, + privateKey: nil, + publicKey: key, + signSize: roundBytes(key.Params().BitSize) * 2, }, nil } func getParamsES(alg Algorithm, size int) (crypto.Hash, error) { var hash crypto.Hash + var keySize int + switch alg { case ES256: - hash = crypto.SHA256 + hash, keySize = crypto.SHA256, 64 case ES384: - hash = crypto.SHA384 + hash, keySize = crypto.SHA384, 96 case ES512: - hash = crypto.SHA512 + hash, keySize = crypto.SHA512, 132 default: return 0, ErrUnsupportedAlg } - if alg.keySize() != size { + if keySize != size { return 0, ErrInvalidKey } return hash, nil } -type esAlg struct { +type ESAlg struct { alg Algorithm hash crypto.Hash publicKey *ecdsa.PublicKey @@ -68,15 +72,15 @@ type esAlg struct { signSize int } -func (es *esAlg) Algorithm() Algorithm { +func (es *ESAlg) Algorithm() Algorithm { return es.alg } -func (es *esAlg) SignSize() int { +func (es *ESAlg) SignSize() int { return es.signSize } -func (es *esAlg) Sign(payload []byte) ([]byte, error) { +func (es *ESAlg) Sign(payload []byte) ([]byte, error) { digest, err := hashPayload(es.hash, payload) if err != nil { return nil, err @@ -96,14 +100,14 @@ func (es *esAlg) Sign(payload []byte) ([]byte, error) { return signature, nil } -func (es *esAlg) VerifyToken(token *Token) error { - if constTimeAlgEqual(token.Header().Algorithm, es.alg) { - return es.Verify(token.Payload(), token.Signature()) +func (es *ESAlg) Verify(token *Token) error { + if !constTimeAlgEqual(token.Header().Algorithm, es.alg) { + return ErrAlgorithmMismatch } - return ErrAlgorithmMismatch + return es.verify(token.PayloadPart(), token.Signature()) } -func (es *esAlg) Verify(payload, signature []byte) error { +func (es *ESAlg) verify(payload, signature []byte) error { if len(signature) != es.SignSize() { return ErrInvalidSignature } diff --git a/algo_es_test.go b/algo_es_test.go index 5759eaf..cbfeb7a 100644 --- a/algo_es_test.go +++ b/algo_es_test.go @@ -2,78 +2,48 @@ package jwt import ( "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "errors" - "sync" "testing" ) -var ( - ecdsaPublicKey256, ecdsaPublicKey384, ecdsaPublicKey521 *ecdsa.PublicKey - ecdsaPrivateKey256, ecdsaPrivateKey384, ecdsaPrivateKey521 *ecdsa.PrivateKey - - ecdsaPublicKey256Another, ecdsaPublicKey384Another, ecdsaPublicKey521Another *ecdsa.PublicKey - ecdsaPrivateKey256Another, ecdsaPrivateKey384Another, ecdsaPrivateKey521Another *ecdsa.PrivateKey -) - -var initESKeysOnce sync.Once - -func initESKeys() { - initESKeysOnce.Do(func() { - f := func(f func() elliptic.Curve) (*ecdsa.PrivateKey, *ecdsa.PublicKey) { - privKey, err := ecdsa.GenerateKey(f(), rand.Reader) - if err != nil { - panic(err) - } - return privKey, &privKey.PublicKey - } - - ecdsaPrivateKey256, ecdsaPublicKey256 = f(elliptic.P256) - ecdsaPrivateKey384, ecdsaPublicKey384 = f(elliptic.P384) - ecdsaPrivateKey521, ecdsaPublicKey521 = f(elliptic.P521) - - ecdsaPrivateKey256Another, ecdsaPublicKey256Another = f(elliptic.P256) - ecdsaPrivateKey384Another, ecdsaPublicKey384Another = f(elliptic.P384) - ecdsaPrivateKey521Another, ecdsaPublicKey521Another = f(elliptic.P521) - }) -} - func TestES(t *testing.T) { - initESKeys() - - f := func(alg Algorithm, privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, isCorrectSign bool) { + f := func(alg Algorithm, privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, wantErr error) { t.Helper() - const payload = `simple-string-payload` - - sign := esSign(t, alg, privateKey, payload) + signer, errSigner := NewSignerES(alg, privateKey) + if errSigner != nil { + t.Fatalf("NewSignerES %v", errSigner) + } + verifier, errVerifier := NewVerifierES(alg, publicKey) + if errVerifier != nil { + t.Fatalf("NewVerifierES %v", errVerifier) + } - err := esVerify(t, alg, publicKey, payload, sign) - if err != nil && isCorrectSign { - t.Error(err) + token, err := NewBuilder(signer).Build(simplePayload) + if err != nil { + t.Fatalf("Build %v", errVerifier) } - if err == nil && !isCorrectSign { - t.Error("must be not nil") + + errVerify := verifier.Verify(token) + if !errors.Is(errVerify, wantErr) { + t.Errorf("want %v, got %v", wantErr, errVerify) } } - f(ES256, ecdsaPrivateKey256, ecdsaPublicKey256, true) - f(ES384, ecdsaPrivateKey384, ecdsaPublicKey384, true) - f(ES512, ecdsaPrivateKey521, ecdsaPublicKey521, true) + f(ES256, ecdsaPrivateKey256, ecdsaPublicKey256, nil) + f(ES384, ecdsaPrivateKey384, ecdsaPublicKey384, nil) + f(ES512, ecdsaPrivateKey521, ecdsaPublicKey521, nil) - f(ES256, ecdsaPrivateKey256, ecdsaPublicKey256Another, false) - f(ES384, ecdsaPrivateKey384, ecdsaPublicKey384Another, false) - f(ES512, ecdsaPrivateKey521, ecdsaPublicKey521Another, false) + f(ES256, ecdsaPrivateKey256, ecdsaPublicKey256Another, ErrInvalidSignature) + f(ES384, ecdsaPrivateKey384, ecdsaPublicKey384Another, ErrInvalidSignature) + f(ES512, ecdsaPrivateKey521, ecdsaPublicKey521Another, ErrInvalidSignature) - f(ES256, ecdsaPrivateKey256Another, ecdsaPublicKey256, false) - f(ES384, ecdsaPrivateKey384Another, ecdsaPublicKey384, false) - f(ES512, ecdsaPrivateKey521Another, ecdsaPublicKey521, false) + f(ES256, ecdsaPrivateKey256Another, ecdsaPublicKey256, ErrInvalidSignature) + f(ES384, ecdsaPrivateKey384Another, ecdsaPublicKey384, ErrInvalidSignature) + f(ES512, ecdsaPrivateKey521Another, ecdsaPublicKey521, ErrInvalidSignature) } func TestES_BadKeys(t *testing.T) { - initESKeys() - f := func(err, wantErr error) { t.Helper() @@ -109,27 +79,72 @@ func TestES_BadKeys(t *testing.T) { f(getVerifierError(NewVerifierES(ES512, ecdsaPublicKey384)), ErrInvalidKey) } -func esSign(t *testing.T, alg Algorithm, privateKey *ecdsa.PrivateKey, payload string) []byte { - t.Helper() +var ( + ecdsaPrivateKey256 = mustParseECKey(testKeyES256) + ecdsaPrivateKey384 = mustParseECKey(testKeyES384) + ecdsaPrivateKey521 = mustParseECKey(testKeyES521) - signer, errSigner := NewSignerES(alg, privateKey) - if errSigner != nil { - t.Fatalf("NewSignerES %v", errSigner) - } + ecdsaPublicKey256 = &ecdsaPrivateKey256.PublicKey + ecdsaPublicKey384 = &ecdsaPrivateKey384.PublicKey + ecdsaPublicKey521 = &ecdsaPrivateKey521.PublicKey - sign, errSign := signer.Sign([]byte(payload)) - if errSign != nil { - t.Fatalf("SignES %v", errSign) - } - return sign -} + ecdsaPrivateKey256Another = mustParseECKey(testKeyES256Another) + ecdsaPrivateKey384Another = mustParseECKey(testKeyES384Another) + ecdsaPrivateKey521Another = mustParseECKey(testKeyES521Another) -func esVerify(t *testing.T, alg Algorithm, publicKey *ecdsa.PublicKey, payload string, sign []byte) error { - t.Helper() + ecdsaPublicKey256Another = &ecdsaPrivateKey256Another.PublicKey + ecdsaPublicKey384Another = &ecdsaPrivateKey384Another.PublicKey + ecdsaPublicKey521Another = &ecdsaPrivateKey521Another.PublicKey +) - verifier, errVerifier := NewVerifierES(alg, publicKey) - if errVerifier != nil { - t.Fatalf("NewVerifierES %v", errVerifier) - } - return verifier.Verify([]byte(payload), sign) -} +// To generate keys: +// ES256 +// openssl ecparam -name prime256v1 -genkey -noout -out es256-private.pem +// ES384 +// openssl ecparam -name secp384r1 -genkey -noout -out es384-private.pem +// ES512 +// openssl ecparam -name secp521r1 -genkey -noout -out es521-private.pem +// +const ( + testKeyES256 = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIM+a8cZ6BjdZBYy7pMIqmWsHKSmAZhZ/RTeSkmzPKohfoAoGCCqGSM49 +AwEHoUQDQgAE18xDMC6wYt4TJakM3DeHBmLoyEin/vNaJl3g2V3xfGSmuZ1GL2Pm +DO00CX84Vj/pHaIGQYgXLm8zuxiBMaNIXA== +-----END EC PRIVATE KEY-----` + + testKeyES384 = `-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCi1H43WBIeJVC2kN/asAJAte564UyXAb+ZIjyB+jF92BfcOrDkQkJV +8er9/kZCSCegBwYFK4EEACKhZANiAAQP3m6j/r1X70mQ38BEArSwFkr/jztwHB9/ +kHDd8paBykz4wjI2TqIANZ+7d6EWQNL/kEcxfd6CUsq0vRMi8cVG1+Yw1ogLcKgd +hdoGc7IiV1oD6w6iX5PfqUdj2lfP6qU= +-----END EC PRIVATE KEY-----` + + testKeyES521 = `-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIBxKNCQAGpI4T9l6yxN8jCTc5KzF4wlnDF1M8uNebQfsgl9RXwAb34 +8YF1Bip5kkf6OFh6hFBDK/J5VgqSrgFMXNigBwYFK4EEACOhgYkDgYYABACju/hE +QgtvmUP6ZToxo2f7nO/Z9b2dOArvZE9qLqUKi8p8vhLd085YJ+PUAc9BRPSSUAsR +0g4FvPJKFBzO5KcdIAD/Rnqt4sjBiU+SsVqeG0YWbuotFqbzYu9J0vMhPiwaZa73 +HIoOXZT7jXPe6TePSmBodJuj3i0hgtlbvC3dQDccyw== +-----END EC PRIVATE KEY-----` + + testKeyES256Another = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIM9p5Ce0A5nPx4LxiA92TP3wDNiXDyK17sn8zZsvaseSoAoGCCqGSM49 +AwEHoUQDQgAEiX+f1oVkt5F2lvpFmerr2OoFRalnqK0yNx0d6vph6MsAcfDC309P +TjEIWn3NQNHI8XROvhODPxiSbXumbnuoOw== +-----END EC PRIVATE KEY-----` + + testKeyES384Another = `-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCRwcoUBpkkIp1geqykLRqrsoQ0swsJJTVyVNu6lDshoOYAOz4NwUw2 +iRl0o/Xm5cCgBwYFK4EEACKhZANiAARz6nE/bj22RdSKRENMDeF5V+VE20tN1Nzk +tvbmDePQD8538gBGQJzv+lVWNNmsx5MD0To1BBc2HbemdKrXmnLnwOJ2a+zsLqCa +JlXGCIn5G1gL2sfMbeNn/WB3MPOQna0= +-----END EC PRIVATE KEY-----` + + testKeyES521Another = `-----BEGIN EC PRIVATE KEY----- +MIHbAgEBBEGSdts6vIYr2KEQxjkfMys7RfxDUidql0AHl7RvbzdiVVDaVjx7g+Cl +tZpqDMe/sYE813duYRpd9xXCFSRVDjHBXqAHBgUrgQQAI6GBiQOBhgAEAQsdOs3W +huEJWa6h86aTP980Pdbme6fkdTERq9mvI1zn8L4211scGA6cbqNeLNn6wt/v7iGb +HNjmL7z8CLwOgqHDASUN5UtdJC+gPDJi7WllkCz7uM2iwvZQ339bTN+bywiHQUrQ +MvbD7c0RONrhLoch5W6TlWCMj9f4EkQQEfk63Q8F +-----END EC PRIVATE KEY-----` +) diff --git a/algo_hs.go b/algo_hs.go index 9438fbf..054ca23 100644 --- a/algo_hs.go +++ b/algo_hs.go @@ -8,25 +8,16 @@ import ( ) // NewSignerHS returns a new HMAC-based signer. -func NewSignerHS(alg Algorithm, key []byte) (Signer, error) { +func NewSignerHS(alg Algorithm, key []byte) (*HSAlg, error) { return newHS(alg, key) } // NewVerifierHS returns a new HMAC-based verifier. -func NewVerifierHS(alg Algorithm, key []byte) (Verifier, error) { +func NewVerifierHS(alg Algorithm, key []byte) (*HSAlg, error) { return newHS(alg, key) } -type hmacAlgo interface { - // copy-pasted Signer & Verifier due to older Go versions - Algorithm() Algorithm - SignSize() int - Sign(payload []byte) ([]byte, error) - Verify(payload, signature []byte) error - VerifyToken(token *Token) error -} - -func newHS(alg Algorithm, key []byte) (hmacAlgo, error) { +func newHS(alg Algorithm, key []byte) (*HSAlg, error) { if len(key) == 0 { return nil, ErrNilKey } @@ -34,7 +25,7 @@ func newHS(alg Algorithm, key []byte) (hmacAlgo, error) { if !ok { return nil, ErrUnsupportedAlg } - return &hsAlg{ + return &HSAlg{ alg: alg, hash: hash, key: key, @@ -59,33 +50,33 @@ func getHashHMAC(alg Algorithm) (crypto.Hash, bool) { } } -type hsAlg struct { +type HSAlg struct { alg Algorithm hash crypto.Hash key []byte hashPool *sync.Pool } -func (hs *hsAlg) Algorithm() Algorithm { +func (hs *HSAlg) Algorithm() Algorithm { return hs.alg } -func (hs *hsAlg) SignSize() int { +func (hs *HSAlg) SignSize() int { return hs.hash.Size() } -func (hs *hsAlg) Sign(payload []byte) ([]byte, error) { +func (hs *HSAlg) Sign(payload []byte) ([]byte, error) { return hs.sign(payload) } -func (hs *hsAlg) VerifyToken(token *Token) error { - if constTimeAlgEqual(token.Header().Algorithm, hs.alg) { - return hs.Verify(token.Payload(), token.Signature()) +func (hs *HSAlg) Verify(token *Token) error { + if !constTimeAlgEqual(token.Header().Algorithm, hs.alg) { + return ErrAlgorithmMismatch } - return ErrAlgorithmMismatch + return hs.verify(token.PayloadPart(), token.Signature()) } -func (hs *hsAlg) Verify(payload, signature []byte) error { +func (hs *HSAlg) verify(payload, signature []byte) error { digest, err := hs.sign(payload) if err != nil { return err @@ -96,15 +87,14 @@ func (hs *hsAlg) Verify(payload, signature []byte) error { return nil } -func (hs *hsAlg) sign(payload []byte) ([]byte, error) { +func (hs *HSAlg) sign(payload []byte) ([]byte, error) { hasher := hs.hashPool.Get().(hash.Hash) defer func() { hasher.Reset() hs.hashPool.Put(hasher) }() - _, err := hasher.Write(payload) - if err != nil { + if _, err := hasher.Write(payload); err != nil { return nil, err } return hasher.Sum(nil), nil diff --git a/algo_hs_test.go b/algo_hs_test.go index f101d62..877cf40 100644 --- a/algo_hs_test.go +++ b/algo_hs_test.go @@ -1,68 +1,51 @@ package jwt import ( + "errors" "testing" ) -var ( - hsKey256 = []byte("hmac-secret-key-256") - hsKey384 = []byte("hmac-secret-key-384") - hsKey512 = []byte("hmac-secret-key-512") - - hsKeyAnother256 = []byte("hmac-secret-key-256-another") - hsKeyAnother384 = []byte("hmac-secret-key-384-another") - hsKeyAnother512 = []byte("hmac-secret-key-512-another") -) - func TestHS(t *testing.T) { - f := func(alg Algorithm, signKey, verifyKey []byte, isCorrectSign bool) { + f := func(alg Algorithm, signKey, verifyKey []byte, wantErr error) { t.Helper() - const payload = `simple-string-payload` - - sign := hsSign(t, alg, signKey, payload) - - err := hsVerify(t, alg, verifyKey, payload, sign) - if err != nil && isCorrectSign { - t.Fatal(err) + signer, errSigner := NewSignerHS(alg, signKey) + if errSigner != nil { + t.Fatalf("NewSignerHS %v", errSigner) } - if err == nil && !isCorrectSign { - t.Fatal("must be not nil") + verifier, errVerifier := NewVerifierHS(alg, verifyKey) + if errVerifier != nil { + t.Fatalf("NewVerifierHS %v", errVerifier) } - } - - f(HS256, hsKey256, hsKey256, true) - f(HS384, hsKey384, hsKey384, true) - f(HS512, hsKey512, hsKey512, true) - f(HS256, hsKey256, hsKeyAnother256, false) - f(HS384, hsKey384, hsKeyAnother384, false) - f(HS512, hsKey512, hsKeyAnother512, false) + token, err := NewBuilder(signer).Build(simplePayload) + if err != nil { + t.Fatalf("Build %v", errVerifier) + } - f(HS256, hsKey256, hsKeyAnother256, false) -} + errVerify := verifier.Verify(token) + if !errors.Is(errVerify, wantErr) { + t.Errorf("want %v, got %v", wantErr, errVerify) + } + } -func hsSign(t *testing.T, alg Algorithm, key []byte, payload string) []byte { - t.Helper() + f(HS256, hsKey256, hsKey256, nil) + f(HS384, hsKey384, hsKey384, nil) + f(HS512, hsKey512, hsKey512, nil) - signer, errSigner := NewSignerHS(alg, key) - if errSigner != nil { - t.Fatalf("NewSignerHS %v", errSigner) - } + f(HS256, hsKey256, hsKeyAnother256, ErrInvalidSignature) + f(HS384, hsKey384, hsKeyAnother384, ErrInvalidSignature) + f(HS512, hsKey512, hsKeyAnother512, ErrInvalidSignature) - sign, errSign := signer.Sign([]byte(payload)) - if errSign != nil { - t.Fatalf("SignHS %v", errSign) - } - return sign + f(HS256, hsKey256, hsKeyAnother256, ErrInvalidSignature) } -func hsVerify(t *testing.T, alg Algorithm, key []byte, payload string, sign []byte) error { - t.Helper() +var ( + hsKey256 = []byte("hmac-secret-key-256") + hsKey384 = []byte("hmac-secret-key-384") + hsKey512 = []byte("hmac-secret-key-512") - verifier, errVerifier := NewVerifierHS(alg, key) - if errVerifier != nil { - t.Fatalf("NewVerifierHS %v", errVerifier) - } - return verifier.Verify([]byte(payload), sign) -} + hsKeyAnother256 = []byte("hmac-secret-key-256-another") + hsKeyAnother384 = []byte("hmac-secret-key-384-another") + hsKeyAnother512 = []byte("hmac-secret-key-512-another") +) diff --git a/algo_ps.go b/algo_ps.go index 677acf7..e0d1a04 100644 --- a/algo_ps.go +++ b/algo_ps.go @@ -7,15 +7,15 @@ import ( ) // NewSignerPS returns a new RSA-PSS-based signer. -func NewSignerPS(alg Algorithm, key *rsa.PrivateKey) (Signer, error) { +func NewSignerPS(alg Algorithm, key *rsa.PrivateKey) (*PSAlg, error) { if key == nil { return nil, ErrNilKey } - hash, opts, err := getParamsPS(alg, key.Size()) + hash, opts, err := getParamsPS(alg) if err != nil { return nil, err } - return &psAlg{ + return &PSAlg{ alg: alg, hash: hash, privateKey: key, @@ -24,15 +24,15 @@ func NewSignerPS(alg Algorithm, key *rsa.PrivateKey) (Signer, error) { } // NewVerifierPS returns a new RSA-PSS-based signer. -func NewVerifierPS(alg Algorithm, key *rsa.PublicKey) (Verifier, error) { +func NewVerifierPS(alg Algorithm, key *rsa.PublicKey) (*PSAlg, error) { if key == nil { return nil, ErrNilKey } - hash, opts, err := getParamsPS(alg, key.Size()) + hash, opts, err := getParamsPS(alg) if err != nil { return nil, err } - return &psAlg{ + return &PSAlg{ alg: alg, hash: hash, publicKey: key, @@ -40,7 +40,7 @@ func NewVerifierPS(alg Algorithm, key *rsa.PublicKey) (Verifier, error) { }, nil } -func getParamsPS(alg Algorithm, size int) (crypto.Hash, *rsa.PSSOptions, error) { +func getParamsPS(alg Algorithm) (crypto.Hash, *rsa.PSSOptions, error) { switch alg { case PS256: return crypto.SHA256, optsPS256, nil @@ -70,7 +70,7 @@ var ( } ) -type psAlg struct { +type PSAlg struct { alg Algorithm hash crypto.Hash publicKey *rsa.PublicKey @@ -78,15 +78,15 @@ type psAlg struct { opts *rsa.PSSOptions } -func (ps *psAlg) SignSize() int { +func (ps *PSAlg) SignSize() int { return ps.privateKey.Size() } -func (ps *psAlg) Algorithm() Algorithm { +func (ps *PSAlg) Algorithm() Algorithm { return ps.alg } -func (ps *psAlg) Sign(payload []byte) ([]byte, error) { +func (ps *PSAlg) Sign(payload []byte) ([]byte, error) { digest, err := hashPayload(ps.hash, payload) if err != nil { return nil, err @@ -99,14 +99,14 @@ func (ps *psAlg) Sign(payload []byte) ([]byte, error) { return signature, nil } -func (ps *psAlg) VerifyToken(token *Token) error { - if constTimeAlgEqual(token.Header().Algorithm, ps.alg) { - return ps.Verify(token.Payload(), token.Signature()) +func (ps *PSAlg) Verify(token *Token) error { + if !constTimeAlgEqual(token.Header().Algorithm, ps.alg) { + return ErrAlgorithmMismatch } - return ErrAlgorithmMismatch + return ps.verify(token.PayloadPart(), token.Signature()) } -func (ps *psAlg) Verify(payload, signature []byte) error { +func (ps *PSAlg) verify(payload, signature []byte) error { digest, err := hashPayload(ps.hash, payload) if err != nil { return err diff --git a/algo_ps_test.go b/algo_ps_test.go index eaf3fa3..9c4aa3b 100644 --- a/algo_ps_test.go +++ b/algo_ps_test.go @@ -1,81 +1,51 @@ package jwt import ( - "crypto/rand" "crypto/rsa" "errors" - "sync" "testing" ) -var ( - rsapsPublicKey256, rsapsPublicKey384, rsapsPublicKey512, rsapsPublicKey512Other *rsa.PublicKey - rsapsPrivateKey256, rsapsPrivateKey384, rsapsPrivateKey512, rsapsPrivateKey512Other *rsa.PrivateKey - - rsapsPublicKey256Another, rsapsPublicKey384Another, rsapsPublicKey512Another *rsa.PublicKey - rsapsPrivateKey256Another, rsapsPrivateKey384Another, rsapsPrivateKey512Another *rsa.PrivateKey -) - -var initPSKeysOnce sync.Once - -func initPSKeys() { - initPSKeysOnce.Do(func() { - f := func(bits int) (*rsa.PrivateKey, *rsa.PublicKey) { - privKey, err := rsa.GenerateKey(rand.Reader, bits) - if err != nil { - panic(err) - } - return privKey, &privKey.PublicKey - } - - rsapsPrivateKey256, rsapsPublicKey256 = f(256 * 8) - rsapsPrivateKey384, rsapsPublicKey384 = f(384 * 8) - rsapsPrivateKey512, rsapsPublicKey512 = f(512 * 8) - rsapsPrivateKey512Other, rsapsPublicKey512Other = f(256 * 8) - - rsapsPrivateKey256Another, rsapsPublicKey256Another = f(256 * 8) - rsapsPrivateKey384Another, rsapsPublicKey384Another = f(384 * 8) - rsapsPrivateKey512Another, rsapsPublicKey512Another = f(512 * 8) - }) -} - func TestPS(t *testing.T) { - initPSKeys() - - f := func(alg Algorithm, privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey, isCorrectSign bool) { + f := func(alg Algorithm, privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey, wantErr error) { t.Helper() - const payload = `simple-string-payload` - - sign := psSign(t, alg, privateKey, payload) + signer, errSigner := NewSignerPS(alg, privateKey) + if errSigner != nil { + t.Fatalf("NewSignerPS %v", errSigner) + } + verifier, errVerifier := NewVerifierPS(alg, publicKey) + if errVerifier != nil { + t.Fatalf("NewVerifierPS %v", errVerifier) + } - err := psVerify(t, alg, publicKey, payload, sign) - if err != nil && isCorrectSign { - t.Error(err) + token, err := NewBuilder(signer).Build(simplePayload) + if err != nil { + t.Fatalf("Build %v", errVerifier) } - if err == nil && !isCorrectSign { - t.Error("must be not nil") + + errVerify := verifier.Verify(token) + if !errors.Is(errVerify, wantErr) { + t.Errorf("want %v, got %v", wantErr, errVerify) } } - f(PS256, rsapsPrivateKey256, rsapsPublicKey256, true) - f(PS384, rsapsPrivateKey384, rsapsPublicKey384, true) - f(PS512, rsapsPrivateKey512, rsapsPublicKey512, true) - f(PS512, rsapsPrivateKey512Other, rsapsPublicKey512Other, true) + f(PS256, rsapsPrivateKey256, rsapsPublicKey256, nil) + f(PS384, rsapsPrivateKey384, rsapsPublicKey384, nil) + f(PS512, rsapsPrivateKey512, rsapsPublicKey512, nil) + f(PS512, rsapsPrivateKey512Other, rsapsPublicKey512Other, nil) - f(PS256, rsapsPrivateKey256, rsapsPublicKey256Another, false) - f(PS384, rsapsPrivateKey384, rsapsPublicKey384Another, false) - f(PS512, rsapsPrivateKey512, rsapsPublicKey512Another, false) + f(PS256, rsapsPrivateKey256, rsapsPublicKey256Another, ErrInvalidSignature) + f(PS384, rsapsPrivateKey384, rsapsPublicKey384Another, ErrInvalidSignature) + f(PS512, rsapsPrivateKey512, rsapsPublicKey512Another, ErrInvalidSignature) - f(PS256, rsapsPrivateKey256Another, rsapsPublicKey256, false) - f(PS384, rsapsPrivateKey384Another, rsapsPublicKey384, false) - f(PS512, rsapsPrivateKey512Another, rsapsPublicKey512, false) - f(PS512, rsapsPrivateKey512Another, rsapsPublicKey512Other, false) + f(PS256, rsapsPrivateKey256Another, rsapsPublicKey256, ErrInvalidSignature) + f(PS384, rsapsPrivateKey384Another, rsapsPublicKey384, ErrInvalidSignature) + f(PS512, rsapsPrivateKey512Another, rsapsPublicKey512, ErrInvalidSignature) + f(PS512, rsapsPrivateKey512Another, rsapsPublicKey512Other, ErrInvalidSignature) } func TestPS_BadKeys(t *testing.T) { - initPSKeys() - f := func(err, wantErr error) { t.Helper() @@ -95,27 +65,22 @@ func TestPS_BadKeys(t *testing.T) { f(getVerifierError(NewVerifierPS("boo", rsapsPublicKey384)), ErrUnsupportedAlg) } -func psSign(t *testing.T, alg Algorithm, privateKey *rsa.PrivateKey, payload string) []byte { - t.Helper() - - signer, errSigner := NewSignerPS(alg, privateKey) - if errSigner != nil { - t.Fatalf("NewSignerPS %v", errSigner) - } - - sign, errSign := signer.Sign([]byte(payload)) - if errSign != nil { - t.Fatalf("SignPS %v", errSign) - } - return sign -} - -func psVerify(t *testing.T, alg Algorithm, publicKey *rsa.PublicKey, payload string, sign []byte) error { - t.Helper() - - verifier, errVerifier := NewVerifierPS(alg, publicKey) - if errVerifier != nil { - t.Fatalf("NewVerifierPS %v", errVerifier) - } - return verifier.Verify([]byte(payload), sign) -} +var ( + rsapsPrivateKey256 = mustParseRSAKey(testKeyRSA1024) + rsapsPrivateKey384 = mustParseRSAKey(testKeyRSA2048) + rsapsPrivateKey512 = mustParseRSAKey(testKeyRSA4096) + rsapsPrivateKey512Other = mustParseRSAKey(testKeyRSA4096Other) + + rsapsPublicKey256 = &rsapsPrivateKey256.PublicKey + rsapsPublicKey384 = &rsapsPrivateKey384.PublicKey + rsapsPublicKey512 = &rsapsPrivateKey512.PublicKey + rsapsPublicKey512Other = &rsapsPrivateKey512Other.PublicKey + + rsapsPrivateKey256Another = mustParseRSAKey(testKeyRSA1024Another) + rsapsPrivateKey384Another = mustParseRSAKey(testKeyRSA2048Another) + rsapsPrivateKey512Another = mustParseRSAKey(testKeyRSA4096Another) + + rsapsPublicKey256Another = &rsapsPrivateKey256Another.PublicKey + rsapsPublicKey384Another = &rsapsPrivateKey384Another.PublicKey + rsapsPublicKey512Another = &rsapsPrivateKey512Another.PublicKey +) diff --git a/algo_rs.go b/algo_rs.go index 393e80b..8ec274d 100644 --- a/algo_rs.go +++ b/algo_rs.go @@ -7,38 +7,40 @@ import ( ) // NewSignerRS returns a new RSA-based signer. -func NewSignerRS(alg Algorithm, key *rsa.PrivateKey) (Signer, error) { +func NewSignerRS(alg Algorithm, key *rsa.PrivateKey) (*RSAlg, error) { if key == nil { return nil, ErrNilKey } - hash, err := getHashRS(alg, key.Size()) + hash, err := getHashRS(alg) if err != nil { return nil, err } - return &rsAlg{ + return &RSAlg{ alg: alg, hash: hash, privateKey: key, + publicKey: nil, }, nil } // NewVerifierRS returns a new RSA-based verifier. -func NewVerifierRS(alg Algorithm, key *rsa.PublicKey) (Verifier, error) { +func NewVerifierRS(alg Algorithm, key *rsa.PublicKey) (*RSAlg, error) { if key == nil { return nil, ErrNilKey } - hash, err := getHashRS(alg, key.Size()) + hash, err := getHashRS(alg) if err != nil { return nil, err } - return &rsAlg{ - alg: alg, - hash: hash, - publicKey: key, + return &RSAlg{ + alg: alg, + hash: hash, + privateKey: nil, + publicKey: key, }, nil } -func getHashRS(alg Algorithm, size int) (crypto.Hash, error) { +func getHashRS(alg Algorithm) (crypto.Hash, error) { var hash crypto.Hash switch alg { case RS256: @@ -53,22 +55,22 @@ func getHashRS(alg Algorithm, size int) (crypto.Hash, error) { return hash, nil } -type rsAlg struct { +type RSAlg struct { alg Algorithm hash crypto.Hash publicKey *rsa.PublicKey privateKey *rsa.PrivateKey } -func (rs *rsAlg) Algorithm() Algorithm { +func (rs *RSAlg) Algorithm() Algorithm { return rs.alg } -func (rs *rsAlg) SignSize() int { +func (rs *RSAlg) SignSize() int { return rs.privateKey.Size() } -func (rs *rsAlg) Sign(payload []byte) ([]byte, error) { +func (rs *RSAlg) Sign(payload []byte) ([]byte, error) { digest, err := hashPayload(rs.hash, payload) if err != nil { return nil, err @@ -81,14 +83,14 @@ func (rs *rsAlg) Sign(payload []byte) ([]byte, error) { return signature, nil } -func (rs *rsAlg) VerifyToken(token *Token) error { - if constTimeAlgEqual(token.Header().Algorithm, rs.alg) { - return rs.Verify(token.Payload(), token.Signature()) +func (rs *RSAlg) Verify(token *Token) error { + if !constTimeAlgEqual(token.Header().Algorithm, rs.alg) { + return ErrAlgorithmMismatch } - return ErrAlgorithmMismatch + return rs.verify(token.PayloadPart(), token.Signature()) } -func (rs *rsAlg) Verify(payload, signature []byte) error { +func (rs *RSAlg) verify(payload, signature []byte) error { digest, err := hashPayload(rs.hash, payload) if err != nil { return err diff --git a/algo_rs_test.go b/algo_rs_test.go index 191f9c8..085ef0d 100644 --- a/algo_rs_test.go +++ b/algo_rs_test.go @@ -1,81 +1,51 @@ package jwt import ( - "crypto/rand" "crypto/rsa" "errors" - "sync" "testing" ) -var ( - rsaPublicKey256, rsaPublicKey384, rsaPublicKey512, rsaPublicKey512Other *rsa.PublicKey - rsaPrivateKey256, rsaPrivateKey384, rsaPrivateKey512, rsaPrivateKey512Other *rsa.PrivateKey - - rsaPublicKey256Another, rsaPublicKey384Another, rsaPublicKey512Another *rsa.PublicKey - rsaPrivateKey256Another, rsaPrivateKey384Another, rsaPrivateKey512Another *rsa.PrivateKey -) - -var initRSKeysOnce sync.Once - -func initRSKeys() { - initRSKeysOnce.Do(func() { - f := func(bits int) (*rsa.PrivateKey, *rsa.PublicKey) { - privKey, err := rsa.GenerateKey(rand.Reader, bits) - if err != nil { - panic(err) - } - return privKey, &privKey.PublicKey - } - - rsaPrivateKey256, rsaPublicKey256 = f(256 * 8) - rsaPrivateKey384, rsaPublicKey384 = f(384 * 8) - rsaPrivateKey512, rsaPublicKey512 = f(512 * 8) - rsaPrivateKey512Other, rsaPublicKey512Other = f(256 * 8) // 256 just for the example - - rsaPrivateKey256Another, rsaPublicKey256Another = f(256 * 8) - rsaPrivateKey384Another, rsaPublicKey384Another = f(384 * 8) - rsaPrivateKey512Another, rsaPublicKey512Another = f(512 * 8) - }) -} - func TestRS(t *testing.T) { - initRSKeys() - - f := func(alg Algorithm, privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey, isCorrectSign bool) { + f := func(alg Algorithm, privateKey *rsa.PrivateKey, publicKey *rsa.PublicKey, wantErr error) { t.Helper() - const payload = `simple-string-payload` - - sign := rsSign(t, alg, privateKey, payload) + signer, errSigner := NewSignerRS(alg, privateKey) + if errSigner != nil { + t.Fatalf("NewSignerRS %v", errSigner) + } + verifier, errVerifier := NewVerifierRS(alg, publicKey) + if errVerifier != nil { + t.Fatalf("NewVerifierRS %v", errVerifier) + } - err := rsVerify(t, alg, publicKey, payload, sign) - if err != nil && isCorrectSign { - t.Error(err) + token, err := NewBuilder(signer).Build(simplePayload) + if err != nil { + t.Fatalf("Build %v", errVerifier) } - if err == nil && !isCorrectSign { - t.Error("must be not nil") + + errVerify := verifier.Verify(token) + if !errors.Is(errVerify, wantErr) { + t.Errorf("want %v, got %v", wantErr, errVerify) } } - f(RS256, rsaPrivateKey256, rsaPublicKey256, true) - f(RS384, rsaPrivateKey384, rsaPublicKey384, true) - f(RS512, rsaPrivateKey512, rsaPublicKey512, true) - f(RS512, rsaPrivateKey512Other, rsaPublicKey512Other, true) + f(RS256, rsaPrivateKey256, rsaPublicKey256, nil) + f(RS384, rsaPrivateKey384, rsaPublicKey384, nil) + f(RS512, rsaPrivateKey512, rsaPublicKey512, nil) + f(RS512, rsaPrivateKey512Other, rsaPublicKey512Other, nil) - f(RS256, rsaPrivateKey256, rsaPublicKey256Another, false) - f(RS384, rsaPrivateKey384, rsaPublicKey384Another, false) - f(RS512, rsaPrivateKey512, rsaPublicKey512Another, false) + f(RS256, rsaPrivateKey256, rsaPublicKey256Another, ErrInvalidSignature) + f(RS384, rsaPrivateKey384, rsaPublicKey384Another, ErrInvalidSignature) + f(RS512, rsaPrivateKey512, rsaPublicKey512Another, ErrInvalidSignature) - f(RS256, rsaPrivateKey256Another, rsaPublicKey256, false) - f(RS384, rsaPrivateKey384Another, rsaPublicKey384, false) - f(RS512, rsaPrivateKey512Another, rsaPublicKey512, false) - f(RS512, rsaPrivateKey512Other, rsaPublicKey512, false) + f(RS256, rsaPrivateKey256Another, rsaPublicKey256, ErrInvalidSignature) + f(RS384, rsaPrivateKey384Another, rsaPublicKey384, ErrInvalidSignature) + f(RS512, rsaPrivateKey512Another, rsaPublicKey512, ErrInvalidSignature) + f(RS512, rsaPrivateKey512Other, rsaPublicKey512, ErrInvalidSignature) } func TestRS_BadKeys(t *testing.T) { - initRSKeys() - f := func(err, wantErr error) { t.Helper() @@ -93,30 +63,280 @@ func TestRS_BadKeys(t *testing.T) { f(getVerifierError(NewVerifierRS(RS384, nil)), ErrNilKey) f(getVerifierError(NewVerifierRS(RS512, nil)), ErrNilKey) f(getVerifierError(NewVerifierRS("boo", rsaPublicKey384)), ErrUnsupportedAlg) - } -func rsSign(t *testing.T, alg Algorithm, privateKey *rsa.PrivateKey, payload string) []byte { - t.Helper() +var ( + rsaPrivateKey256 = mustParseRSAKey(testKeyRSA1024) + rsaPrivateKey384 = mustParseRSAKey(testKeyRSA2048) + rsaPrivateKey512 = mustParseRSAKey(testKeyRSA4096) + rsaPrivateKey512Other = mustParseRSAKey(testKeyRSA4096Other) - signer, errSigner := NewSignerRS(alg, privateKey) - if errSigner != nil { - t.Fatalf("NewSignerRS %v", errSigner) - } + rsaPublicKey256 = &rsaPrivateKey256.PublicKey + rsaPublicKey384 = &rsaPrivateKey384.PublicKey + rsaPublicKey512 = &rsaPrivateKey512.PublicKey + rsaPublicKey512Other = &rsaPrivateKey512Other.PublicKey - sign, errSign := signer.Sign([]byte(payload)) - if errSign != nil { - t.Fatalf("SignRS %v", errSign) - } - return sign -} + rsaPrivateKey256Another = mustParseRSAKey(testKeyRSA1024Another) + rsaPrivateKey384Another = mustParseRSAKey(testKeyRSA2048Another) + rsaPrivateKey512Another = mustParseRSAKey(testKeyRSA4096Another) -func rsVerify(t *testing.T, alg Algorithm, publicKey *rsa.PublicKey, payload string, sign []byte) error { - t.Helper() + rsaPublicKey256Another = &rsaPrivateKey256Another.PublicKey + rsaPublicKey384Another = &rsaPrivateKey384Another.PublicKey + rsaPublicKey512Another = &rsaPrivateKey512Another.PublicKey +) - verifier, errVerifier := NewVerifierRS(alg, publicKey) - if errVerifier != nil { - t.Fatalf("NewVerifierRS %v", errVerifier) - } - return verifier.Verify([]byte(payload), sign) -} +// To generate keys: +// RS256 +// openssl genrsa -out rs256-1024-private.rsa 1024 +// RS384 +// openssl genrsa -out rs384-2048-private.rsa 2048 +// RS512 +// openssl genrsa -out rs512-4096-private.rsa 4096 +// +const ( + testKeyRSA1024 = `-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDZY2zMlYH8Onz1eAxYc6IdyPT5AmVsae/Q2+wuhFcf6DrGRrBj +cHDFBuVYm7zwlCdqjfGIxN4iYLG+TCRHhwY2dGCtDysgsQE4QiLZom5xRLW1CSJV +Z0rmL3/wmWdrCJQd7FMreaqwZyHMOAt/jHEZGMz09LpVL/OtOHxvwhmY8wIDAQAB +AoGAOm0cesfcDbxyhU7kkolRkwvFu39HXyIB0HKrQb1T5UF3On8ZPUClLm8yCOSi +nU5UYbEQr5+pzDeMzgaM4aSKqG0z13BTHwDF5nNXz7gPj8VOEfzKP3tdCh9vqlNQ +VbYc4/JSXBI+JbreUB3NaqFh4nCFy5vhYcs+64gAYdr3ioECQQDzfNLUgqhgcOlO +1Q4+xUimuVp7VAPrkVCQkdff57QMv6mBrBvoRrciFx50l1pkYeEZyBMct1Fokns9 +JyPrafrTAkEA5I9BgSscQjDrUSjzPjtfQIGNd9s4brlcAGW+OOwm2+8NCLCPbam8 +nplUQxjdsj1/SyTpudMz9s7T4D5G0cDVYQJBANZO0HZRRggUeZV0OySOmkJ8tCIG +saieb02/wEUH+FacP4KtzKZlz3yG4rx2Fw5xhCIgEopc459qBmSt1ZS35BcCQEqu +XHL+SR9/qIQ+YyyEbd0/95+gK9JSErO2iu9Cinf2pkWem17zxUP1SckayOXCnmNJ +Tm1/i00ry6NL9gv3fEECQQCOsRAgxrlBxAmSqxdS9o2CF1gh5ngb/c7GcRrsPXTZ +lCqhuIGtMXpXUngLV2suQHVBIg2yuKBuVWRKFgVgOLZj +-----END RSA PRIVATE KEY-----` + + testKeyRSA2048 = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtuc37fBR8CjO6jSeV7eYW5yEwQ3ltzYQHjS5SQlR/MkhFrzX +Y36bB3eSrkv+cIz0PmreFvIDBR1qpJgnEcVyM1DY1imzB48m6TGmz12A5jQEo1T3 +KRLtjX6pmbEk/ZPVg8C8Rk+n+L0//w2Cz3ZrttdPoMcv0jZPqf2JM57yuTHm7vVC +6gA22iQbg8kD7zhP6iQqs4+AyfFY1mwxhX/GgOrXYRksfNSt+GvOLkzmyZ68LLMy +gBKysIU4FJzm4L9OacPSkeJ3jvQmlsFfPpKvoXohxh1n/NS+ylyPtf/GPQ5AQMgg +Anu8QibDhZd7TMrtHjz3zhT8ed0ikV5LW06f4QIDAQABAoIBAQCEn5o1hRnU+7I1 +rxvV3QG0BAVa+xnDxIbhcDeeFw0FME427416zpXZT0Hj8qS3te1wyQrrNmcsMU1f +thg2UaZiQVBJ0ojHhWygkOk02ccapUNrr7NcbCYmgF64W/PHj4e19m5OyXmx6oBa +D9D3YBXwyaUqnuQ1GD6hs37mGG5GDAhRa7PONCNmNbIVP91Ria1t9locZ3tW2Cv2 +8OQuFh4xM/pYCeTcFgAjD8So9i+394DvG+rlW3bE1cXirr01B4oiD6Uxj/4ITQWN +JK69E7mewbLdhDfxl88rK+Trt9n5nxCC5FCQFUvV5H2bakXw18drY1+S83RLylu8 +QzblDZTBAoGBAN3Dhn8y/770C6DcUUxq08aBQGbPA23X5RyXyHCIRTSFXIAoHiDG +3v1Br5V1UqzNHJ1Yr1Oc3eSgX2cnE+UEk9WQqATgOchZjq9fwEqj7SRcwFi1/w2m +azKU+p3VoxBDGAwLjR2EjQVxTNBzAqKN6Y4Obe0N1kfDirpImun5uZlJAoGBANMj +27peZo+vs57SynnHZxyplaACulQ2uD2mbZuIaHjfJiKfeAK7CP20ruq9kAx0Lmv1 +D3KOmDvbFPpCtQaaF9XSFyXN2/5F+j9HoKWT0Zcr3NHXv2Gglqe0UBuQVo946epO +25t2LmPWYdSwnMepm7Wdga2cnVQvWaUQPfqe9ynZAoGAf1JyFve14/GD96BmwOQY +oMC2tBAo3Nj2fvsaJpnmeegTmrTtz02+21LqN3o7tGCzDBfN2ciXkVsOS1AhWPzO +z/AUt2/xXPkmNcRYx8HkNltWR9h+Dl088LaeSR5OV9jdppS/OXJP8Q8C1i8iFSg7 +LsdUD6VBIMTBEoD7CFjLWZkCgYAz4QCxZ1aeAZcJ9FNJ3uQIF+cq3OC5poRYqZcO ++1JoOLRfF33edeR8qjO8e/10AewSHLHB/SWMt4UNBO0EBULMCAYmBNIERV96wvfH +F24NTfrBGNjufQ3ngReZ3jpWoGghaWTuavh1EY0SPJ8ZNCSNWHkvlmbV85h8RWRl +O0AR6QKBgAebaRIrmV8mEEk5Ku7al/y/Wx0V0QGNwlSnXmHKlR3fQP7BhGnr0ozZ +JS/w9ZdEsPa+4h7Lfum3vNYA7COzEzwIoYJBJG1r8H2h6PCdhXc2448XXobWozay +5dk2svfo9r7fY6We5cfDYHbOfnaQ+/FDvhkHdobzEYMtt/KPe9tk +-----END RSA PRIVATE KEY-----` + + testKeyRSA4096 = `-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- +` + + testKeyRSA4096Other = `-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- +` + + testKeyRSA1024Another = `-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDCzQ4MMppUkCXTi/BjPWO2gLnaVmPhyMdo7rnccfoBnH5lCTdY +x2aK2vNkVVLi4w8zITBXAXwKB7O5iQaaXImnUD2KPReRKbyGbvkGwQGpU1UsZjzZ +uPFfbDtdWr+d2CxQUdPjKu886Lad4BsJFWSJYt06K1byYCGAYyN5hosmOQIDAQAB +AoGAO5EIYqJ2nrUVXALGlxIGk5/5NNKF6FzE3UlifA4+LI/19l9DFVqj+IHLOzr8 +BXT5COF1LqW9kDOauXk1E66ISJ/vAFYvS+hIugKDqUhpBTpgPa2nyJGOjUHScvIP +sVdo1unpYU40bvhhy7HD4kwQvohYq9w5KW732jpqPJK5TKECQQD3XpZGlXAJ+O/5 +p97Xwt6Rz7peG1Aqx3TlzVUvOPCXT8rnycEub0j52sYZUwg3dtf763R385pJmBJs +TJc2oN9PAkEAyZjyDqGUM6IJy7O55Ylsy3dxply7NIym+BM4p8MiEwzHZb5dXgX3 +pxuPlLX3DojlGWNcLB5+gw1ZSq9Y5dz/9wJBAOQoQtUBemBIUhbj5d795sl4Xn30 +FUIPy9s1Qy+WBhqZxx148gxBKn8BcRvkgLyfieDasAb/Ebx1XfCzx/jj8nMCQBNr +WT3RkL4ciMcHjAuxXjqHSfpVim74cYkKCPYYFOsy2u5RFRtehcmiHQWdNaw/wZnd +eV6CnXswSP6pv219CWcCQBv3wKhme0RkuPuyG4MUFFeHxOcilasHx/nWiz8U90Tm +hP30X1iUlekEFj/2oneT6qWqtH4nVX18/WehPQoDoLg= +-----END RSA PRIVATE KEY-----` + + testKeyRSA2048Another = `-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA8NBRypbuyT1o2p7Ze94kmZTrwu2TsqZ1u7BOcY97xn6cc7/e +c9aZI+S4Ure57XNvKQAZlULWjWhEfY8vhP1m2hDzVCV0DnNRCPmMJxx212b2iTmA +1IsMmRYFHOYgVVUdx5QzS1xIQMZgyLP++CBkYJXZZCC1MBqyW93BkBcNzt0+70ZT +mMpXOYKoq/pFcxVMllKY41JCcDqpKcJnSmWyS+DQX5X4CcNecXxCMoL7WGeMVrng +7NTFJmv4Iyh19/WRERqQUqlPPQoWd0Wrw/Ih+p38PlxvdxxcGIgG8gZC1eZ441MR +4KeHEnx7nQ08TtzdsTULYlx3kM173h1yI+HuBwIDAQABAoIBAQCyX5w2E9aL+ZDR +Xxh5R/KUUFrR6Giey+4pOE7ijwV/4gjBND3yT+LfU2u02aI+4GJWXFyW0wtZcwJI +fucT+x9UJ3oVuihdC83ad/34en0M0JeMzas/xD9wpX7kCRGqI4ILcxsLly9ty4Ol +Jq6V3Gh9ooGESTXsi9nRclEOCgWQU6F8BeDGbI19aqkFi67wZqvOYrlUXfznRwQQ +iaffaeh+wH5qp79dd+MoSPJLmhuhNH6Q/T70tVqTvlslufcro1/7YYuq9X2/IO+u +O1Nd1/nyT46xYQ16HqLdH2KPN2jsmbWFCMM4leTbyk18ldDnU3LG7BMwwoW7vemE +gU8KuX4BAoGBAP4zMVT+M1421fXUTyxK1ViQprdqT5zksFwK0cMdy1upE0EFqUrr +TtN5mao+7rGFp7R/0xuVSwYs+LX7jsrRPXn5JgB2JdPg9UakdKkULAfGVCLJouXm +/32C9YlFuqPjJWxr5Ndb1aqvPNfIvsfmys1O+GJ39x9R+iFezvuKN2BvAoGBAPKE +3E9fSWjXg9N+y2QazeU6wJjJYhIGtceuTwPPW1n3IfOzgB1QHXZhH7YM07OoI2jF +NFBM99ygjdfRbKCosEQoUQCF78avHYJJDhdPhjAWiaIZg7X4gfgWqEMJ0SWXyCAM +cxQ0XEC0AHocWNipWv8zVFEC62K3omMXS/9leefpAoGAB/eGxkkpRvyk/A1pZdP6 +l8oAz6LPV/V66YeVR245n2fPKKyKv8RcNhiLjmBmjr3HocqXzTeCoHDsYpe9w/GG +4bnDTSRmzxsv1MT2uw3cy2mV3XlAV8BDpaVjGKhMzzIhTCKdi3pfWfggCgtKn21G +UeT1t/BWmG6zTjRwfEW6spUCgYAUsXF69E53O6xr523DZOYcoR696rELiLcKCr2D +PbY1vviOqspLtgJNj4v9JKsLsVUUI3+LOoYLtUdlGuGB8+LWbfo7aTJEabzC2Sjy +pD526/Vid3rdlA7C9Gv3DGdkJcdVtLo9Bxq4CqPfx3ttQUYacG7JWs5q5fBdNCev +6yCzwQKBgHZRiC82Bzd10OgIL4WadlNphmMnGgROgNhwBu2bd5loPc+26omBAVtC +mQ9Ug7u6QOshlvxmqrgRFlWkLAwozqvS6RC4yru8FRqYnmtW7QgxO1pOj9VEzHSw +iugbqlkWvaTnn5JZoHZ+60PZc8Z4UJvzi0/h9ksnWhp5l6u1KBmc +-----END RSA PRIVATE KEY-----` + + testKeyRSA4096Another = `-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY-----` +) diff --git a/algo_test.go b/algo_test.go index ba18407..f0f4a7a 100644 --- a/algo_test.go +++ b/algo_test.go @@ -4,15 +4,9 @@ import ( "testing" ) -func initKeys() { - initRSKeys() - initPSKeys() - initESKeys() -} +const simplePayload = `simple-string-payload` func TestSignerAlg(t *testing.T) { - initKeys() - f := func(s Signer, want Algorithm) { t.Helper() if alg := s.Algorithm(); alg != want { @@ -38,8 +32,6 @@ func TestSignerAlg(t *testing.T) { } func TestVerifierAlg(t *testing.T) { - initKeys() - f := func(v Verifier, want Algorithm) { t.Helper() if alg := v.Algorithm(); alg != want { @@ -65,8 +57,6 @@ func TestVerifierAlg(t *testing.T) { } func TestSignerBadParams(t *testing.T) { - initKeys() - f := func(_ Signer, err error) { t.Helper() if err == nil { @@ -91,8 +81,6 @@ func TestSignerBadParams(t *testing.T) { } func TestVerifierBadParams(t *testing.T) { - initKeys() - f := func(_ Verifier, err error) { t.Helper() if err == nil { diff --git a/audience.go b/audience.go index 6b311a0..4d47715 100644 --- a/audience.go +++ b/audience.go @@ -3,7 +3,7 @@ package jwt import "encoding/json" // Audience is a special claim that be a single string or an array of strings -// see RFC 7519. +// See: https://tools.ietf.org/html/rfc7519 type Audience []string // MarshalJSON implements a marshaling function for "aud" claim. diff --git a/build.go b/build.go index c7205a5..cca3a48 100644 --- a/build.go +++ b/build.go @@ -25,16 +25,6 @@ type Builder struct { headerRaw []byte } -// BuildBytes is used to create and encode JWT with a provided claims. -func BuildBytes(signer Signer, claims interface{}) ([]byte, error) { - return NewBuilder(signer).BuildBytes(claims) -} - -// Build is used to create and encode JWT with a provided claims. -func Build(signer Signer, claims interface{}) (*Token, error) { - return NewBuilder(signer).Build(claims) -} - // NewBuilder returns new instance of Builder. func NewBuilder(signer Signer, opts ...BuilderOption) *Builder { b := &Builder{ @@ -53,15 +43,6 @@ func NewBuilder(signer Signer, opts ...BuilderOption) *Builder { return b } -// BuildBytes used to create and encode JWT with a provided claims. -func (b *Builder) BuildBytes(claims interface{}) ([]byte, error) { - token, err := b.Build(claims) - if err != nil { - return nil, err - } - return token.Raw(), nil -} - // Build used to create and encode JWT with a provided claims. // If claims param is of type []byte or string then it's treated as a marshaled JSON. // In other words you can pass already marshaled claims. @@ -126,7 +107,7 @@ func encodeHeader(header Header) []byte { } // another algorithm? encode below } - // returned err is always nil, see *Header.MarshalJSON + // returned err is always nil, see jwt.Header.MarshalJSON buf, _ := header.MarshalJSON() encoded := make([]byte, b64EncodedLen(len(buf))) diff --git a/build_test.go b/build_test.go index 6b29624..8fa4cde 100644 --- a/build_test.go +++ b/build_test.go @@ -6,17 +6,15 @@ import ( ) func TestBuild(t *testing.T) { - initKeys() - f := func(signer Signer, verifier Verifier, claims interface{}) { t.Helper() - token, err := Build(signer, claims) + token, err := NewBuilder(signer).Build(claims) if err != nil { t.Error(err) } - errVerify := verifier.Verify(token.Payload(), token.Signature()) + errVerify := verifier.Verify(token) if errVerify != nil { t.Error(errVerify) } @@ -94,18 +92,16 @@ func TestBuild(t *testing.T) { } func TestBuildHeader(t *testing.T) { - initKeys() - f := func(signer Signer, want string, opts ...BuilderOption) { t.Helper() - token, err := NewBuilder(signer, opts...).Build(&StandardClaims{}) + token, err := NewBuilder(signer, opts...).Build(&RegisteredClaims{}) if err != nil { t.Error(err) } - want = strToBase64(want) - raw := string(token.RawHeader()) + want = bytesToBase64([]byte(want)) + raw := string(token.HeaderPart()) if raw != want { t.Errorf("\nwant %v,\n got %v", want, raw) } @@ -172,7 +168,7 @@ func TestBuildClaims(t *testing.T) { t.Fatal(err) } - errVerify := v.Verify(token.Payload(), token.Signature()) + errVerify := v.Verify(token) if errVerify != nil { t.Fatal(errVerify) } @@ -214,7 +210,7 @@ func TestBuildMalformed(t *testing.T) { f := func(signer Signer, claims interface{}) { t.Helper() - _, err := Build(signer, claims) + _, err := NewBuilder(signer).Build(claims) if err == nil { t.Error("want err, got nil") } diff --git a/claims.go b/claims.go index f0d4a07..209b9db 100644 --- a/claims.go +++ b/claims.go @@ -5,13 +5,10 @@ import ( "time" ) -// RegisteredClaims will replace StandardClaims in v4. -type RegisteredClaims = StandardClaims - -// StandardClaims represents claims for JWT. +// RegisteredClaims represents claims for JWT. // See: https://tools.ietf.org/html/rfc7519#section-4.1 // -type StandardClaims struct { +type RegisteredClaims struct { // ID claim provides a unique identifier for the JWT. ID string `json:"jti,omitempty"` @@ -41,7 +38,7 @@ type StandardClaims struct { } // IsForAudience reports whether token has a given audience. -func (sc *StandardClaims) IsForAudience(audience string) bool { +func (sc *RegisteredClaims) IsForAudience(audience string) bool { for _, aud := range sc.Audience { if constTimeEqual(aud, audience) { return true @@ -51,37 +48,37 @@ func (sc *StandardClaims) IsForAudience(audience string) bool { } // IsIssuer reports whether token has a given issuer. -func (sc *StandardClaims) IsIssuer(issuer string) bool { +func (sc *RegisteredClaims) IsIssuer(issuer string) bool { return constTimeEqual(sc.Issuer, issuer) } // IsSubject reports whether token has a given subject. -func (sc *StandardClaims) IsSubject(subject string) bool { +func (sc *RegisteredClaims) IsSubject(subject string) bool { return constTimeEqual(sc.Subject, subject) } // IsID reports whether token has a given id. -func (sc *StandardClaims) IsID(id string) bool { +func (sc *RegisteredClaims) IsID(id string) bool { return constTimeEqual(sc.ID, id) } // IsValidExpiresAt reports whether a token isn't expired at a given time. -func (sc *StandardClaims) IsValidExpiresAt(now time.Time) bool { +func (sc *RegisteredClaims) IsValidExpiresAt(now time.Time) bool { return sc.ExpiresAt == nil || sc.ExpiresAt.After(now) } // IsValidNotBefore reports whether a token isn't used before a given time. -func (sc *StandardClaims) IsValidNotBefore(now time.Time) bool { +func (sc *RegisteredClaims) IsValidNotBefore(now time.Time) bool { return sc.NotBefore == nil || sc.NotBefore.Before(now) } // IsValidIssuedAt reports whether a token was created before a given time. -func (sc *StandardClaims) IsValidIssuedAt(now time.Time) bool { +func (sc *RegisteredClaims) IsValidIssuedAt(now time.Time) bool { return sc.IssuedAt == nil || sc.IssuedAt.Before(now) } // IsValidAt reports whether a token is valid at a given time. -func (sc *StandardClaims) IsValidAt(now time.Time) bool { +func (sc *RegisteredClaims) IsValidAt(now time.Time) bool { return sc.IsValidExpiresAt(now) && sc.IsValidNotBefore(now) && sc.IsValidIssuedAt(now) } diff --git a/claims_test.go b/claims_test.go index 2c6f30e..07b545d 100644 --- a/claims_test.go +++ b/claims_test.go @@ -6,7 +6,7 @@ import ( ) func TestClaims(t *testing.T) { - f := func(claims *StandardClaims, f func(claims *StandardClaims) bool, want bool) { + f := func(claims *RegisteredClaims, f func(claims *RegisteredClaims) bool, want bool) { t.Helper() got := f(claims) @@ -16,43 +16,43 @@ func TestClaims(t *testing.T) { } f( - &StandardClaims{Audience: Audience([]string{"winner"})}, - func(claims *StandardClaims) bool { + &RegisteredClaims{Audience: Audience([]string{"winner"})}, + func(claims *RegisteredClaims) bool { return claims.IsForAudience("winner") }, true, ) f( - &StandardClaims{Audience: Audience([]string{"oops", "winner"})}, - func(claims *StandardClaims) bool { + &RegisteredClaims{Audience: Audience([]string{"oops", "winner"})}, + func(claims *RegisteredClaims) bool { return claims.IsForAudience("winner") }, true, ) f( - &StandardClaims{Audience: Audience([]string{"w0nner"})}, - func(claims *StandardClaims) bool { + &RegisteredClaims{Audience: Audience([]string{"w0nner"})}, + func(claims *RegisteredClaims) bool { return claims.IsForAudience("winner") }, false, ) f( - &StandardClaims{ID: "test-id"}, - func(claims *StandardClaims) bool { + &RegisteredClaims{ID: "test-id"}, + func(claims *RegisteredClaims) bool { return claims.IsID("test-id") }, true, ) f( - &StandardClaims{Issuer: "test-issuer"}, - func(claims *StandardClaims) bool { + &RegisteredClaims{Issuer: "test-issuer"}, + func(claims *RegisteredClaims) bool { return claims.IsIssuer("test-issuer") }, true, ) f( - &StandardClaims{Subject: "test-subject"}, - func(claims *StandardClaims) bool { + &RegisteredClaims{Subject: "test-subject"}, + func(claims *RegisteredClaims) bool { return claims.IsSubject("test-subject") }, true, @@ -63,7 +63,7 @@ func TestTimingClaims(t *testing.T) { before := time.Now() after := before.Add(time.Minute) - f := func(claims *StandardClaims, f func(claims *StandardClaims) bool, want bool) { + f := func(claims *RegisteredClaims, f func(claims *RegisteredClaims) bool, want bool) { t.Helper() got := f(claims) @@ -74,22 +74,22 @@ func TestTimingClaims(t *testing.T) { // IsValidExpiresAt f( - &StandardClaims{}, - func(claims *StandardClaims) bool { + &RegisteredClaims{}, + func(claims *RegisteredClaims) bool { return claims.IsValidExpiresAt(after) }, true, ) f( - &StandardClaims{ExpiresAt: NewNumericDate(before)}, - func(claims *StandardClaims) bool { + &RegisteredClaims{ExpiresAt: NewNumericDate(before)}, + func(claims *RegisteredClaims) bool { return claims.IsValidExpiresAt(after) }, false, ) f( - &StandardClaims{ExpiresAt: NewNumericDate(after)}, - func(claims *StandardClaims) bool { + &RegisteredClaims{ExpiresAt: NewNumericDate(after)}, + func(claims *RegisteredClaims) bool { return claims.IsValidExpiresAt(before) }, true, @@ -97,22 +97,22 @@ func TestTimingClaims(t *testing.T) { // IsValidIssuedAt f( - &StandardClaims{}, - func(claims *StandardClaims) bool { + &RegisteredClaims{}, + func(claims *RegisteredClaims) bool { return claims.IsValidIssuedAt(after) }, true, ) f( - &StandardClaims{IssuedAt: NewNumericDate(before)}, - func(claims *StandardClaims) bool { + &RegisteredClaims{IssuedAt: NewNumericDate(before)}, + func(claims *RegisteredClaims) bool { return claims.IsValidIssuedAt(after) }, true, ) f( - &StandardClaims{IssuedAt: NewNumericDate(after)}, - func(claims *StandardClaims) bool { + &RegisteredClaims{IssuedAt: NewNumericDate(after)}, + func(claims *RegisteredClaims) bool { return claims.IsValidIssuedAt(before) }, false, @@ -120,22 +120,22 @@ func TestTimingClaims(t *testing.T) { // IsValidNotBefore f( - &StandardClaims{}, - func(claims *StandardClaims) bool { + &RegisteredClaims{}, + func(claims *RegisteredClaims) bool { return claims.IsValidNotBefore(after) }, true, ) f( - &StandardClaims{NotBefore: NewNumericDate(before)}, - func(claims *StandardClaims) bool { + &RegisteredClaims{NotBefore: NewNumericDate(before)}, + func(claims *RegisteredClaims) bool { return claims.IsValidNotBefore(after) }, true, ) f( - &StandardClaims{NotBefore: NewNumericDate(after)}, - func(claims *StandardClaims) bool { + &RegisteredClaims{NotBefore: NewNumericDate(after)}, + func(claims *RegisteredClaims) bool { return claims.IsValidNotBefore(before) }, false, @@ -149,7 +149,7 @@ func TestIsValidAt(t *testing.T) { afterNow := now.Add(10 * time.Second) after := now.Add(time.Minute) - f := func(claims *StandardClaims, f func(claims *StandardClaims) bool, want bool) { + f := func(claims *RegisteredClaims, f func(claims *RegisteredClaims) bool, want bool) { t.Helper() got := f(claims) @@ -159,26 +159,26 @@ func TestIsValidAt(t *testing.T) { } f( - &StandardClaims{}, - func(claims *StandardClaims) bool { return claims.IsValidAt(after) }, + &RegisteredClaims{}, + func(claims *RegisteredClaims) bool { return claims.IsValidAt(after) }, true, ) f( - &StandardClaims{ + &RegisteredClaims{ ExpiresAt: NewNumericDate(after), NotBefore: NewNumericDate(before), IssuedAt: NewNumericDate(beforeNow), }, - func(claims *StandardClaims) bool { return claims.IsValidAt(now) }, + func(claims *RegisteredClaims) bool { return claims.IsValidAt(now) }, true, ) f( - &StandardClaims{ + &RegisteredClaims{ ExpiresAt: NewNumericDate(after), NotBefore: NewNumericDate(before), IssuedAt: NewNumericDate(afterNow), }, - func(claims *StandardClaims) bool { return claims.IsValidAt(now) }, + func(claims *RegisteredClaims) bool { return claims.IsValidAt(now) }, false, ) } diff --git a/doc.go b/doc.go index fdfa365..b1ff87c 100644 --- a/doc.go +++ b/doc.go @@ -1,6 +1,6 @@ // Package jwt represents JSON Web Token for Go. // -// Builder, all the Signers and Verifiers are safe for use by multiple goroutines simultaneously. +// Builder, all Signers and Verifiers are safe for use by multiple goroutines simultaneously. // // See [RFC 7519](https://tools.ietf.org/html/rfc7519) and see [jwt.io](https://jwt.io) for more. // diff --git a/errors.go b/errors.go index 83a26c1..e8846df 100644 --- a/errors.go +++ b/errors.go @@ -1,37 +1,30 @@ package jwt -// Error represents a JWT error. -type Error string +import "errors" -func (e Error) Error() string { - return string(e) -} - -var _ error = (Error)("") - -// Build and parse errors. -const ( +// JWT sign, verify, build and parse errors. +var ( // ErrNilKey indicates that key is nil. - ErrNilKey = Error("jwt: key is nil") + ErrNilKey = errors.New("jwt: key is nil") // ErrInvalidKey indicates that key is not valid. - ErrInvalidKey = Error("jwt: key is not valid") + ErrInvalidKey = errors.New("jwt: key is not valid") // ErrUnsupportedAlg indicates that given algorithm is not supported. - ErrUnsupportedAlg = Error("jwt: algorithm is not supported") + ErrUnsupportedAlg = errors.New("jwt: algorithm is not supported") // ErrInvalidFormat indicates that token format is not valid. - ErrInvalidFormat = Error("jwt: token format is not valid") + ErrInvalidFormat = errors.New("jwt: token format is not valid") // ErrAudienceInvalidFormat indicates that audience format is not valid. - ErrAudienceInvalidFormat = Error("jwt: audience format is not valid") + ErrAudienceInvalidFormat = errors.New("jwt: audience format is not valid") // ErrDateInvalidFormat indicates that date format is not valid. - ErrDateInvalidFormat = Error("jwt: date is not valid") + ErrDateInvalidFormat = errors.New("jwt: date is not valid") // ErrAlgorithmMismatch indicates that token is signed by another algorithm. - ErrAlgorithmMismatch = Error("jwt: token is signed by another algorithm") + ErrAlgorithmMismatch = errors.New("jwt: token is signed by another algorithm") // ErrInvalidSignature indicates that signature is not valid. - ErrInvalidSignature = Error("jwt: signature is not valid") + ErrInvalidSignature = errors.New("jwt: signature is not valid") ) diff --git a/example_test.go b/example_test.go index 16250e5..e2a4fdc 100644 --- a/example_test.go +++ b/example_test.go @@ -5,10 +5,10 @@ import ( "fmt" "time" - "github.com/cristalhq/jwt/v3" + "github.com/cristalhq/jwt/v4" ) -func Example() { +func ExampleSignAndVerify() { // create a Signer (HMAC in this example) key := []byte(`secret`) signer, err := jwt.NewSignerHS(jwt.HS256, key) @@ -24,35 +24,39 @@ func Example() { builder := jwt.NewBuilder(signer) // and build a Token - newToken, err := builder.Build(claims) + token, err := builder.Build(claims) checkErr(err) - // here is token as byte slice - var _ []byte = newToken.Raw() // or just token.String() for string + // here is token as a string + var _ string = token.String() // create a Verifier (HMAC in this example) verifier, err := jwt.NewVerifierHS(jwt.HS256, key) checkErr(err) - // parse a Token (by example received from a request) - tokenStr := newToken.String() - token, err := jwt.ParseAndVerifyString(tokenStr, verifier) + // parse and verify a token + tokenBytes := token.Bytes() + newToken, err := jwt.Parse(tokenBytes, verifier) checkErr(err) - // and verify it's signature - err = verifier.Verify(token.Payload(), token.Signature()) + // or just verify it's signature + err = verifier.Verify(newToken) checkErr(err) - // also you can parse and verify together - newToken, err = jwt.ParseAndVerifyString(tokenStr, verifier) + // also you can parse without verify (NOT RECOMMENDED!) + newToken, err = jwt.ParseNoVerify(tokenBytes) checkErr(err) - // get standard claims - var newClaims jwt.StandardClaims - errClaims := json.Unmarshal(newToken.RawClaims(), &newClaims) + // get REGISTERED claims + var newClaims jwt.RegisteredClaims + errClaims := json.Unmarshal(newToken.Claims(), &newClaims) checkErr(errClaims) - // verify claims as you + // or parse only claims + errParseClaims := jwt.ParseClaims(tokenBytes, verifier, &newClaims) + checkErr(errParseClaims) + + // verify claims as you wish var _ bool = newClaims.IsForAudience("admin") var _ bool = newClaims.IsValidAt(time.Now()) @@ -71,20 +75,28 @@ func ExampleBuild() { token, err := builder.Build(claims) checkErr(err) - fmt.Printf("Algorithm %v\n", token.Header().Algorithm) - fmt.Printf("Type %v\n", token.Header().Type) - fmt.Printf("Claims %v\n", string(token.RawClaims())) - fmt.Printf("Payload %v\n", string(token.Payload())) - fmt.Printf("Token %v\n", string(token.Raw())) + fmt.Printf("Token %s\n", token.String()) + fmt.Printf("Algorithm %s\n", token.Header().Algorithm) + fmt.Printf("Type %s\n", token.Header().Type) + fmt.Printf("Claims %s\n", token.Claims()) + fmt.Printf("HeaderPart %s\n", token.HeaderPart()) + fmt.Printf("ClaimsPart %s\n", token.ClaimsPart()) + fmt.Printf("PayloadPart %s\n", token.PayloadPart()) + fmt.Printf("SignaturePart %s\n", token.SignaturePart()) // Output: + // Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJyYW5kb20tdW5pcXVlLXN0cmluZyIsImF1ZCI6ImFkbWluIn0.uNaqGEggmy02lZq8FM7KoUKXhOy-zrSF7inYuzIET9o // Algorithm HS256 // Type JWT // Claims {"jti":"random-unique-string","aud":"admin"} - // Payload eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJyYW5kb20tdW5pcXVlLXN0cmluZyIsImF1ZCI6ImFkbWluIn0 - // Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJyYW5kb20tdW5pcXVlLXN0cmluZyIsImF1ZCI6ImFkbWluIn0.uNaqGEggmy02lZq8FM7KoUKXhOy-zrSF7inYuzIET9o + // HeaderPart eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 + // ClaimsPart eyJqdGkiOiJyYW5kb20tdW5pcXVlLXN0cmluZyIsImF1ZCI6ImFkbWluIn0 + // PayloadPart eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJyYW5kb20tdW5pcXVlLXN0cmluZyIsImF1ZCI6ImFkbWluIn0 + // SignaturePart uNaqGEggmy02lZq8FM7KoUKXhOy-zrSF7inYuzIET9o } +// Payload eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJyYW5kb20tdW5pcXVlLXN0cmluZyIsImF1ZCI6ImFkbWluIn0 + type userClaims struct { jwt.RegisteredClaims IsAdministrator bool `json:"is_admin"` @@ -107,9 +119,9 @@ func ExampleBuild_WithUserClaims() { token, err := builder.Build(claims) checkErr(err) - fmt.Printf("Claims %v\n", string(token.RawClaims())) - fmt.Printf("Payload %v\n", string(token.Payload())) - fmt.Printf("Token %v\n", string(token.Raw())) + fmt.Printf("Claims %v\n", string(token.Claims())) + fmt.Printf("Payload %v\n", string(token.PayloadPart())) + fmt.Printf("Token %v\n", string(token.Bytes())) // Output: // Claims {"jti":"random-unique-string","aud":"admin","is_admin":true,"email":"foo@bar.baz"} @@ -120,14 +132,17 @@ func ExampleBuild_WithUserClaims() { func ExampleParse() { rawToken := []byte(`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhZG1pbiIsImp0aSI6InJhbmRvbS11bmlxdWUtc3RyaW5nIn0.dv9-XpY9P8ypm1uWQwB6eKvq3jeyodLA7brhjsf4JVs`) - token, err := jwt.Parse(rawToken) + key := []byte(`secret`) + verifier, _ := jwt.NewVerifierHS(jwt.HS256, key) + + token, err := jwt.Parse(rawToken, verifier) checkErr(err) fmt.Printf("Algorithm %v\n", token.Header().Algorithm) fmt.Printf("Type %v\n", token.Header().Type) - fmt.Printf("Claims %v\n", string(token.RawClaims())) - fmt.Printf("Payload %v\n", string(token.Payload())) - fmt.Printf("Token %v\n", string(token.Raw())) + fmt.Printf("Claims %v\n", string(token.Claims())) + fmt.Printf("Payload %v\n", string(token.PayloadPart())) + fmt.Printf("Token %v\n", string(token.Bytes())) // Output: // Algorithm HS256 @@ -137,20 +152,17 @@ func ExampleParse() { // Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhZG1pbiIsImp0aSI6InJhbmRvbS11bmlxdWUtc3RyaW5nIn0.dv9-XpY9P8ypm1uWQwB6eKvq3jeyodLA7brhjsf4JVs } -func ExampleParseAndVerify() { +func ExampleParseNoVerify() { rawToken := []byte(`eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJhZG1pbiIsImp0aSI6InJhbmRvbS11bmlxdWUtc3RyaW5nIn0.dv9-XpY9P8ypm1uWQwB6eKvq3jeyodLA7brhjsf4JVs`) - key := []byte(`secret`) - verifier, _ := jwt.NewVerifierHS(jwt.HS256, key) - - token, err := jwt.ParseAndVerify(rawToken, verifier) + token, err := jwt.ParseNoVerify(rawToken) checkErr(err) fmt.Printf("Algorithm %v\n", token.Header().Algorithm) fmt.Printf("Type %v\n", token.Header().Type) - fmt.Printf("Claims %v\n", string(token.RawClaims())) - fmt.Printf("Payload %v\n", string(token.Payload())) - fmt.Printf("Token %v\n", string(token.Raw())) + fmt.Printf("Claims %v\n", string(token.Claims())) + fmt.Printf("Payload %v\n", string(token.PayloadPart())) + fmt.Printf("Token %v\n", string(token.Bytes())) // Output: // Algorithm HS256 diff --git a/fuzz.go b/fuzz.go index 5fd1e8c..ee19bea 100644 --- a/fuzz.go +++ b/fuzz.go @@ -1,4 +1,6 @@ +//go:build gofuzz // +build gofuzz + // To run the fuzzer, run the following commands: // $ GO111MODULE=off go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build // $ cd $GOPATH/src/github.com/cristalhq/jwt/ @@ -10,7 +12,7 @@ package jwt func Fuzz(data []byte) int { - if _, err := Parse(data); err != nil { + if _, err := ParseNoVerify(data); err != nil { return 0 } return 1 diff --git a/go.mod b/go.mod index da3445d..8128a36 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/cristalhq/jwt/v3 +module github.com/cristalhq/jwt/v4 -go 1.13 +go 1.17 diff --git a/jwt.go b/jwt.go index 9bd4692..fc92139 100644 --- a/jwt.go +++ b/jwt.go @@ -21,15 +21,28 @@ func (t *Token) String() string { return string(t.raw) } -// SecureString returns token without a signature (replaced with `.`). -func (t *Token) SecureString() string { - dot := bytes.LastIndexByte(t.raw, '.') - return string(t.raw[:dot]) + `.` +func (t *Token) Bytes() []byte { + return t.raw } -// Raw returns token's raw bytes. -func (t *Token) Raw() []byte { - return t.raw +// HeaderPart returns token header part. +func (t *Token) HeaderPart() []byte { + return t.raw[:t.dot1] +} + +// ClaimsPart returns token claims part. +func (t *Token) ClaimsPart() []byte { + return t.raw[t.dot1+1 : t.dot2] +} + +// PayloadPart returns token payload part. +func (t *Token) PayloadPart() []byte { + return t.raw[:t.dot2] +} + +// SignaturePart returns token signature part. +func (t *Token) SignaturePart() []byte { + return t.raw[t.dot2+1:] } // Header returns token's header. @@ -37,19 +50,14 @@ func (t *Token) Header() Header { return t.header } -// RawHeader returns token's header raw bytes. -func (t *Token) RawHeader() []byte { - return t.raw[:t.dot1] -} - -// RawClaims returns token's claims as a raw bytes. -func (t *Token) RawClaims() []byte { +// Claims returns token's claims. +func (t *Token) Claims() json.RawMessage { return t.claims } -// Payload returns token's payload. -func (t *Token) Payload() []byte { - return t.raw[:t.dot2] +// DecodeClaims into a given parameter. +func (t *Token) DecodeClaims(dst interface{}) error { + return json.Unmarshal(t.claims, dst) } // Signature returns token's signature. @@ -68,7 +76,7 @@ type Header struct { } // MarshalJSON implements the json.Marshaler interface. -func (h *Header) MarshalJSON() ([]byte, error) { +func (h Header) MarshalJSON() ([]byte, error) { buf := bytes.Buffer{} buf.WriteString(`{"alg":"`) buf.WriteString(string(h.Algorithm)) diff --git a/jwt_bench_test.go b/jwt_bench_test.go index 6c1a58a..5bd60cb 100644 --- a/jwt_bench_test.go +++ b/jwt_bench_test.go @@ -6,26 +6,28 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" - mathRand "math/rand" + "fmt" + "io" "testing" "time" - "github.com/cristalhq/jwt/v3" + "github.com/cristalhq/jwt/v4" ) func BenchmarkAlgEDSA(b *testing.B) { - pubKey, privKey, keyErr := ed25519.GenerateKey(rand.Reader) - if keyErr != nil { - b.Fatal(keyErr) + pubKey, privKey, errKey := ed25519.GenerateKey(rand.Reader) + if errKey != nil { + b.Fatal(errKey) } - signer, signerErr := jwt.NewSignerEdDSA(privKey) - if signerErr != nil { - b.Fatal(signerErr) + signer, errSigner := jwt.NewSignerEdDSA(privKey) + if errSigner != nil { + b.Fatal(errSigner) } - verifier, verifierErr := jwt.NewVerifierEdDSA(pubKey) - if verifierErr != nil { - b.Fatal(verifierErr) + verifier, errVerifier := jwt.NewVerifierEdDSA(pubKey) + if errVerifier != nil { + b.Fatal(errVerifier) } + builder := jwt.NewBuilder(signer) b.Run("Sign-"+string(jwt.EdDSA), func(b *testing.B) { runSignerBench(b, builder) @@ -42,18 +44,19 @@ func BenchmarkAlgES(b *testing.B) { jwt.ES512: elliptic.P521(), } for algo, curve := range esAlgos { - key, keyErr := ecdsa.GenerateKey(curve, rand.Reader) - if keyErr != nil { - b.Fatal(keyErr) + key, errKey := ecdsa.GenerateKey(curve, rand.Reader) + if errKey != nil { + b.Fatal(errKey) } - signer, signerErr := jwt.NewSignerES(algo, key) - if signerErr != nil { - b.Fatal(signerErr) + signer, errSigner := jwt.NewSignerES(algo, key) + if errSigner != nil { + b.Fatal(errSigner) } - verifier, verifierErr := jwt.NewVerifierES(algo, &key.PublicKey) - if verifierErr != nil { - b.Fatal(verifierErr) + verifier, errVerifier := jwt.NewVerifierES(algo, &key.PublicKey) + if errVerifier != nil { + b.Fatal(errVerifier) } + builder := jwt.NewBuilder(signer) b.Run("Sign-"+string(algo), func(b *testing.B) { runSignerBench(b, builder) @@ -67,18 +70,19 @@ func BenchmarkAlgES(b *testing.B) { func BenchmarkAlgPS(b *testing.B) { psAlgos := []jwt.Algorithm{jwt.PS256, jwt.PS384, jwt.PS512} for _, algo := range psAlgos { - key, keyErr := rsa.GenerateKey(rand.Reader, 2048) - if keyErr != nil { - b.Fatal(keyErr) + key, errKey := rsa.GenerateKey(rand.Reader, 2048) + if errKey != nil { + b.Fatal(errKey) } - signer, signerErr := jwt.NewSignerPS(algo, key) - if signerErr != nil { - b.Fatal(signerErr) + signer, errSigner := jwt.NewSignerPS(algo, key) + if errSigner != nil { + b.Fatal(errSigner) } - verifier, verifierErr := jwt.NewVerifierPS(algo, &key.PublicKey) - if verifierErr != nil { - b.Fatal(verifierErr) + verifier, errVerifier := jwt.NewVerifierPS(algo, &key.PublicKey) + if errVerifier != nil { + b.Fatal(errVerifier) } + builder := jwt.NewBuilder(signer) b.Run("Sign-"+string(algo), func(b *testing.B) { runSignerBench(b, builder) @@ -92,18 +96,19 @@ func BenchmarkAlgPS(b *testing.B) { func BenchmarkAlgRS(b *testing.B) { rsAlgos := []jwt.Algorithm{jwt.RS256, jwt.RS384, jwt.RS512} for _, algo := range rsAlgos { - key, keyErr := rsa.GenerateKey(rand.Reader, 2048) - if keyErr != nil { - b.Fatal(keyErr) + key, errKey := rsa.GenerateKey(rand.Reader, 2048) + if errKey != nil { + b.Fatal(errKey) } - signer, signerErr := jwt.NewSignerRS(algo, key) - if signerErr != nil { - b.Fatal(signerErr) + signer, errSigner := jwt.NewSignerRS(algo, key) + if errSigner != nil { + b.Fatal(errSigner) } - verifier, verifierErr := jwt.NewVerifierRS(algo, &key.PublicKey) - if verifierErr != nil { - b.Fatal(verifierErr) + verifier, errVerifier := jwt.NewVerifierRS(algo, &key.PublicKey) + if errVerifier != nil { + b.Fatal(errVerifier) } + builder := jwt.NewBuilder(signer) b.Run("Sign-"+string(algo), func(b *testing.B) { runSignerBench(b, builder) @@ -118,14 +123,15 @@ func BenchmarkAlgHS(b *testing.B) { key := []byte("12345") hsAlgos := []jwt.Algorithm{jwt.HS256, jwt.HS384, jwt.HS512} for _, algo := range hsAlgos { - signer, signerErr := jwt.NewSignerHS(algo, key) - if signerErr != nil { - b.Fatal(signerErr) + signer, errSigner := jwt.NewSignerHS(algo, key) + if errSigner != nil { + b.Fatal(errSigner) } - verifier, verifierErr := jwt.NewVerifierHS(algo, key) - if verifierErr != nil { - b.Fatal(verifierErr) + verifier, errVerifier := jwt.NewVerifierHS(algo, key) + if errVerifier != nil { + b.Fatal(errVerifier) } + builder := jwt.NewBuilder(signer) b.Run("Sign-"+string(algo), func(b *testing.B) { runSignerBench(b, builder) @@ -137,53 +143,56 @@ func BenchmarkAlgHS(b *testing.B) { } func runSignerBench(b *testing.B, builder *jwt.Builder) { + b.Helper() b.ReportAllocs() - sink := int(0) - for i := 0; i < b.N; i++ { - token, tokenErr := builder.Build(jwt.StandardClaims{ - ID: "id", - Issuer: "sdf", - IssuedAt: jwt.NewNumericDate(time.Now()), - }) - if tokenErr != nil { - b.Fatal(tokenErr) - } - sink += int(token.Payload()[0]) + claims := jwt.RegisteredClaims{ + ID: "id", + Issuer: "sdf", + IssuedAt: jwt.NewNumericDate(time.Now()), } - if mathRand.Intn(10000) > 9999 { - b.Log(sink) + var dummy int + for i := 0; i < b.N; i++ { + token, err := builder.Build(claims) + if err != nil { + b.Fatal(err) + } + dummy += int(token.PayloadPart()[0]) } + sink(dummy) } func runVerifyBench(b *testing.B, builder *jwt.Builder, verifier jwt.Verifier) { - tokensCount := 32 + b.Helper() + const tokensCount = 32 tokens := make([]*jwt.Token, 0, tokensCount) for i := 0; i < tokensCount; i++ { - token, tokenErr := builder.Build(jwt.StandardClaims{ + token, err := builder.Build(jwt.RegisteredClaims{ ID: "id", Issuer: "sdf", IssuedAt: jwt.NewNumericDate(time.Now()), }) - if tokenErr != nil { - b.Fatal(tokenErr) + if err != nil { + b.Fatal(err) } tokens = append(tokens, token) } b.ReportAllocs() - sink := uintptr(0) + var dummy int for i := 0; i < b.N/tokensCount; i++ { for _, token := range tokens { - verificationErr := verifier.Verify(token.Payload(), token.Signature()) - if verificationErr != nil { - b.Fatal(verificationErr) + err := verifier.Verify(token) + if err != nil { + b.Fatal(err) } + dummy++ } } + sink(dummy) +} - if mathRand.Intn(10000) > 9999 { - b.Log(sink) - } +func sink(v interface{}) { + fmt.Fprint(io.Discard, v) } diff --git a/jwt_test.go b/jwt_test.go index 95fdad1..33bc8c1 100644 --- a/jwt_test.go +++ b/jwt_test.go @@ -1,37 +1,36 @@ package jwt import ( + "crypto/ecdsa" + "crypto/rsa" + "crypto/x509" "encoding/base64" - "strings" + "encoding/pem" + "reflect" "testing" ) -var bytesToBase64 = base64.RawURLEncoding.EncodeToString - -func strToBase64(s string) string { - return base64.RawURLEncoding.EncodeToString([]byte(s)) -} - -func getSignerError(_ Signer, err error) error { - return err -} - -func getVerifierError(_ Verifier, err error) error { - return err -} - -func mustSigner(s Signer, err error) Signer { +func TestDecodeClaims(t *testing.T) { + tokenStr := `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiYXVkIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.jC1Ncd2FW0ZpoiHV9_Bk2eDWdfCqUIzfCgTHZfK0h_o` + token, err := ParseNoVerify([]byte(tokenStr)) if err != nil { - panic(err) + t.Fatal(err) } - return s -} -func mustVerifier(v Verifier, err error) Verifier { - if err != nil { - panic(err) + claims := RegisteredClaims{} + if err := token.DecodeClaims(&claims); err != nil { + t.Fatal(err) + } + + iat := asNumericDate(1516239022) + wantClaims := RegisteredClaims{ + IssuedAt: &iat, + Audience: Audience{"John Doe"}, + Subject: "1234567890", + } + if !reflect.DeepEqual(claims, wantClaims) { + t.Fatalf("want %v, got %v", wantClaims, claims) } - return v } func TestMarshalHeader(t *testing.T) { @@ -73,33 +72,57 @@ func TestMarshalHeader(t *testing.T) { ) } -func TestSecurePrint(t *testing.T) { - sign, _ := NewSignerHS(HS256, []byte(`test-key`)) - claims := &StandardClaims{ - ID: "test-id", - Audience: Audience([]string{"test-user"}), - } +var bytesToBase64 = base64.RawURLEncoding.EncodeToString + +func base64ToBytes(s string) []byte { + b, _ := base64.RawURLEncoding.DecodeString(s) + return b +} - token, err := Build(sign, claims) +func getSignerError(_ Signer, err error) error { + return err +} + +func getVerifierError(_ Verifier, err error) error { + return err +} + +func mustSigner(s Signer, err error) Signer { if err != nil { - t.Fatal(err) + panic(err) } + return s +} - secure := token.SecureString() - insecure := token.String() - - pos := strings.Index(secure, `.`) +func mustVerifier(v Verifier, err error) Verifier { + if err != nil { + panic(err) + } + return v +} - if secure[:pos] != insecure[:pos] { - t.Fatalf("parts must be equal, got %v and %v", secure[:pos], insecure[:pos]) +func mustParseRSAKey(s string) *rsa.PrivateKey { + block, _ := pem.Decode([]byte(s)) + if block == nil { + panic("invalid PEM") } - if secure[pos:] == insecure[pos:] { - t.Fatalf("parts must not be equal, got %v and %v", secure[:pos], insecure[:pos]) + + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + panic(err) } - if !strings.HasSuffix(secure, `.`) { - t.Fatalf("must have safe suffix, got %v", secure) + return key +} + +func mustParseECKey(s string) *ecdsa.PrivateKey { + block, _ := pem.Decode([]byte(s)) + if block == nil { + panic("invalid PEM") } - if strings.HasSuffix(insecure, `.`) { - t.Fatalf("must not have safe suffix, got %v", insecure) + + key, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + panic(err) } + return key } diff --git a/numeric_date.go b/numeric_date.go index d5bde9d..5535002 100644 --- a/numeric_date.go +++ b/numeric_date.go @@ -16,15 +16,12 @@ type NumericDate struct { // NewNumericDate creates a new NumericDate value from time.Time. func NewNumericDate(t time.Time) *NumericDate { - if t.IsZero() { - return nil - } return &NumericDate{t} } // MarshalJSON implements the json.Marshaler interface. -func (t *NumericDate) MarshalJSON() ([]byte, error) { - if t == nil || t.IsZero() { +func (t NumericDate) MarshalJSON() ([]byte, error) { + if t.IsZero() { return []byte("null"), nil } return []byte(strconv.FormatInt(t.Unix(), 10)), nil diff --git a/parse.go b/parse.go index b339de1..b1fbac2 100644 --- a/parse.go +++ b/parse.go @@ -6,36 +6,36 @@ import ( "encoding/json" ) -// ParseString decodes a token. -func ParseString(raw string) (*Token, error) { - return Parse([]byte(raw)) -} - -// Parse decodes a token from a raw bytes. -func Parse(raw []byte) (*Token, error) { - return parse(raw) -} - -// ParseAndVerifyString decodes a token and verifies it's signature. -func ParseAndVerifyString(raw string, verifier Verifier) (*Token, error) { - return ParseAndVerify([]byte(raw), verifier) -} - -// ParseAndVerify decodes a token and verifies it's signature. -func ParseAndVerify(raw []byte, verifier Verifier) (*Token, error) { - token, err := parse(raw) +// Parse decodes a token and verifies it's signature. +func Parse(raw []byte, verifier Verifier) (*Token, error) { + token, err := ParseNoVerify(raw) if err != nil { return nil, err } - if !constTimeAlgEqual(token.Header().Algorithm, verifier.Algorithm()) { - return nil, ErrAlgorithmMismatch - } - if err := verifier.Verify(token.Payload(), token.Signature()); err != nil { + if err := verifier.Verify(token); err != nil { return nil, err } return token, nil } +// ParseClaims decodes a token claims and verifies it's signature. +func ParseClaims(raw []byte, verifier Verifier, claims interface{}) error { + token, err := Parse(raw, verifier) + if err != nil { + return err + } + if err := verifier.Verify(token); err != nil { + return err + } + return token.DecodeClaims(claims) +} + +// ParseNoVerify decodes a token from a raw bytes. +// NOTE: Consider to use Parse with a verifier to verify token signature. +func ParseNoVerify(raw []byte) (*Token, error) { + return parse(raw) +} + func parse(token []byte) (*Token, error) { // "eyJ" is `{"` which is begin of every JWT token. // Quick check for the invalid input. diff --git a/parse_test.go b/parse_test.go index ee73a6c..b9c4d87 100644 --- a/parse_test.go +++ b/parse_test.go @@ -12,12 +12,12 @@ func TestParse(t *testing.T) { parts := strings.Split(token, ".") partHeader, _, _ := parts[0], parts[1], parts[2] - tk, err := Parse([]byte(token)) + tk, err := Parse([]byte(token), nopVerifier{}) if err != nil { t.Errorf("want nil, got %#v", err) } - if gotHeader := string(tk.RawHeader()); partHeader != gotHeader { + if gotHeader := string(tk.HeaderPart()); partHeader != gotHeader { t.Errorf("want header %q, got %q", partHeader, gotHeader) } @@ -25,7 +25,7 @@ func TestParse(t *testing.T) { t.Errorf("want %#v, got %#v", header, tk.Header()) } - gotClaims := string(tk.RawClaims()) + gotClaims := string(tk.Claims()) if gotClaims != claims { t.Errorf("want claim %s, got %s", claims, gotClaims) } @@ -57,11 +57,21 @@ func TestParse(t *testing.T) { ) } +func TestParseAnotherAlgorithm(t *testing.T) { + tokenHS256 := `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJqdXN0IGFuIGlkIiwiYXVkIjoiYXVkaWVuY2UifQ.t5oEdZGp0Qbth7lo5fZlV_o4-r9gMoYBSktXbarjWoo` + verifier := mustVerifier(NewVerifierHS(HS512, []byte("key"))) + + _, err := Parse([]byte(tokenHS256), verifier) + if err == nil { + t.Fatal() + } +} + func TestParseMalformed(t *testing.T) { f := func(got string) { t.Helper() - _, err := Parse([]byte(got)) + _, err := Parse([]byte(got), nopVerifier{}) if err == nil { t.Error("got nil want err") } @@ -74,3 +84,8 @@ func TestParseMalformed(t *testing.T) { f(`eyJhIjoxMjN9.x!yz.e30`) // `e30` is JSON `{}` in base64 f(`eyJhIjoxMjN9.e30.x!yz`) } + +type nopVerifier struct{} + +func (nopVerifier) Algorithm() Algorithm { return "nop" } +func (nopVerifier) Verify(token *Token) error { return nil }