Skip to content
This repository has been archived by the owner on May 21, 2022. It is now read-only.

Commit

Permalink
Merge pull request #330 from dgrijalva/dg/xerrors
Browse files Browse the repository at this point in the history
[WIP] Support for xerrors / go2 errors
  • Loading branch information
dgrijalva committed Jan 7, 2020
2 parents fd39db2 + d9519f1 commit 4145594
Show file tree
Hide file tree
Showing 15 changed files with 291 additions and 210 deletions.
18 changes: 5 additions & 13 deletions claims.go
Expand Up @@ -28,34 +28,26 @@ type StandardClaims struct {
// Validates "aud" if present in claims. (see: WithAudience, WithoutAudienceValidation)
// Validates "iss" if option is provided (see: WithIssuer)
func (c StandardClaims) Valid(h *ValidationHelper) error {
vErr := new(ValidationError)
var vErr error

if h == nil {
h = DefaultValidationHelper
}

if err := h.ValidateExpiresAt(c.ExpiresAt); err != nil {
vErr.Inner = err
vErr.Errors |= ValidationErrorExpired
vErr = wrapError(err, vErr)
}

if err := h.ValidateNotBefore(c.NotBefore); err != nil {
vErr.Inner = err
vErr.Errors |= ValidationErrorNotValidYet
vErr = wrapError(err, vErr)
}

if err := h.ValidateAudience(c.Audience); err != nil {
vErr.Inner = err
vErr.Errors |= ValidationErrorAudience
vErr = wrapError(err, vErr)
}

if err := h.ValidateIssuer(c.Issuer); err != nil {
vErr.Inner = err
vErr.Errors |= ValidationErrorIssuer
}

if vErr.valid() {
return nil
vErr = wrapError(err, vErr)
}

return vErr
Expand Down
26 changes: 9 additions & 17 deletions claims_test.go
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/dgrijalva/jwt-go/v4"
"github.com/dgrijalva/jwt-go/v4/test"
"golang.org/x/xerrors"
)

const (
Expand Down Expand Up @@ -49,25 +50,16 @@ func TestClaimValidExpired(t *testing.T) {
if err == nil {
t.Errorf("[%v] Expecting error. Didn't get one.", name)
} else {
ve := err.(*jwt.ValidationError)
// compare the bitfield part of the error
if e := ve.Errors; e != jwt.ValidationErrorExpired {
t.Errorf("[%v] Errors don't match expectation. %v != %v", name, e, jwt.ValidationErrorExpired)
var expErr *jwt.TokenExpiredError

if !xerrors.As(err, &expErr) {
t.Errorf("[%v] Expected error to unwrap as *jwt.TokenExpiredError but it didn't", name)
return
}
switch vi := ve.Inner.(type) {
default:
expectedErrorStr := "token is expired by 1m40s"
if fmt.Sprint(ve.Inner.Error()) != expectedErrorStr {
t.Errorf("[%v] Errors inner text is not as expected. \"%v\" is not \"%v\"", name, ve.Inner, expectedErrorStr)
}
case *jwt.ExpiredError:
if vi.ExpiredBy != 100*time.Second {
t.Errorf("[%v] ExpiredError.ExpiredBy %v is not %v\n", name, vi.ExpiredBy, 100*time.Second)
}
if vi.Error() != "Token is expired" {
t.Errorf("[%v] Error message is not as expected \"%v\"\n", name, vi.Error())
}

expectedErrorStr := "token is expired by 1m40s"
if expErr.Error() != expectedErrorStr {
t.Errorf("[%v] Error message is not as expected \"%v\" != \"%v\"", name, expErr.Error(), expectedErrorStr)
}
}
})
Expand Down
23 changes: 9 additions & 14 deletions ecdsa.go
Expand Up @@ -5,15 +5,10 @@ import (
"crypto/ecdsa"
"crypto/rand"
"encoding/asn1"
"errors"
"fmt"
"math/big"
)

// Errors returned by ecdsa signing method
var (
ErrECDSAVerification = errors.New("crypto/ecdsa: verification error")
)

// SigningMethodECDSA implements the ECDSA family of signing methods signing methods
// Expects *ecdsa.PrivateKey for signing and *ecdsa.PublicKey for verification
type SigningMethodECDSA struct {
Expand Down Expand Up @@ -81,14 +76,14 @@ func (m *SigningMethodECDSA) Verify(signingString, signature string, key interfa
case crypto.Signer:
pub := k.Public()
if ecdsaKey, ok = pub.(*ecdsa.PublicKey); !ok {
return ErrInvalidKeyType
return &InvalidKeyError{Message: fmt.Sprintf("crypto.Signer returned an unexpected public key type: %T", pub)}
}
default:
return ErrInvalidKeyType
return NewInvalidKeyTypeError("*ecdsa.PublicKey or crypto.Signer", key)
}

if len(sig) != 2*m.KeySize {
return ErrECDSAVerification
return &UnverfiableTokenError{Message: "signature length is invalid"}
}

r := big.NewInt(0).SetBytes(sig[:m.KeySize])
Expand All @@ -105,7 +100,7 @@ func (m *SigningMethodECDSA) Verify(signingString, signature string, key interfa
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true {
return nil
}
return ErrECDSAVerification
return new(InvalidSignatureError)
}

// Sign implements the Sign method from SigningMethod
Expand All @@ -116,12 +111,12 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string
var ok bool

if signer, ok = key.(crypto.Signer); !ok {
return "", ErrInvalidKey
return "", NewInvalidKeyTypeError("*ecdsa.PrivateKey or crypto.Signer", key)
}

//sanity check that the signer is an ecdsa signer
if pub, ok = signer.Public().(*ecdsa.PublicKey); !ok {
return "", ErrInvalidKeyType
return "", &InvalidKeyError{Message: fmt.Sprintf("signer returned unexpected public key type: %T", pub)}
}

// Create the hasher
Expand All @@ -147,13 +142,13 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string
}

if len(rest) != 0 {
return "", ErrECDSASignatureUnmarshal
return "", &UnverfiableTokenError{Message: "unexpected extra bytes in ecda signature"}
}

curveBits := pub.Curve.Params().BitSize

if m.CurveBits != curveBits {
return "", ErrInvalidKey
return "", &InvalidKeyError{Message: "CurveBits in public key don't match those in signing method"}
}

keyBytes := curveBits / 8
Expand Down
211 changes: 164 additions & 47 deletions errors.go
@@ -1,72 +1,189 @@
package jwt

import (
"errors"
"fmt"
"time"
)

// Error constants
var (
ErrInvalidKey = errors.New("key is invalid")
ErrInvalidKeyType = errors.New("key is of invalid type")
ErrHashUnavailable = errors.New("the requested hash function is unavailable")
ErrECDSASignatureUnmarshal = errors.New("unexpected extra bytes in ecda signature")
ErrHashUnavailable = new(HashUnavailableError)
)

// The errors that might occur when parsing and validating a token
const (
ValidationErrorMalformed uint32 = 1 << iota // Token is malformed
ValidationErrorUnverifiable // Token could not be verified because of signing problems
ValidationErrorSignatureInvalid // Signature validation failed

// Standard Claim validation errors
ValidationErrorAudience // AUD validation failed
ValidationErrorExpired // EXP validation failed
ValidationErrorIssuedAt // IAT validation failed
ValidationErrorIssuer // ISS validation failed
ValidationErrorNotValidYet // NBF validation failed
ValidationErrorID // JTI validation failed
ValidationErrorClaimsInvalid // Generic claims validation error
)
// Embeds b within a, if a is a valid wrapper. returns a
// If a is not a valid wrapper, b is dropped
// If one of the errors is nil, the other is returned
func wrapError(a, b error) error {
if b == nil {
return a
}
if a == nil {
return b
}

type iErrorWrapper interface {
Wrap(error)
}
if w, ok := a.(iErrorWrapper); ok {
w.Wrap(b)
}
return a
}

// ErrorWrapper provides a simple, concrete helper for implementing nestable errors
type ErrorWrapper struct{ err error }

// Unwrap implements xerrors.Wrapper
func (w ErrorWrapper) Unwrap() error {
return w.err
}

// Wrap stores the provided error value and returns it when Unwrap is called
func (w ErrorWrapper) Wrap(err error) {
w.err = err
}

// InvalidKeyError is returned if the key is unusable for some reason other than type
type InvalidKeyError struct {
Message string
ErrorWrapper
}

// NewValidationError is a helper for constructing a ValidationError with a string error message
func NewValidationError(errorText string, errorFlags uint32) *ValidationError {
return &ValidationError{
text: errorText,
Errors: errorFlags,
func (e *InvalidKeyError) Error() string {
return fmt.Sprintf("key is invalid: %v", e.Message)
}

// InvalidKeyTypeError is returned if the key is unusable because it is of an incompatible type
type InvalidKeyTypeError struct {
Expected, Received string // String descriptions of expected and received types
ErrorWrapper
}

func (e *InvalidKeyTypeError) Error() string {
if e.Expected == "" && e.Received == "" {
return "key is of invalid type"
}
return fmt.Sprintf("key is of invalid type: expected %v, received %v", e.Expected, e.Received)
}

// NewInvalidKeyTypeError creates an InvalidKeyTypeError, automatically capturing the type
// of received
func NewInvalidKeyTypeError(expected string, received interface{}) error {
return &InvalidKeyTypeError{Expected: expected, Received: fmt.Sprintf("%T", received)}
}

type MalformedTokenError struct {
Message string
ErrorWrapper
}

func (e *MalformedTokenError) Error() string {
if e.Message == "" {
return "token is malformed"
}
return fmt.Sprintf("token is malformed: %v", e.Message)
}

// ValidationError is the error from Parse if token is not valid
type ValidationError struct {
Inner error // stores the error returned by external dependencies, i.e.: KeyFunc
Errors uint32 // bitfield. see ValidationError... constants
text string // errors that do not have a valid error just have text
type UnverfiableTokenError struct {
Message string
ErrorWrapper
}

// Validation error is an error type
func (e ValidationError) Error() string {
if e.Inner != nil {
return e.Inner.Error()
} else if e.text != "" {
return e.text
} else {
return "token is invalid"
func (e *UnverfiableTokenError) Error() string {
if e.Message == "" {
return "token is unverifiable"
}
return fmt.Sprintf("token is unverifiable: %v", e.Message)
}

// No errors
func (e *ValidationError) valid() bool {
return e.Errors == 0
type InvalidSignatureError struct {
Message string
ErrorWrapper
}

func (e *InvalidSignatureError) Error() string {
if e.Message == "" {
return "token signature is invalid"
}
return fmt.Sprintf("token signature is invalid: %v", e.Message)
}

// ExpiredError allows the caller to know the delta between now and the expired time and the unvalidated claims.
// TokenExpiredError allows the caller to know the delta between now and the expired time and the unvalidated claims.
// A client system may have a bug that doesn't refresh a token in time, or there may be clock skew so this information can help you understand.
type ExpiredError struct {
Now int64
ExpiredBy time.Duration
type TokenExpiredError struct {
At time.Time // The time at which the exp was evaluated. Includes leeway.
ExpiredBy time.Duration // How long the token had been expired at time of evaluation
ErrorWrapper // Value for unwrapping
}

func (e *TokenExpiredError) Error() string {
return fmt.Sprintf("token is expired by %v", e.ExpiredBy)
}

type TokenNotValidYetError struct {
At time.Time // The time at which the exp was evaluated. Includes leeway.
EarlyBy time.Duration // How long the token had been expired at time of evaluation
ErrorWrapper // Value for unwrapping
}

func (e *TokenNotValidYetError) Error() string {
return fmt.Sprintf("token is not valid yet; wait %v", e.EarlyBy)
}

type InvalidAudienceError struct {
Message string
ErrorWrapper
}

func (e *InvalidAudienceError) Error() string {
if e.Message == "" {
return "token audience is invalid"
}
return fmt.Sprintf("token audience is invalid: %v", e.Message)
}

type InvalidIssuerError struct {
Message string
ErrorWrapper
}

func (e *InvalidIssuerError) Error() string {
if e.Message == "" {
return "token issuer is invalid"
}
return fmt.Sprintf("token issuer is invalid: %v", e.Message)
}

// InvalidClaimsError is a catchall type for claims errors that don't have their own type
type InvalidClaimsError struct {
Message string
ErrorWrapper
}

func (e *InvalidClaimsError) Error() string {
if e.Message == "" {
return "token claim is invalid"
}
return fmt.Sprintf("token claim is invalid: %v", e.Message)
}

// SigningError is a catchall type for signing errors
type SigningError struct {
Message string
ErrorWrapper
}

func (e *SigningError) Error() string {
if e.Message == "" {
return "error encountered during signing"
}
return fmt.Sprintf("error encountered during signing: %v", e.Message)
}

type HashUnavailableError struct {
ErrorWrapper
}

func (e *ExpiredError) Error() string {
return "Token is expired"
func (e *HashUnavailableError) Error() string {
return "the requested hash function is unavailable"
}

0 comments on commit 4145594

Please sign in to comment.