From 4ac762c22768de20e53dcf5ff8841903bed5c9ae Mon Sep 17 00:00:00 2001 From: Baptiste Jamin Date: Tue, 31 Aug 2021 11:37:51 +0200 Subject: [PATCH] Revert "Prepare release 3.2.2 (#42)" This reverts commit 4bbdd8ac624fc7a9ef7aec841c43d99b5fe65a29. --- .github/workflows/build.yml | 2 +- .travis.yml | 11 +++++ README.md | 9 +--- VERSION_HISTORY.md | 7 ---- cmd/jwt/app.go | 15 ------- ecdsa.go | 18 +++++--- ecdsa_test.go | 48 +-------------------- ed25519.go | 81 ----------------------------------- ed25519_test.go | 84 ------------------------------------- ed25519_utils.go | 64 ---------------------------- map_claims.go | 36 ++++++---------- map_claims_test.go | 34 --------------- parser_test.go | 2 - test/ed25519-private.pem | 3 -- test/ed25519-public.pem | 3 -- token.go | 8 +++- 16 files changed, 44 insertions(+), 381 deletions(-) create mode 100644 .travis.yml delete mode 100644 ed25519.go delete mode 100644 ed25519_test.go delete mode 100644 ed25519_utils.go delete mode 100644 test/ed25519-private.pem delete mode 100644 test/ed25519-public.pem diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ec7bd9ac..4da8eab6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - go: [1.15, 1.16] + go: [1.11, 1.12, 1.13, 1.14, 1.15, 1.16] steps: - name: Checkout uses: actions/checkout@v2 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..036a862f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,11 @@ +language: go + +script: + - go vet ./... + - go test -v ./... + +go: + - 1.7 + - 1.8 + - 1.9 + - 1.10 diff --git a/README.md b/README.md index 9b653e46..13c31c09 100644 --- a/README.md +++ b/README.md @@ -9,17 +9,10 @@ A [go](http://www.golang.org) (or 'golang' for search engine friendliness) imple Future releases will be using the `github.com/golang-jwt/jwt` import path and continue the existing versioning scheme of `v3.x.x+incompatible`. Backwards-compatible patches and fixes will be done on the `v3` release branch, where as new build-breaking features will be developed in a `v4` release, possibly including a SIV-style import path. -**SECURITY NOTICE:** Some older versions of Go have a security issue in the crypto/elliptic. Recommendation is to upgrade to at least 1.15 See issue [dgrijalva/jwt-go#216](https://github.com/dgrijalva/jwt-go/issues/216) for more detail. +**SECURITY NOTICE:** Some older versions of Go have a security issue in the crypto/elliptic. Recommendation is to upgrade to at least 1.8.3. See issue [dgrijalva/jwt-go#216](https://github.com/dgrijalva/jwt-go/issues/216) for more detail. **SECURITY NOTICE:** It's important that you [validate the `alg` presented is what you expect](https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/). This library attempts to make it easy to do the right thing by requiring key types match the expected alg, but you should take the extra step to verify it in your usage. See the examples provided. -### Supported Go versions - -Our support of Go versions is aligned with Go's [version release policy](https://golang.org/doc/devel/release#policy). -So we will support a major version of Go until there are two newer major releases. -We no longer support building jwt-go with unsupported Go versions, as these contain security vulnerabilities -which will not be fixed. - ## What the heck is a JWT? JWT.io has [a great introduction](https://jwt.io/introduction) to JSON Web Tokens. diff --git a/VERSION_HISTORY.md b/VERSION_HISTORY.md index 637f2ba6..dac737bc 100644 --- a/VERSION_HISTORY.md +++ b/VERSION_HISTORY.md @@ -1,12 +1,5 @@ ## `jwt-go` Version History -#### 3.2.2 - -* Starting from this release, we are adopting the policy to support the most 2 recent versions of Go currently available. By the time of this release, this is Go 1.15 and 1.16 ([#28](https://github.com/golang-jwt/jwt/pull/28)). -* Fixed a potential issue that could occur when the verification of `exp`, `iat` or `nbf` was not required and contained invalid contents, i.e. non-numeric/date. Thanks for @thaJeztah for making us aware of that and @giorgos-f3 for originally reporting it to the formtech fork ([#40](https://github.com/golang-jwt/jwt/pull/40)). -* Added support for EdDSA / ED25519 ([#36](https://github.com/golang-jwt/jwt/pull/36)). -* Optimized allocations ([#33](https://github.com/golang-jwt/jwt/pull/33)). - #### 3.2.1 * **Import Path Change**: See MIGRATION_GUIDE.md for tips on updating your code diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index c8fd9361..b3649271 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -136,8 +136,6 @@ func verifyToken() error { return jwt.ParseECPublicKeyFromPEM(data) } else if isRs() { return jwt.ParseRSAPublicKeyFromPEM(data) - } else if isEd() { - return jwt.ParseEdPublicKeyFromPEM(data) } return data, nil }) @@ -231,15 +229,6 @@ func signToken() error { return err } } - } else if isEd() { - if k, ok := key.([]byte); !ok { - return fmt.Errorf("Couldn't convert key data to key") - } else { - key, err = jwt.ParseEdPrivateKeyFromPEM(k) - if err != nil { - return err - } - } } if out, err := token.SignedString(key); err == nil { @@ -291,7 +280,3 @@ func isEs() bool { func isRs() bool { return strings.HasPrefix(*flagAlg, "RS") || strings.HasPrefix(*flagAlg, "PS") } - -func isEd() bool { - return strings.HasPrefix(strings.ToUpper(*flagAlg), "Ed") -} diff --git a/ecdsa.go b/ecdsa.go index 15e23435..d310af1c 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -128,12 +128,18 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string keyBytes += 1 } - // We serialize the outputs (r and s) into big-endian byte arrays - // padded with zeros on the left to make sure the sizes work out. - // Output must be 2*keyBytes long. - out := make([]byte, 2*keyBytes) - r.FillBytes(out[0:keyBytes]) // r is assigned to the first half of output. - s.FillBytes(out[keyBytes:]) // s is assigned to the second half of output. + // We serialize the outpus (r and s) into big-endian byte arrays and pad + // them with zeros on the left to make sure the sizes work out. Both arrays + // must be keyBytes long, and the output must be 2*keyBytes long. + rBytes := r.Bytes() + rBytesPadded := make([]byte, keyBytes) + copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) + + sBytes := s.Bytes() + sBytesPadded := make([]byte, keyBytes) + copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) + + out := append(rBytesPadded, sBytesPadded...) return EncodeSegment(out), nil } else { diff --git a/ecdsa_test.go b/ecdsa_test.go index c384608e..ad0a401a 100644 --- a/ecdsa_test.go +++ b/ecdsa_test.go @@ -87,60 +87,14 @@ func TestECDSASign(t *testing.T) { if data.valid { parts := strings.Split(data.tokenString, ".") - toSign := strings.Join(parts[0:2], ".") method := jwt.GetSigningMethod(data.alg) - sig, err := method.Sign(toSign, ecdsaKey) - + sig, err := method.Sign(strings.Join(parts[0:2], "."), ecdsaKey) if err != nil { t.Errorf("[%v] Error signing token: %v", data.name, err) } if sig == parts[2] { t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig) } - - err = method.Verify(toSign, sig, ecdsaKey.Public()) - if err != nil { - t.Errorf("[%v] Sign produced an invalid signature: %v", data.name, err) - } - } - } -} - -func BenchmarkECDSASigning(b *testing.B) { - for _, data := range ecdsaTestData { - key, _ := ioutil.ReadFile(data.keys["private"]) - - ecdsaKey, err := jwt.ParseECPrivateKeyFromPEM(key) - if err != nil { - b.Fatalf("Unable to parse ECDSA private key: %v", err) } - - method := jwt.GetSigningMethod(data.alg) - - b.Run(data.name, func(b *testing.B) { - benchmarkSigning(b, method, ecdsaKey) - }) - - // Directly call method.Sign without the decoration of *Token. - b.Run(data.name+"/sign-only", func(b *testing.B) { - if !data.valid { - b.Skipf("Skipping because data is not valid") - } - - parts := strings.Split(data.tokenString, ".") - toSign := strings.Join(parts[0:2], ".") - - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - sig, err := method.Sign(toSign, ecdsaKey) - if err != nil { - b.Fatalf("[%v] Error signing token: %v", data.name, err) - } - if sig == parts[2] { - b.Fatalf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig) - } - } - }) } } diff --git a/ed25519.go b/ed25519.go deleted file mode 100644 index a2f8ddbe..00000000 --- a/ed25519.go +++ /dev/null @@ -1,81 +0,0 @@ -package jwt - -import ( - "errors" - - "crypto/ed25519" -) - -var ( - ErrEd25519Verification = errors.New("ed25519: verification error") -) - -// Implements the EdDSA family -// Expects ed25519.PrivateKey for signing and ed25519.PublicKey for verification -type SigningMethodEd25519 struct{} - -// Specific instance for EdDSA -var ( - SigningMethodEdDSA *SigningMethodEd25519 -) - -func init() { - SigningMethodEdDSA = &SigningMethodEd25519{} - RegisterSigningMethod(SigningMethodEdDSA.Alg(), func() SigningMethod { - return SigningMethodEdDSA - }) -} - -func (m *SigningMethodEd25519) Alg() string { - return "EdDSA" -} - -// Implements the Verify method from SigningMethod -// For this verify method, key must be an ed25519.PublicKey -func (m *SigningMethodEd25519) Verify(signingString, signature string, key interface{}) error { - var err error - var ed25519Key ed25519.PublicKey - var ok bool - - if ed25519Key, ok = key.(ed25519.PublicKey); !ok { - return ErrInvalidKeyType - } - - if len(ed25519Key) != ed25519.PublicKeySize { - return ErrInvalidKey - } - - // Decode the signature - var sig []byte - if sig, err = DecodeSegment(signature); err != nil { - return err - } - - // Verify the signature - if !ed25519.Verify(ed25519Key, []byte(signingString), sig) { - return ErrEd25519Verification - } - - return nil -} - -// Implements the Sign method from SigningMethod -// For this signing method, key must be an ed25519.PrivateKey -func (m *SigningMethodEd25519) Sign(signingString string, key interface{}) (string, error) { - var ed25519Key ed25519.PrivateKey - var ok bool - - if ed25519Key, ok = key.(ed25519.PrivateKey); !ok { - return "", ErrInvalidKeyType - } - - // ed25519.Sign panics if private key not equal to ed25519.PrivateKeySize - // this allows to avoid recover usage - if len(ed25519Key) != ed25519.PrivateKeySize { - return "", ErrInvalidKey - } - - // Sign the string and return the encoded result - sig := ed25519.Sign(ed25519Key, []byte(signingString)) - return EncodeSegment(sig), nil -} diff --git a/ed25519_test.go b/ed25519_test.go deleted file mode 100644 index 26177e28..00000000 --- a/ed25519_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package jwt_test - -import ( - "io/ioutil" - "strings" - "testing" - - "github.com/golang-jwt/jwt" -) - -var ed25519TestData = []struct { - name string - keys map[string]string - tokenString string - alg string - claims map[string]interface{} - valid bool -}{ - { - "Basic Ed25519", - map[string]string{"private": "test/ed25519-private.pem", "public": "test/ed25519-public.pem"}, - "eyJhbGciOiJFRDI1NTE5IiwidHlwIjoiSldUIn0.eyJmb28iOiJiYXIifQ.ESuVzZq1cECrt9Od_gLPVG-_6uRP_8Nq-ajx6CtmlDqRJZqdejro2ilkqaQgSL-siE_3JMTUW7UwAorLaTyFCw", - "EdDSA", - map[string]interface{}{"foo": "bar"}, - true, - }, - { - "Basic Ed25519", - map[string]string{"private": "test/ed25519-private.pem", "public": "test/ed25519-public.pem"}, - "eyJhbGciOiJFRDI1NTE5IiwidHlwIjoiSldUIn0.eyJmb28iOiJiYXoifQ.ESuVzZq1cECrt9Od_gLPVG-_6uRP_8Nq-ajx6CtmlDqRJZqdejro2ilkqaQgSL-siE_3JMTUW7UwAorLaTyFCw", - "EdDSA", - map[string]interface{}{"foo": "bar"}, - false, - }, -} - -func TestEd25519Verify(t *testing.T) { - for _, data := range ed25519TestData { - var err error - - key, _ := ioutil.ReadFile(data.keys["public"]) - - ed25519Key, err := jwt.ParseEdPublicKeyFromPEM(key) - if err != nil { - t.Errorf("Unable to parse Ed25519 public key: %v", err) - } - - parts := strings.Split(data.tokenString, ".") - - method := jwt.GetSigningMethod(data.alg) - - err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ed25519Key) - if data.valid && err != nil { - t.Errorf("[%v] Error while verifying key: %v", data.name, err) - } - if !data.valid && err == nil { - t.Errorf("[%v] Invalid key passed validation", data.name) - } - } -} - -func TestEd25519Sign(t *testing.T) { - for _, data := range ed25519TestData { - var err error - key, _ := ioutil.ReadFile(data.keys["private"]) - - ed25519Key, err := jwt.ParseEdPrivateKeyFromPEM(key) - if err != nil { - t.Errorf("Unable to parse Ed25519 private key: %v", err) - } - - parts := strings.Split(data.tokenString, ".") - - method := jwt.GetSigningMethod(data.alg) - - sig, err := method.Sign(strings.Join(parts[0:2], "."), ed25519Key) - if err != nil { - t.Errorf("[%v] Error signing token: %v", data.name, err) - } - if sig == parts[2] && !data.valid { - t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig) - } - } -} diff --git a/ed25519_utils.go b/ed25519_utils.go deleted file mode 100644 index c6357275..00000000 --- a/ed25519_utils.go +++ /dev/null @@ -1,64 +0,0 @@ -package jwt - -import ( - "crypto" - "crypto/ed25519" - "crypto/x509" - "encoding/pem" - "errors" -) - -var ( - ErrNotEdPrivateKey = errors.New("Key is not a valid Ed25519 private key") - ErrNotEdPublicKey = errors.New("Key is not a valid Ed25519 public key") -) - -// Parse PEM-encoded Edwards curve private key -func ParseEdPrivateKeyFromPEM(key []byte) (crypto.PrivateKey, error) { - var err error - - // Parse PEM block - var block *pem.Block - if block, _ = pem.Decode(key); block == nil { - return nil, ErrKeyMustBePEMEncoded - } - - // Parse the key - var parsedKey interface{} - if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { - return nil, err - } - - var pkey ed25519.PrivateKey - var ok bool - if pkey, ok = parsedKey.(ed25519.PrivateKey); !ok { - return nil, ErrNotEdPrivateKey - } - - return pkey, nil -} - -// Parse PEM-encoded Edwards curve public key -func ParseEdPublicKeyFromPEM(key []byte) (crypto.PublicKey, error) { - var err error - - // Parse PEM block - var block *pem.Block - if block, _ = pem.Decode(key); block == nil { - return nil, ErrKeyMustBePEMEncoded - } - - // Parse the key - var parsedKey interface{} - if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { - return nil, err - } - - var pkey ed25519.PublicKey - var ok bool - if pkey, ok = parsedKey.(ed25519.PublicKey); !ok { - return nil, ErrNotEdPublicKey - } - - return pkey, nil -} diff --git a/map_claims.go b/map_claims.go index 72c79f92..ba290f42 100644 --- a/map_claims.go +++ b/map_claims.go @@ -34,35 +34,27 @@ func (m MapClaims) VerifyAudience(cmp string, req bool) bool { // Compares the exp claim against cmp. // If required is false, this method will return true if the value matches or is unset func (m MapClaims) VerifyExpiresAt(cmp int64, req bool) bool { - exp, ok := m["exp"] - if !ok { - return !req - } - switch expType := exp.(type) { + switch exp := m["exp"].(type) { case float64: - return verifyExp(int64(expType), cmp, req) + return verifyExp(int64(exp), cmp, req) case json.Number: - v, _ := expType.Int64() + v, _ := exp.Int64() return verifyExp(v, cmp, req) } - return false + return !req } // Compares the iat claim against cmp. // If required is false, this method will return true if the value matches or is unset func (m MapClaims) VerifyIssuedAt(cmp int64, req bool) bool { - iat, ok := m["iat"] - if !ok { - return !req - } - switch iatType := iat.(type) { + switch iat := m["iat"].(type) { case float64: - return verifyIat(int64(iatType), cmp, req) + return verifyIat(int64(iat), cmp, req) case json.Number: - v, _ := iatType.Int64() + v, _ := iat.Int64() return verifyIat(v, cmp, req) } - return false + return !req } // Compares the iss claim against cmp. @@ -75,18 +67,14 @@ func (m MapClaims) VerifyIssuer(cmp string, req bool) bool { // Compares the nbf claim against cmp. // If required is false, this method will return true if the value matches or is unset func (m MapClaims) VerifyNotBefore(cmp int64, req bool) bool { - nbf, ok := m["nbf"] - if !ok { - return !req - } - switch nbfType := nbf.(type) { + switch nbf := m["nbf"].(type) { case float64: - return verifyNbf(int64(nbfType), cmp, req) + return verifyNbf(int64(nbf), cmp, req) case json.Number: - v, _ := nbfType.Int64() + v, _ := nbf.Int64() return verifyNbf(v, cmp, req) } - return false + return !req } // Validates time based claims "exp, iat, nbf". diff --git a/map_claims_test.go b/map_claims_test.go index f05e7afe..ff65913f 100644 --- a/map_claims_test.go +++ b/map_claims_test.go @@ -66,37 +66,3 @@ func TestVerifyAud(t *testing.T) { }) } } - -func TestMapclaimsVerifyIssuedAtInvalidTypeString(t *testing.T) { - mapClaims := MapClaims{ - "iat": "foo", - } - want := false - got := mapClaims.VerifyIssuedAt(0, false) - if want != got { - t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) - } -} - -func TestMapclaimsVerifyNotBeforeInvalidTypeString(t *testing.T) { - mapClaims := MapClaims{ - "nbf": "foo", - } - want := false - got := mapClaims.VerifyNotBefore(0, false) - if want != got { - t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) - } -} - -func TestMapclaimsVerifyExpiresAtInvalidTypeString(t *testing.T) { - mapClaims := MapClaims{ - "exp": "foo", - } - want := false - got := mapClaims.VerifyExpiresAt(0, false) - - if want != got { - t.Fatalf("Failed to verify claims, wanted: %v got %v", want, got) - } -} diff --git a/parser_test.go b/parser_test.go index abd245e8..7fb15809 100644 --- a/parser_test.go +++ b/parser_test.go @@ -290,8 +290,6 @@ func TestParser_ParseUnverified(t *testing.T) { // Helper method for benchmarking various methods func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) { t := jwt.New(method) - b.ReportAllocs() - b.ResetTimer() b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err := t.SignedString(key); err != nil { diff --git a/test/ed25519-private.pem b/test/ed25519-private.pem deleted file mode 100644 index e77a58c8..00000000 --- a/test/ed25519-private.pem +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PRIVATE KEY----- -MC4CAQAwBQYDK2VwBCIEIEFMEZrmlYxczXKFxIlNvNGR5JQvDhTkLovJYxwQd3ua ------END PRIVATE KEY----- diff --git a/test/ed25519-public.pem b/test/ed25519-public.pem deleted file mode 100644 index 37c34c50..00000000 --- a/test/ed25519-public.pem +++ /dev/null @@ -1,3 +0,0 @@ ------BEGIN PUBLIC KEY----- -MCowBQYDK2VwAyEAWH7z6hpYqvPns2i4n9yymwvB3APhi4LyQ7iHOT6crtE= ------END PUBLIC KEY----- diff --git a/token.go b/token.go index 6b30ced1..99868d29 100644 --- a/token.go +++ b/token.go @@ -95,10 +95,14 @@ func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token // Encode JWT specific base64url encoding with padding stripped func EncodeSegment(seg []byte) string { - return base64.RawURLEncoding.EncodeToString(seg) + return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=") } // Decode JWT specific base64url encoding with padding stripped func DecodeSegment(seg string) ([]byte, error) { - return base64.RawURLEncoding.DecodeString(seg) + if l := len(seg) % 4; l > 0 { + seg += strings.Repeat("=", 4-l) + } + + return base64.URLEncoding.DecodeString(seg) }