From 517905c5bd1c1bcb8b7aba66a8fa259cb3134365 Mon Sep 17 00:00:00 2001 From: Emanoel Xavier Date: Thu, 31 Dec 2015 07:48:39 -0800 Subject: [PATCH 1/8] Adding inner error in the ValidationError type --- errors.go | 1 + parser.go | 2 +- parser_test.go | 15 ++++++++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/errors.go b/errors.go index e9e788f..eb30a93 100644 --- a/errors.go +++ b/errors.go @@ -23,6 +23,7 @@ const ( // The error from Parse if token is not valid type ValidationError struct { err string + Inner error // stores the error returned by external dependencies, i.e.: KeyFunc Errors uint32 // bitfield. see ValidationError... constants } diff --git a/parser.go b/parser.go index 3fc27bf..ff751b3 100644 --- a/parser.go +++ b/parser.go @@ -78,7 +78,7 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { } if key, err = keyFunc(token); err != nil { // keyFunc returned an error - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable} + return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable, Inner: err} } // Check expiration times diff --git a/parser_test.go b/parser_test.go index 9115017..4cf9751 100644 --- a/parser_test.go +++ b/parser_test.go @@ -3,19 +3,22 @@ package jwt_test import ( "encoding/json" "fmt" - "github.com/dgrijalva/jwt-go" "io/ioutil" "net/http" "reflect" "testing" "time" + + "github.com/dgrijalva/jwt-go" ) +var keyFuncError error = fmt.Errorf("error loading key") + var ( jwtTestDefaultKey []byte defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil } emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil } - errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, fmt.Errorf("error loading key") } + errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, keyFuncError } nilKeyFunc jwt.Keyfunc = nil ) @@ -180,10 +183,16 @@ func TestParser_Parse(t *testing.T) { if err == nil { t.Errorf("[%v] Expecting error. Didn't get one.", data.name) } else { + + ve := err.(*jwt.ValidationError) // compare the bitfield part of the error - if e := err.(*jwt.ValidationError).Errors; e != data.errors { + if e := ve.Errors; e != data.errors { t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors) } + + if err.Error() == keyFuncError.Error() && ve.Inner != keyFuncError { + t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, keyFuncError) + } } } if data.valid && token.Signature == "" { From 9249eabf87db369c203fd7ebf4b26bd5476329c5 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 12 Apr 2016 17:31:30 -0700 Subject: [PATCH 2/8] expose inner error within ValidationError --- errors.go | 13 ++++++++++--- none.go | 10 ++++------ parser.go | 28 ++++++++++++++-------------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/errors.go b/errors.go index eb30a93..a6b60a3 100644 --- a/errors.go +++ b/errors.go @@ -20,19 +20,26 @@ const ( ValidationErrorNotValidYet // NBF validation failed ) +// Helper for constructing a ValidationError with a string error message +func NewValidationError(errorText string, errorFlags uint32) *ValidationError { + return &ValidationError{ + Inner: errors.New(errorText), + Errors: errorFlags, + } +} + // The error from Parse if token is not valid type ValidationError struct { - err string Inner error // stores the error returned by external dependencies, i.e.: KeyFunc Errors uint32 // bitfield. see ValidationError... constants } // Validation error is an error type func (e ValidationError) Error() string { - if e.err == "" { + if e.Inner == nil { return "token is invalid" } - return e.err + return e.Inner.Error() } // No errors diff --git a/none.go b/none.go index d697578..f04d189 100644 --- a/none.go +++ b/none.go @@ -13,10 +13,8 @@ type unsafeNoneMagicConstant string func init() { SigningMethodNone = &signingMethodNone{} - NoneSignatureTypeDisallowedError = &ValidationError{ - "'none' signature type is not allowed", - ValidationErrorSignatureInvalid, - } + NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid) + RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod { return SigningMethodNone }) @@ -35,10 +33,10 @@ func (m *signingMethodNone) Verify(signingString, signature string, key interfac } // If signing method is none, signature must be an empty string if signature != "" { - return &ValidationError{ + return NewValidationError( "'none' signing method with non-empty signature", ValidationErrorSignatureInvalid, - } + ) } // Accept 'none' signing method. diff --git a/parser.go b/parser.go index c859393..a1accc2 100644 --- a/parser.go +++ b/parser.go @@ -18,7 +18,7 @@ type Parser struct { 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} + return nil, NewValidationError("token contains an invalid number of segments", ValidationErrorMalformed) } var err error @@ -27,34 +27,34 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { var headerBytes []byte if headerBytes, err = DecodeSegment(parts[0]); err != nil { if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { - return token, &ValidationError{err: "tokenstring should not contain 'bearer '", Errors: ValidationErrorMalformed} + return token, NewValidationError("tokenstring should not contain 'bearer '", ValidationErrorMalformed) } - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} + return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} } if err = json.Unmarshal(headerBytes, &token.Header); err != nil { - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} + return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed} } // parse Claims var claimBytes []byte if claimBytes, err = DecodeSegment(parts[1]); err != nil { - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} + return token, &ValidationError{Inner: err, 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} + return token, &ValidationError{Inner: err, 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} + return token, NewValidationError("signing method (alg) is unavailable.", ValidationErrorUnverifiable) } } else { - return token, &ValidationError{err: "signing method (alg) is unspecified.", Errors: ValidationErrorUnverifiable} + return token, NewValidationError("signing method (alg) is unspecified.", ValidationErrorUnverifiable) } // Verify signing method is in the required set @@ -69,7 +69,7 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { } if !signingMethodValid { // signing method is not in the listed set - return token, &ValidationError{err: fmt.Sprintf("signing method %v is invalid", alg), Errors: ValidationErrorSignatureInvalid} + return token, NewValidationError(fmt.Sprintf("signing method %v is invalid", alg), ValidationErrorSignatureInvalid) } } @@ -77,11 +77,11 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { var key interface{} if keyFunc == nil { // keyFunc was not provided. short circuiting validation - return token, &ValidationError{err: "no Keyfunc was provided.", Errors: ValidationErrorUnverifiable} + return token, NewValidationError("no Keyfunc was provided.", ValidationErrorUnverifiable) } if key, err = keyFunc(token); err != nil { // keyFunc returned an error - return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable, Inner: err} + return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable} } // Check expiration times @@ -113,19 +113,19 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { } if vexp && now > exp { - vErr.err = "token is expired" + vErr.Inner = fmt.Errorf("token is expired") vErr.Errors |= ValidationErrorExpired } if vnbf && now < nbf { - vErr.err = "token is not valid yet" + vErr.Inner = fmt.Errorf("token is not valid yet") vErr.Errors |= ValidationErrorNotValidYet } // Perform validation token.Signature = parts[2] if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { - vErr.err = err.Error() + vErr.Inner = err vErr.Errors |= ValidationErrorSignatureInvalid } From a2c85815a77d0f951e33ba4db5ae93629a1530af Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 12 Apr 2016 17:36:00 -0700 Subject: [PATCH 3/8] release notes --- VERSION_HISTORY.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/VERSION_HISTORY.md b/VERSION_HISTORY.md index 14cf1a8..3918734 100644 --- a/VERSION_HISTORY.md +++ b/VERSION_HISTORY.md @@ -1,9 +1,15 @@ ## `jwt-go` Version History -#### 2.5.0 +#### 2.6.0 This will likely be the last backwards compatible release before 3.0.0. +* Exposed inner error within ValidationError +* Fixed validation errors when using UseJSONNumber flag +* Added several unit tests + +#### 2.5.0 + * Added support for signing method none. You shouldn't use this. The API tries to make this clear. * Updated/fixed some documentation * Added more helpful error message when trying to parse tokens that begin with `BEARER ` From 36ab8fba559509e8194d8041699fb5fdd33c88e8 Mon Sep 17 00:00:00 2001 From: Alexandre Bourget Date: Thu, 14 Apr 2016 14:25:22 -0400 Subject: [PATCH 4/8] Implement a "-show" command too.. for debugging purposes.. --- cmd/jwt/README.md | 13 +++++++++++++ cmd/jwt/app.go | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 cmd/jwt/README.md diff --git a/cmd/jwt/README.md b/cmd/jwt/README.md new file mode 100644 index 0000000..4a68ba4 --- /dev/null +++ b/cmd/jwt/README.md @@ -0,0 +1,13 @@ +`jwt` command-line tool +======================= + +This is a simple tool to sign, verify and show JSON Web Tokens from +the command line. + +The following will create and sign a token, then verify it and output the original claims: + + echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify - + +To simply display a token, use: + + echo $JWT | jwt -show - diff --git a/cmd/jwt/app.go b/cmd/jwt/app.go index 4068a80..e8bc336 100644 --- a/cmd/jwt/app.go +++ b/cmd/jwt/app.go @@ -16,7 +16,7 @@ import ( "regexp" "strings" - "github.com/dgrijalva/jwt-go" + jwt "github.com/dgrijalva/jwt-go" ) var ( @@ -29,6 +29,7 @@ var ( // Modes - exactly one of these is required flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin") flagVerify = flag.String("verify", "", "path to JWT token to verify or '-' to read from stdin") + flagShow = flag.String("show", "", "path to JWT file or '-' to read from stdin") ) func main() { @@ -56,6 +57,8 @@ func start() error { return signToken() } else if *flagVerify != "" { return verifyToken() + } else if *flagShow != "" { + return showToken() } else { flag.Usage() return fmt.Errorf("None of the required flags are present. What do you want me to do?") @@ -205,6 +208,39 @@ func signToken() error { return nil } +// showToken pretty-prints the token on the command line. +func showToken() error { + // get the token + tokData, err := loadData(*flagShow) + if err != nil { + return fmt.Errorf("Couldn't read token: %v", err) + } + + // trim possible whitespace from token + tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{}) + if *flagDebug { + fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData)) + } + + token, err := jwt.Parse(string(tokData), nil) + if token == nil { + return fmt.Errorf("malformed token: %v", err) + } + + // Print the token details + fmt.Println("Header:") + if err := printJSON(token.Header); err != nil { + return fmt.Errorf("Failed to output header: %v", err) + } + + fmt.Println("Claims:") + if err := printJSON(token.Claims); err != nil { + return fmt.Errorf("Failed to output claims: %v", err) + } + + return nil +} + func isEs() bool { return strings.HasPrefix(*flagAlg, "ES") } From 96fef824970c561c9ded281e9789ce907be05d56 Mon Sep 17 00:00:00 2001 From: "John.Lockwood" Date: Mon, 25 Apr 2016 11:34:10 -0700 Subject: [PATCH 5/8] Include expire delta in error message --- parser.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/parser.go b/parser.go index a1accc2..fbde9cb 100644 --- a/parser.go +++ b/parser.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "strings" + "time" ) type Parser struct { @@ -113,7 +114,8 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { } if vexp && now > exp { - vErr.Inner = fmt.Errorf("token is expired") + delta := time.Unix(now, 0).Sub(time.Unix(exp, 0)) + vErr.Inner = fmt.Errorf("token is expired by %v", delta) vErr.Errors |= ValidationErrorExpired } From 40bd0f3b4891a9d7f121bfb7b8e8b0525625e262 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Wed, 4 May 2016 10:25:48 -0700 Subject: [PATCH 6/8] fixes #135 copy/paste error in rsa decoding tools --- rsa_utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rsa_utils.go b/rsa_utils.go index 6f3b6ff..213a90d 100644 --- a/rsa_utils.go +++ b/rsa_utils.go @@ -10,6 +10,7 @@ import ( var ( ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key") ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key") + ErrNotRSAPublicKey = errors.New("Key is not a valid RSA public key") ) // Parse PEM encoded PKCS1 or PKCS8 private key @@ -61,7 +62,7 @@ func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { var pkey *rsa.PublicKey var ok bool if pkey, ok = parsedKey.(*rsa.PublicKey); !ok { - return nil, ErrNotRSAPrivateKey + return nil, ErrNotRSAPublicKey } return pkey, nil From e1403b0ab234872a9a95375ba84b346f72940166 Mon Sep 17 00:00:00 2001 From: Benjamin Ruston Date: Fri, 27 May 2016 14:22:32 +0100 Subject: [PATCH 7/8] Fix typo in KeyFunc documentation --- token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token.go b/token.go index 1cf267d..c275333 100644 --- a/token.go +++ b/token.go @@ -15,7 +15,7 @@ var TimeFunc = time.Now // Parse methods use this callback function to supply // the key for verification. The function receives the parsed, -// but unverified Token. This allows you to use propries in the +// but unverified Token. This allows you to use properties in the // Header of the token (such as `kid`) to identify which key to use. type Keyfunc func(*Token) (interface{}, error) From c04502f106d7c5b3fae17c5da49a1bbdd3006b3c Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 6 Jun 2016 17:56:07 -0700 Subject: [PATCH 8/8] notice about imminent 3.0.0 --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index bf0100f..88448eb 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ A [go](http://www.golang.org) (or 'golang' for search engine friendliness) imple [![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go) +**BREAKING CHANGES COMING:*** Version 3.0.0 is almost complete. It will include _a lot_ of changes including a few that break the API. We've tried to break as few things as possible, so there should just be a few type signature changes. A full list of breaking changes will be available before 3.0.0 lands. If you would like to have any input befor 3.0.0 is locked, now's the time to review and provide feedback. + **NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected. There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API. In the short term, please make sure your implementation verifies the `alg` is what you expect. ## What the heck is a JWT?