Merge remote-tracking branch 'origin/master' into release_3_0_0

This commit is contained in:
Dave Grijalva 2016-06-06 18:20:35 -07:00
commit 317b82a681
12 changed files with 111 additions and 34 deletions

View File

@ -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) [![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. **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.

View File

@ -17,10 +17,16 @@
* Moved examples from README to executable example files * Moved examples from README to executable example files
* Signing method registry is now thread safe * 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. 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. * Added support for signing method none. You shouldn't use this. The API tries to make this clear.
* Updated/fixed some documentation * Updated/fixed some documentation
* Added more helpful error message when trying to parse tokens that begin with `BEARER ` * Added more helpful error message when trying to parse tokens that begin with `BEARER `

View File

@ -2,6 +2,8 @@ package jwt
import ( import (
"crypto/subtle" "crypto/subtle"
"fmt"
"time"
) )
// For a type to be a Claims object, it must just have a Valid method that determines // For a type to be a Claims object, it must just have a Valid method that determines
@ -34,17 +36,18 @@ func (c StandardClaims) Valid() error {
// The claims below are optional, by default, so if they are set to the // 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. // default value in Go, let's not fail the verification for them.
if c.VerifyExpiresAt(now, false) == false { 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 vErr.Errors |= ValidationErrorExpired
} }
if c.VerifyIssuedAt(now, false) == false { 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 vErr.Errors |= ValidationErrorIssuedAt
} }
if c.VerifyNotBefore(now, false) == false { 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 vErr.Errors |= ValidationErrorNotValidYet
} }

13
cmd/jwt/README.md Normal file
View File

@ -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 -

View File

@ -16,7 +16,7 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
) )
var ( var (
@ -29,6 +29,7 @@ var (
// Modes - exactly one of these is required // Modes - exactly one of these is required
flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin") 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") 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() { func main() {
@ -56,6 +57,8 @@ func start() error {
return signToken() return signToken()
} else if *flagVerify != "" { } else if *flagVerify != "" {
return verifyToken() return verifyToken()
} else if *flagShow != "" {
return showToken()
} else { } else {
flag.Usage() flag.Usage()
return fmt.Errorf("None of the required flags are present. What do you want me to do?") return fmt.Errorf("None of the required flags are present. What do you want me to do?")
@ -204,6 +207,39 @@ func signToken() error {
return nil 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 { func isEs() bool {
return strings.HasPrefix(*flagAlg, "ES") return strings.HasPrefix(*flagAlg, "ES")
} }

View File

@ -26,18 +26,26 @@ const (
ValidationErrorClaimsInvalid // Generic claims validation error 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 // The error from Parse if token is not valid
type ValidationError struct { type ValidationError struct {
err string Inner error // stores the error returned by external dependencies, i.e.: KeyFunc
Errors uint32 // bitfield. see ValidationError... constants Errors uint32 // bitfield. see ValidationError... constants
} }
// Validation error is an error type // Validation error is an error type
func (e ValidationError) Error() string { func (e ValidationError) Error() string {
if e.err == "" { if e.Inner == nil {
return "token is invalid" return "token is invalid"
} }
return e.err return e.Inner.Error()
} }
// No errors // No errors

View File

@ -2,6 +2,8 @@ package jwt
import ( import (
"encoding/json" "encoding/json"
"errors"
// "fmt"
) )
// Claims type that uses the map[string]interface{} for JSON decoding // Claims type that uses the map[string]interface{} for JSON decoding
@ -70,17 +72,17 @@ func (m MapClaims) Valid() error {
now := TimeFunc().Unix() now := TimeFunc().Unix()
if m.VerifyExpiresAt(now, false) == false { if m.VerifyExpiresAt(now, false) == false {
vErr.err = "Token is expired" vErr.Inner = errors.New("Token is expired")
vErr.Errors |= ValidationErrorExpired vErr.Errors |= ValidationErrorExpired
} }
if m.VerifyIssuedAt(now, false) == false { 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 vErr.Errors |= ValidationErrorIssuedAt
} }
if m.VerifyNotBefore(now, false) == false { 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 vErr.Errors |= ValidationErrorNotValidYet
} }

10
none.go
View File

@ -13,10 +13,8 @@ type unsafeNoneMagicConstant string
func init() { func init() {
SigningMethodNone = &signingMethodNone{} SigningMethodNone = &signingMethodNone{}
NoneSignatureTypeDisallowedError = &ValidationError{ NoneSignatureTypeDisallowedError = NewValidationError("'none' signature type is not allowed", ValidationErrorSignatureInvalid)
"'none' signature type is not allowed",
ValidationErrorSignatureInvalid,
}
RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod { RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod {
return SigningMethodNone 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 signing method is none, signature must be an empty string
if signature != "" { if signature != "" {
return &ValidationError{ return NewValidationError(
"'none' signing method with non-empty signature", "'none' signing method with non-empty signature",
ValidationErrorSignatureInvalid, ValidationErrorSignatureInvalid,
} )
} }
// Accept 'none' signing method. // Accept 'none' signing method.

View File

@ -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) { func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
parts := strings.Split(tokenString, ".") parts := strings.Split(tokenString, ".")
if len(parts) != 3 { 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 var err error
@ -32,12 +32,12 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
var headerBytes []byte var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil { if headerBytes, err = DecodeSegment(parts[0]); err != nil {
if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { 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 { 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 // parse Claims
@ -45,7 +45,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
token.Claims = claims token.Claims = claims
if claimBytes, err = DecodeSegment(parts[1]); err != nil { 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)) dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
if p.UseJSONNumber { if p.UseJSONNumber {
@ -59,16 +59,16 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
} }
// Handle decode error // Handle decode error
if err != nil { if err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} return token, &ValidationError{Inner: err, Errors: ValidationErrorMalformed}
} }
// Lookup signature method // Lookup signature method
if method, ok := token.Header["alg"].(string); ok { if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil { 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 { } 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 // Verify signing method is in the required set
@ -83,7 +83,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
} }
if !signingMethodValid { if !signingMethodValid {
// signing method is not in the listed set // 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)
} }
} }
@ -91,11 +91,11 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
var key interface{} var key interface{}
if keyFunc == nil { if keyFunc == nil {
// keyFunc was not provided. short circuiting validation // 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 { if key, err = keyFunc(token); err != nil {
// keyFunc returned an error // keyFunc returned an error
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable} return token, &ValidationError{Inner: err, Errors: ValidationErrorUnverifiable}
} }
vErr := &ValidationError{} vErr := &ValidationError{}
@ -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 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 it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
if e, ok := err.(*ValidationError); !ok { if e, ok := err.(*ValidationError); !ok {
vErr = &ValidationError{err: err.Error(), Errors: ValidationErrorClaimsInvalid} vErr = &ValidationError{Inner: err, Errors: ValidationErrorClaimsInvalid}
} else { } else {
vErr = e vErr = e
} }
@ -115,7 +115,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
// Perform validation // Perform validation
token.Signature = parts[2] token.Signature = parts[2]
if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { 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 vErr.Errors |= ValidationErrorSignatureInvalid
} }

View File

@ -12,11 +12,13 @@ import (
"github.com/dgrijalva/jwt-go/test" "github.com/dgrijalva/jwt-go/test"
) )
var keyFuncError error = fmt.Errorf("error loading key")
var ( var (
jwtTestDefaultKey *rsa.PublicKey jwtTestDefaultKey *rsa.PublicKey
defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil } defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil }
emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, 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 nilKeyFunc jwt.Keyfunc = nil
) )
@ -218,10 +220,16 @@ func TestParser_Parse(t *testing.T) {
if err == nil { if err == nil {
t.Errorf("[%v] Expecting error. Didn't get one.", data.name) t.Errorf("[%v] Expecting error. Didn't get one.", data.name)
} else { } else {
ve := err.(*jwt.ValidationError)
// compare the bitfield part of the error // 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) 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 == "" { if data.valid && token.Signature == "" {

View File

@ -10,6 +10,7 @@ import (
var ( var (
ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key") 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") 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 // Parse PEM encoded PKCS1 or PKCS8 private key
@ -61,7 +62,7 @@ func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) {
var pkey *rsa.PublicKey var pkey *rsa.PublicKey
var ok bool var ok bool
if pkey, ok = parsedKey.(*rsa.PublicKey); !ok { if pkey, ok = parsedKey.(*rsa.PublicKey); !ok {
return nil, ErrNotRSAPrivateKey return nil, ErrNotRSAPublicKey
} }
return pkey, nil return pkey, nil

View File

@ -14,7 +14,7 @@ var TimeFunc = time.Now
// Parse methods use this callback function to supply // Parse methods use this callback function to supply
// the key for verification. The function receives the parsed, // 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. // Header of the token (such as `kid`) to identify which key to use.
type Keyfunc func(*Token) (interface{}, error) type Keyfunc func(*Token) (interface{}, error)