Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into release_3_0_0
Browse files Browse the repository at this point in the history
  • Loading branch information
dgrijalva committed Jun 7, 2016
2 parents fa2cc68 + c04502f commit 317b82a
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 34 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.


Expand Down
8 changes: 7 additions & 1 deletion VERSION_HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,16 @@
* Moved examples from README to executable example files
* Signing method registry is now thread safe

#### 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 `
Expand Down
9 changes: 6 additions & 3 deletions claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package jwt

import (
"crypto/subtle"
"fmt"
"time"
)

// For a type to be a Claims object, it must just have a Valid method that determines
Expand Down Expand Up @@ -34,17 +36,18 @@ func (c StandardClaims) Valid() error {
// The claims below are optional, by default, so if they are set to the
// default value in Go, let's not fail the verification for them.
if c.VerifyExpiresAt(now, false) == false {
vErr.err = "Token is expired"
delta := time.Unix(now, 0).Sub(time.Unix(c.ExpiresAt, 0))
vErr.Inner = fmt.Errorf("token is expired by %v", delta)
vErr.Errors |= ValidationErrorExpired
}

if c.VerifyIssuedAt(now, false) == false {
vErr.err = "Token used before issued, clock skew issue?"
vErr.Inner = fmt.Errorf("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}

if c.VerifyNotBefore(now, false) == false {
vErr.err = "Token is not valid yet"
vErr.Inner = fmt.Errorf("token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}

Expand Down
13 changes: 13 additions & 0 deletions cmd/jwt/README.md
Original file line number Diff line number Diff line change
@@ -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 -
38 changes: 37 additions & 1 deletion cmd/jwt/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
"regexp"
"strings"

"github.com/dgrijalva/jwt-go"
jwt "github.com/dgrijalva/jwt-go"
)

var (
Expand All @@ -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() {
Expand Down Expand Up @@ -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?")
Expand Down Expand Up @@ -204,6 +207,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")
}
14 changes: 11 additions & 3 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,26 @@ const (
ValidationErrorClaimsInvalid // Generic claims validation error
)

// 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
Expand Down
8 changes: 5 additions & 3 deletions map_claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package jwt

import (
"encoding/json"
"errors"
// "fmt"
)

// Claims type that uses the map[string]interface{} for JSON decoding
Expand Down Expand Up @@ -70,17 +72,17 @@ func (m MapClaims) Valid() error {
now := TimeFunc().Unix()

if m.VerifyExpiresAt(now, false) == false {
vErr.err = "Token is expired"
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired
}

if m.VerifyIssuedAt(now, false) == false {
vErr.err = "Token used before issued, clock skew issue?"
vErr.Inner = errors.New("Token used before issued")
vErr.Errors |= ValidationErrorIssuedAt
}

if m.VerifyNotBefore(now, false) == false {
vErr.err = "Token is not valid yet"
vErr.Inner = errors.New("Token is not valid yet")
vErr.Errors |= ValidationErrorNotValidYet
}

Expand Down
10 changes: 4 additions & 6 deletions none.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
Expand All @@ -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.
Expand Down
26 changes: 13 additions & 13 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, 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
Expand All @@ -32,20 +32,20 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
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
token.Claims = claims

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 {
Expand All @@ -59,16 +59,16 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
}
// Handle decode error
if 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
Expand All @@ -83,19 +83,19 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
}
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)
}
}

// 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}
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}
return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
}

vErr := &ValidationError{}
Expand All @@ -106,7 +106,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
// If the Claims Valid returned an error, check if it is a validation error,
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
if e, ok := err.(*ValidationError); !ok {
vErr = &ValidationError{err: err.Error(), Errors: ValidationErrorClaimsInvalid}
vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
} else {
vErr = e
}
Expand All @@ -115,7 +115,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
// 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
}

Expand Down
12 changes: 10 additions & 2 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import (
"github.com/dgrijalva/jwt-go/test"
)

var keyFuncError error = fmt.Errorf("error loading key")

var (
jwtTestDefaultKey *rsa.PublicKey
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
)

Expand Down Expand Up @@ -218,10 +220,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 == "" {
Expand Down
3 changes: 2 additions & 1 deletion rsa_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion token.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,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)

Expand Down

0 comments on commit 317b82a

Please sign in to comment.