From abf9b1b8220fa989993418b5665e4cb2a3f37513 Mon Sep 17 00:00:00 2001 From: lBilali Date: Thu, 20 Aug 2015 10:53:58 +0200 Subject: [PATCH 01/13] Update .travis.yml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index dfe286eb..d6089146 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,3 +3,5 @@ language: go go: - 1.3.3 - 1.4.2 + - 1.5 + - tip From c1da56349675b292d3200463e2c88b9aa5e02391 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Fri, 4 Sep 2015 14:24:56 -0700 Subject: [PATCH 02/13] Added comment about adding custom signing methods to README --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 1435ddba..001c0a33 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,12 @@ Parsing and verifying tokens is pretty straight forward. You pass in the token tokenString, err := token.SignedString(mySigningKey) ``` +## Extensions + +This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`. + +Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go + ## Project Status & Versioning This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason). From 127f538a322904237d60741156a52f6450c1054a Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 14 Sep 2015 12:07:08 -0700 Subject: [PATCH 03/13] updated documenation of SigningMethod interface --- signing_method.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/signing_method.go b/signing_method.go index 109dd0f5..12cf0f3d 100644 --- a/signing_method.go +++ b/signing_method.go @@ -2,11 +2,11 @@ package jwt var signingMethods = map[string]func() SigningMethod{} -// Signing method +// Implement SigningMethod to add new methods for signing or verifying tokens. type SigningMethod interface { - Verify(signingString, signature string, key interface{}) error - Sign(signingString string, key interface{}) (string, error) - Alg() string + Verify(signingString, signature string, key interface{}) error // Returns nil if signature is valid + Sign(signingString string, key interface{}) (string, error) // Returns encoded signature or error + Alg() string // returns the alg identifier for this method (example: 'HS256') } // Register the "alg" name and a factory function for signing method. From 581ca994780e6c9cf16cda1c7f71aa5549639f09 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 14 Sep 2015 12:07:47 -0700 Subject: [PATCH 04/13] minor refactor of HMAC verify for legibility. no functional changes --- hmac.go | 42 ++++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/hmac.go b/hmac.go index 402ff085..192e625f 100644 --- a/hmac.go +++ b/hmac.go @@ -44,26 +44,36 @@ func (m *SigningMethodHMAC) Alg() string { return m.Name } +// Verify the signature of HSXXX tokens. Returns nil if the signature is valid. func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error { - if keyBytes, ok := key.([]byte); ok { - var sig []byte - var err error - if sig, err = DecodeSegment(signature); err == nil { - if !m.Hash.Available() { - return ErrHashUnavailable - } - - hasher := hmac.New(m.Hash.New, keyBytes) - hasher.Write([]byte(signingString)) - - if !hmac.Equal(sig, hasher.Sum(nil)) { - err = ErrSignatureInvalid - } - } + // Verify the key is the right type + keyBytes, ok := key.([]byte) + if !ok { + return ErrInvalidKey + } + + // Decode signature, for comparison + sig, err := DecodeSegment(signature) + if err != nil { return err } - return ErrInvalidKey + // Can we use the specified hashing method? + if !m.Hash.Available() { + return ErrHashUnavailable + } + + // This signing method is symmetric, so we validate the signature + // by reproducing the signature from the signing string and key, then + // comparing that against the provided signature. + hasher := hmac.New(m.Hash.New, keyBytes) + hasher.Write([]byte(signingString)) + if !hmac.Equal(sig, hasher.Sum(nil)) { + return ErrSignatureInvalid + } + + // No validation errors. Signature is good. + return nil } // Implements the Sign method from SigningMethod for this signing method. From 5bc4686aabfd6ac6bd1572082dd90305c8c71fc1 Mon Sep 17 00:00:00 2001 From: Evan Phoenix Date: Wed, 16 Sep 2015 19:53:08 -0700 Subject: [PATCH 05/13] Fix ES signature serialization --- ecdsa.go | 63 ++++++++++++++++++++++++++++++--------------------- ecdsa_test.go | 6 ++--- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/ecdsa.go b/ecdsa.go index 9cd538ff..0518ed10 100644 --- a/ecdsa.go +++ b/ecdsa.go @@ -4,7 +4,6 @@ import ( "crypto" "crypto/ecdsa" "crypto/rand" - "encoding/asn1" "errors" "math/big" ) @@ -16,14 +15,10 @@ var ( // Implements the ECDSA family of signing methods signing methods type SigningMethodECDSA struct { - Name string - Hash crypto.Hash -} - -// Marshalling structure for r, s EC point -type ECPoint struct { - R *big.Int - S *big.Int + Name string + Hash crypto.Hash + KeySize int + CurveBits int } // Specific instances for EC256 and company @@ -35,19 +30,19 @@ var ( func init() { // ES256 - SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256} + SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256} RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod { return SigningMethodES256 }) // ES384 - SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384} + SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384} RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod { return SigningMethodES384 }) // ES512 - SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512} + SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521} RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod { return SigningMethodES512 }) @@ -77,12 +72,13 @@ func (m *SigningMethodECDSA) Verify(signingString, signature string, key interfa return ErrInvalidKey } - // Unmarshal asn1 ECPoint - var ecpoint = new(ECPoint) - if _, err := asn1.Unmarshal(sig, ecpoint); err != nil { - return err + if len(sig) != 2*m.KeySize { + return ErrECDSAVerification } + r := big.NewInt(0).SetBytes(sig[:m.KeySize]) + s := big.NewInt(0).SetBytes(sig[m.KeySize:]) + // Create hasher if !m.Hash.Available() { return ErrHashUnavailable @@ -91,7 +87,7 @@ func (m *SigningMethodECDSA) Verify(signingString, signature string, key interfa hasher.Write([]byte(signingString)) // Verify the signature - if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), ecpoint.R, ecpoint.S); verifystatus == true { + if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true { return nil } else { return ErrECDSAVerification @@ -120,16 +116,31 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string // Sign the string and return r, s if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil { - // asn1 marhsal r, s using ecPoint as the structure - var ecpoint = new(ECPoint) - ecpoint.R = r - ecpoint.S = s - - if signature, err := asn1.Marshal(*ecpoint); err != nil { - return "", err - } else { - return EncodeSegment(signature), nil + curveBits := ecdsaKey.Curve.Params().BitSize + + if m.CurveBits != curveBits { + return "", ErrInvalidKey } + + keyBytes := curveBits / 8 + if curveBits%8 > 0 { + keyBytes += 1 + } + + // 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 { return "", err } diff --git a/ecdsa_test.go b/ecdsa_test.go index 98e3e5ed..753047b1 100644 --- a/ecdsa_test.go +++ b/ecdsa_test.go @@ -20,7 +20,7 @@ var ecdsaTestData = []struct { { "Basic ES256", map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"}, - "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8w", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ", "ES256", map[string]interface{}{"foo": "bar"}, true, @@ -28,7 +28,7 @@ var ecdsaTestData = []struct { { "Basic ES384", map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"}, - "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MGUCMQCHBr61FXDuFY9xUhyp8iWQAuBIaSgaf1z2j_8XrKcCfzTPzoSa3SZKq-m3L492xe8CMG3kafRMeuaN5Aw8ZJxmOLhkTo4D3-LaGzcaUWINvWvkwFMl7dMC863s0gov6xvXuA", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld", "ES384", map[string]interface{}{"foo": "bar"}, true, @@ -36,7 +36,7 @@ var ecdsaTestData = []struct { { "Basic ES512", map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"}, - "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MIGIAkIAmVKjdJE5lG1byOFgZZVTeNDRp6E7SNvUj0UrvpzoBH6nrleWVTcwfHzbwWuooNpPADDSFR_Ql3ze-Vwwi8hBqQsCQgHn-ZooL8zegkOVeEEsqd7WHWdhb8UekFCYw3X8JnNP-D3wvZQ1-tkkHakt5gZ2-xO29TxfSPun4ViGkMYa7Q4N-Q", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5", "ES512", map[string]interface{}{"foo": "bar"}, true, From 80be4727a30483e5fe5fcc85e42cdd2785b3607c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20Pila=C5=99?= Date: Thu, 15 Oct 2015 08:38:32 +0200 Subject: [PATCH 06/13] Added parsing of ECDSA keys in the example app --- cmd/jwt/app.go | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index 62cb9a46..75db9d26 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -116,7 +116,14 @@ func verifyToken() error { // Parse the token. Load the key from command line option token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) { - return loadData(*flagKey) + data, err := loadData(*flagKey) + if err != nil { + return nil, err + } + if isEs() { + return jwt.ParseECPublicKeyFromPEM(data) + } + return data, nil }) // Print some debug data @@ -161,7 +168,8 @@ func signToken() error { } // get the key - keyData, err := loadData(*flagKey) + var key interface{} + key, err = loadData(*flagKey) if err != nil { return fmt.Errorf("Couldn't read key: %v", err) } @@ -176,7 +184,18 @@ func signToken() error { token := jwt.New(alg) token.Claims = claims - if out, err := token.SignedString(keyData); err == nil { + if isEs() { + if k, ok := key.([]byte); !ok { + return fmt.Errorf("Couldn't convert key data to key") + } else { + key, err = jwt.ParseECPrivateKeyFromPEM(k) + if err != nil { + return err + } + } + } + + if out, err := token.SignedString(key); err == nil { fmt.Println(out) } else { return fmt.Errorf("Error signing token: %v", err) @@ -184,3 +203,7 @@ func signToken() error { return nil } + +func isEs() bool { + return (*flagAlg)[0:2] == "ES" +} From acc5a5f31e3493f1992315fd85aa49226372b810 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 20 Oct 2015 13:53:03 -0700 Subject: [PATCH 07/13] fixes #95 - incorrect documentation --- rsa_pss.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rsa_pss.go b/rsa_pss.go index 0cedd5ee..b5b70735 100644 --- a/rsa_pss.go +++ b/rsa_pss.go @@ -69,7 +69,7 @@ func init() { } // Implements the Verify method from SigningMethod -// For this verify method, key must be an rsa.PrivateKey struct +// For this verify method, key must be an rsa.PublicKey struct func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error { var err error @@ -98,7 +98,7 @@ func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interf } // Implements the Sign method from SigningMethod -// For this signing method, key must be an rsa.PublicKey struct +// For this signing method, key must be an rsa.PrivateKey struct func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) { var rsaKey *rsa.PrivateKey From bdfe8ca0b6948aac25efaf3298640ae6d092b6b4 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Thu, 29 Oct 2015 11:45:16 -0700 Subject: [PATCH 08/13] created a Parser type to allow for non-global parser configuration --- jwt.go | 74 +----------------------------------- parser.go | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 73 deletions(-) create mode 100644 parser.go diff --git a/jwt.go b/jwt.go index 06995aa6..d35aaa4a 100644 --- a/jwt.go +++ b/jwt.go @@ -84,79 +84,7 @@ func (t *Token) SigningString() (string, error) { // keyFunc will receive the parsed token and should return the key for validating. // If everything is kosher, err will be nil func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { - parts := strings.Split(tokenString, ".") - if len(parts) != 3 { - return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed} - } - - var err error - token := &Token{Raw: tokenString} - // parse Header - var headerBytes []byte - if headerBytes, err = DecodeSegment(parts[0]); err != nil { - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} - } - if err = json.Unmarshal(headerBytes, &token.Header); err != nil { - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} - } - - // parse Claims - var claimBytes []byte - if claimBytes, err = DecodeSegment(parts[1]); err != nil { - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} - } - if err = json.Unmarshal(claimBytes, &token.Claims); err != nil { - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} - } - - // Lookup signature method - if method, ok := token.Header["alg"].(string); ok { - if token.Method = GetSigningMethod(method); token.Method == nil { - return token, &ValidationError{err: "signing method (alg) is unavailable.", Errors: ValidationErrorUnverifiable} - } - } else { - return token, &ValidationError{err: "signing method (alg) is unspecified.", Errors: ValidationErrorUnverifiable} - } - - // Lookup key - var key interface{} - if keyFunc == nil { - // keyFunc was not provided. short circuiting validation - return token, &ValidationError{err: "no Keyfunc was provided.", Errors: ValidationErrorUnverifiable} - } - if key, err = keyFunc(token); err != nil { - // keyFunc returned an error - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable} - } - - // Check expiration times - vErr := &ValidationError{} - now := TimeFunc().Unix() - if exp, ok := token.Claims["exp"].(float64); ok { - if now > int64(exp) { - vErr.err = "token is expired" - vErr.Errors |= ValidationErrorExpired - } - } - if nbf, ok := token.Claims["nbf"].(float64); ok { - if now < int64(nbf) { - vErr.err = "token is not valid yet" - vErr.Errors |= ValidationErrorNotValidYet - } - } - - // Perform validation - if err = token.Method.Verify(strings.Join(parts[0:2], "."), parts[2], key); err != nil { - vErr.err = err.Error() - vErr.Errors |= ValidationErrorSignatureInvalid - } - - if vErr.valid() { - token.Valid = true - return token, nil - } - - return token, vErr + return new(Parser).Parse(tokenString, keyFunc) } // Try to find the token in an http.Request. diff --git a/parser.go b/parser.go new file mode 100644 index 00000000..7c0a8919 --- /dev/null +++ b/parser.go @@ -0,0 +1,111 @@ +package jwt + +import ( + "bytes" + "encoding/json" + "fmt" + "strings" +) + +type Parser struct { + ValidMethods []string // If populated, only these methods will be considered valid + UseJSONNumber bool // Use JSON Number format in JSON decoder +} + +// Parse, validate, and return a token. +// keyFunc will receive the parsed token and should return the key for validating. +// If everything is kosher, err will be nil +func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { + parts := strings.Split(tokenString, ".") + if len(parts) != 3 { + return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed} + } + + var err error + token := &Token{Raw: tokenString} + // parse Header + var headerBytes []byte + if headerBytes, err = DecodeSegment(parts[0]); err != nil { + return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} + } + if err = json.Unmarshal(headerBytes, &token.Header); err != nil { + return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} + } + + // Verify signing method is in the required set + if p.ValidMethods != nil { + var signingMethodValid = false + var alg = token.Method.Alg() + for _, m := range p.ValidMethods { + if m == alg { + signingMethodValid = true + break + } + } + if !signingMethodValid { + return nil, &ValidationError{err: fmt.Sprintf("signing method %v is invalid", alg), Errors: ValidationErrorSignatureInvalid} + } + } + + // parse Claims + var claimBytes []byte + if claimBytes, err = DecodeSegment(parts[1]); err != nil { + return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} + } + dec := json.NewDecoder(bytes.NewBuffer(claimBytes)) + if p.UseJSONNumber { + dec.UseNumber() + } + if err = dec.Decode(&token.Claims); err != nil { + return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} + } + + // Lookup signature method + if method, ok := token.Header["alg"].(string); ok { + if token.Method = GetSigningMethod(method); token.Method == nil { + return token, &ValidationError{err: "signing method (alg) is unavailable.", Errors: ValidationErrorUnverifiable} + } + } else { + return token, &ValidationError{err: "signing method (alg) is unspecified.", Errors: ValidationErrorUnverifiable} + } + + // Lookup key + var key interface{} + if keyFunc == nil { + // keyFunc was not provided. short circuiting validation + return token, &ValidationError{err: "no Keyfunc was provided.", Errors: ValidationErrorUnverifiable} + } + if key, err = keyFunc(token); err != nil { + // keyFunc returned an error + return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable} + } + + // Check expiration times + vErr := &ValidationError{} + now := TimeFunc().Unix() + if exp, ok := token.Claims["exp"].(float64); ok { + if now > int64(exp) { + vErr.err = "token is expired" + vErr.Errors |= ValidationErrorExpired + } + } + if nbf, ok := token.Claims["nbf"].(float64); ok { + if now < int64(nbf) { + vErr.err = "token is not valid yet" + vErr.Errors |= ValidationErrorNotValidYet + } + } + + // Perform validation + if err = token.Method.Verify(strings.Join(parts[0:2], "."), parts[2], key); err != nil { + vErr.err = err.Error() + vErr.Errors |= ValidationErrorSignatureInvalid + } + + if vErr.valid() { + token.Valid = true + return token, nil + } + + return token, vErr +} From 7ac27fb6aceabdb7fabcb372a75c669d53c97a26 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 2 Nov 2015 11:26:07 -0800 Subject: [PATCH 09/13] renamed files to match their purpose --- jwt_test.go => parser_test.go | 0 jwt.go => token.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename jwt_test.go => parser_test.go (100%) rename jwt.go => token.go (100%) diff --git a/jwt_test.go b/parser_test.go similarity index 100% rename from jwt_test.go rename to parser_test.go diff --git a/jwt.go b/token.go similarity index 100% rename from jwt.go rename to token.go From 774f319043f4c9806948f75a8fc1c106caae3b75 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 2 Nov 2015 15:22:08 -0800 Subject: [PATCH 10/13] added tests for parser. fixed parser when using restricted method list --- parser.go | 31 ++++++++++++++++--------------- parser_test.go | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/parser.go b/parser.go index 7c0a8919..40b02bcd 100644 --- a/parser.go +++ b/parser.go @@ -32,21 +32,6 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} } - // Verify signing method is in the required set - if p.ValidMethods != nil { - var signingMethodValid = false - var alg = token.Method.Alg() - for _, m := range p.ValidMethods { - if m == alg { - signingMethodValid = true - break - } - } - if !signingMethodValid { - return nil, &ValidationError{err: fmt.Sprintf("signing method %v is invalid", alg), Errors: ValidationErrorSignatureInvalid} - } - } - // parse Claims var claimBytes []byte if claimBytes, err = DecodeSegment(parts[1]); err != nil { @@ -69,6 +54,22 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { return token, &ValidationError{err: "signing method (alg) is unspecified.", Errors: ValidationErrorUnverifiable} } + // Verify signing method is in the required set + if p.ValidMethods != nil { + var signingMethodValid = false + var alg = token.Method.Alg() + for _, m := range p.ValidMethods { + if m == alg { + signingMethodValid = true + break + } + } + if !signingMethodValid { + // signing method is not in the listed set + return token, &ValidationError{err: fmt.Sprintf("signing method %v is invalid", alg), Errors: ValidationErrorSignatureInvalid} + } + } + // Lookup key var key interface{} if keyFunc == nil { diff --git a/parser_test.go b/parser_test.go index 9108dedb..6592affd 100644 --- a/parser_test.go +++ b/parser_test.go @@ -1,6 +1,7 @@ package jwt_test import ( + "encoding/json" "fmt" "github.com/dgrijalva/jwt-go" "io/ioutil" @@ -25,6 +26,7 @@ var jwtTestData = []struct { claims map[string]interface{} valid bool errors uint32 + parser *jwt.Parser }{ { "basic", @@ -33,6 +35,7 @@ var jwtTestData = []struct { map[string]interface{}{"foo": "bar"}, true, 0, + nil, }, { "basic expired", @@ -41,6 +44,7 @@ var jwtTestData = []struct { map[string]interface{}{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, false, jwt.ValidationErrorExpired, + nil, }, { "basic nbf", @@ -49,6 +53,7 @@ var jwtTestData = []struct { map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, false, jwt.ValidationErrorNotValidYet, + nil, }, { "expired and nbf", @@ -57,6 +62,7 @@ var jwtTestData = []struct { map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)}, false, jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, + nil, }, { "basic invalid", @@ -65,6 +71,7 @@ var jwtTestData = []struct { map[string]interface{}{"foo": "bar"}, false, jwt.ValidationErrorSignatureInvalid, + nil, }, { "basic nokeyfunc", @@ -73,6 +80,7 @@ var jwtTestData = []struct { map[string]interface{}{"foo": "bar"}, false, jwt.ValidationErrorUnverifiable, + nil, }, { "basic nokey", @@ -81,6 +89,7 @@ var jwtTestData = []struct { map[string]interface{}{"foo": "bar"}, false, jwt.ValidationErrorSignatureInvalid, + nil, }, { "basic errorkey", @@ -89,6 +98,25 @@ var jwtTestData = []struct { map[string]interface{}{"foo": "bar"}, false, jwt.ValidationErrorUnverifiable, + nil, + }, + { + "invalid signing method", + "", + defaultKeyFunc, + map[string]interface{}{"foo": "bar"}, + false, + jwt.ValidationErrorSignatureInvalid, + &jwt.Parser{ValidMethods: []string{"HS256"}}, + }, + { + "JSON Number", + "", + defaultKeyFunc, + map[string]interface{}{"foo": json.Number("123.4")}, + true, + 0, + &jwt.Parser{UseJSONNumber: true}, }, } @@ -116,12 +144,19 @@ func makeSample(c map[string]interface{}) string { return s } -func TestJWT(t *testing.T) { +func TestParser_Parse(t *testing.T) { for _, data := range jwtTestData { if data.tokenString == "" { data.tokenString = makeSample(data.claims) } - token, err := jwt.Parse(data.tokenString, data.keyfunc) + + var token *jwt.Token + var err error + if data.parser != nil { + token, err = data.parser.Parse(data.tokenString, data.keyfunc) + } else { + token, err = jwt.Parse(data.tokenString, data.keyfunc) + } if !reflect.DeepEqual(data.claims, token.Claims) { t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) @@ -137,8 +172,8 @@ func TestJWT(t *testing.T) { t.Errorf("[%v] Expecting error. Didn't get one.", data.name) } else { // compare the bitfield part of the error - if err.(*jwt.ValidationError).Errors != data.errors { - t.Errorf("[%v] Errors don't match expectation", data.name) + if e := err.(*jwt.ValidationError).Errors; e != data.errors { + t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors) } } @@ -149,6 +184,12 @@ func TestJWT(t *testing.T) { func TestParseRequest(t *testing.T) { // Bearer token request for _, data := range jwtTestData { + // FIXME: custom parsers are not supported by this helper. skip tests that require them + if data.parser != nil { + t.Logf("Skipping [%v]. Custom parsers are not supported by ParseRequest", data.name) + continue + } + if data.tokenString == "" { data.tokenString = makeSample(data.claims) } From f2dd936aa8b13b59cead3f1432b83111c442419d Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 2 Nov 2015 15:24:32 -0800 Subject: [PATCH 11/13] added test for valid signing method --- parser_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/parser_test.go b/parser_test.go index 6592affd..97d9eee0 100644 --- a/parser_test.go +++ b/parser_test.go @@ -109,6 +109,15 @@ var jwtTestData = []struct { jwt.ValidationErrorSignatureInvalid, &jwt.Parser{ValidMethods: []string{"HS256"}}, }, + { + "valid signing method", + "", + defaultKeyFunc, + map[string]interface{}{"foo": "bar"}, + true, + 0, + &jwt.Parser{ValidMethods: []string{"RS256", "HS256"}}, + }, { "JSON Number", "", From dd0ede9741811ecd8ea9b9b952fb5073f2cfabfa Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Fri, 13 Nov 2015 12:50:04 -0800 Subject: [PATCH 12/13] fix array OOB panic (#100) --- cmd/jwt/app.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index 75db9d26..a78d81a6 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -205,5 +205,8 @@ func signToken() error { } func isEs() bool { - return (*flagAlg)[0:2] == "ES" + if alg := *flagAlg; len(alg) >= 2 && alg[0:2] == "ES" { + return true + } + return false } From d9df83c8271c7383117aef118ae3b84feca03f23 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Fri, 13 Nov 2015 15:03:12 -0800 Subject: [PATCH 13/13] use cleaner version of prefix checking (thanks shurcooL) --- cmd/jwt/app.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index a78d81a6..4068a805 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -14,6 +14,7 @@ import ( "io/ioutil" "os" "regexp" + "strings" "github.com/dgrijalva/jwt-go" ) @@ -205,8 +206,5 @@ func signToken() error { } func isEs() bool { - if alg := *flagAlg; len(alg) >= 2 && alg[0:2] == "ES" { - return true - } - return false + return strings.HasPrefix(*flagAlg, "ES") }