mirror of https://github.com/golang-jwt/jwt.git
Merge remote-tracking branch 'origin/master' into release_3_0_0
This commit is contained in:
commit
317b82a681
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
@ -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 `
|
||||
|
|
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 -
|
|
@ -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?")
|
||||
|
@ -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
errors.go
14
errors.go
|
@ -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
|
||||
|
|
|
@ -2,6 +2,8 @@ package jwt
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
// "fmt"
|
||||
)
|
||||
|
||||
// Claims type that uses the map[string]interface{} for JSON decoding
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
10
none.go
10
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.
|
||||
|
|
26
parser.go
26
parser.go
|
@ -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
|
||||
|
@ -32,12 +32,12 @@ 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
|
||||
|
@ -45,7 +45,7 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
|
|||
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 {
|
||||
|
@ -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
|
||||
|
@ -83,7 +83,7 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,11 +91,11 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
|
|||
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{}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
@ -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 == "" {
|
||||
|
|
|
@ -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
|
||||
|
|
2
token.go
2
token.go
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue