diff --git a/parser.go b/parser.go index 4f61b7c7..00d360a7 100644 --- a/parser.go +++ b/parser.go @@ -5,6 +5,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "reflect" "strings" ) @@ -74,14 +75,38 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf } } - // Lookup key - var key interface{} + // Lookup key(s) if keyFunc == nil { // keyFunc was not provided. short circuiting validation return token, newError("no keyfunc was provided", ErrTokenUnverifiable) } - if key, err = keyFunc(token); err != nil { - return token, newError("error while executing keyfunc", ErrTokenUnverifiable, err) + + keys := make([]interface{}, 1) + // Convert the key or list of keys into a list of keys. + { + got, err := keyFunc(token) + if err != nil { + return token, newError("error while executing keyfunc", ErrTokenUnverifiable, err) + } + + switch have := got.(type) { + case []interface{}: + keys = have + case []byte, []int8: // HMAC is an outlier, so treat it specially. + keys[0] = have + case interface{}: + typ := reflect.TypeOf(have) + switch typ.Kind() { + case reflect.Array, reflect.Slice: + val := reflect.ValueOf(have) + keys = make([]interface{}, val.Len()) + for i := 0; i < val.Len(); i++ { + keys[i] = val.Index(i).Interface() + } + default: + keys[0] = have + } + } } // Decode signature @@ -90,8 +115,20 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf return token, newError("could not base64 decode signature", ErrTokenMalformed, err) } - // Perform signature validation - if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { + text := strings.Join(parts[0:2], ".") + + // Assume there is an error until proven otherwise because an empty array of + // keys means no checks are performed. + err = ErrTokenSignatureInvalid + for _, key := range keys { + // Perform signature validation, skipping the rest when a match is found. + err = token.Method.Verify(text, token.Signature, key) + if err == nil { + break + } + } + // If the only key or last key checked failed, then it's an error. + if err != nil { return token, newError("", ErrTokenSignatureInvalid, err) } diff --git a/parser_test.go b/parser_test.go index a615186a..83e4b3f5 100644 --- a/parser_test.go +++ b/parser_test.go @@ -28,6 +28,17 @@ var ( emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil } errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, errKeyFuncError } nilKeyFunc jwt.Keyfunc = nil + multipleZeroKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return []interface{}{}, nil } + multipleEmptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return []interface{}{nil, nil}, nil } + multipleLastKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { + return []interface{}{jwtTestEC256PublicKey, jwtTestDefaultKey}, nil + } + multipleFirstKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { + return []interface{}{jwtTestDefaultKey, jwtTestEC256PublicKey}, nil + } + multipleAltTypedKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { + return []*rsa.PublicKey{jwtTestDefaultKey, jwtTestDefaultKey}, nil + } ) func init() { @@ -94,6 +105,36 @@ var jwtTestData = []struct { nil, jwt.SigningMethodRS256, }, + { + "multiple keys, last matches", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + multipleLastKeyFunc, + jwt.MapClaims{"foo": "bar"}, + true, + nil, + nil, + jwt.SigningMethodRS256, + }, + { + "multiple keys not []interface{} type, all match", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + multipleAltTypedKeyFunc, + jwt.MapClaims{"foo": "bar"}, + true, + nil, + nil, + jwt.SigningMethodRS256, + }, + { + "multiple keys, first matches", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + multipleFirstKeyFunc, + jwt.MapClaims{"foo": "bar"}, + true, + nil, + nil, + jwt.SigningMethodRS256, + }, { "basic expired", "", // autogen @@ -154,6 +195,26 @@ var jwtTestData = []struct { nil, jwt.SigningMethodRS256, }, + { + "multiple nokey", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + multipleEmptyKeyFunc, + jwt.MapClaims{"foo": "bar"}, + false, + []error{jwt.ErrTokenSignatureInvalid}, + nil, + jwt.SigningMethodRS256, + }, + { + "zero length key list", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", + multipleZeroKeyFunc, + jwt.MapClaims{"foo": "bar"}, + false, + []error{jwt.ErrTokenSignatureInvalid}, + nil, + jwt.SigningMethodRS256, + }, { "basic errorkey", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", diff --git a/token.go b/token.go index c8ad7c78..bf3e4e8b 100644 --- a/token.go +++ b/token.go @@ -9,6 +9,8 @@ import ( // the key for verification. The function receives the parsed, but unverified // Token. This allows you to use properties in the Header of the token (such as // `kid`) to identify which key to use. +// The returned interface{} may be a single key or an array of keys to try. If +// an array of keys is returned an []interface{} with mixed types is allowed. type Keyfunc func(*Token) (interface{}, error) // Token represents a JWT Token. Different fields will be used depending on