2014-12-28 23:24:41 +03:00
|
|
|
package jwt_test
|
2012-04-18 03:49:21 +04:00
|
|
|
|
|
|
|
import (
|
2021-09-24 22:32:29 +03:00
|
|
|
"crypto"
|
2015-07-20 20:23:11 +03:00
|
|
|
"crypto/rsa"
|
2022-01-20 00:55:19 +03:00
|
|
|
"errors"
|
2012-04-18 23:35:16 +04:00
|
|
|
"fmt"
|
2012-04-18 23:59:37 +04:00
|
|
|
"reflect"
|
|
|
|
"testing"
|
2014-03-08 02:43:11 +04:00
|
|
|
"time"
|
2015-07-17 20:28:08 +03:00
|
|
|
|
2022-08-27 12:36:37 +03:00
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
|
|
"github.com/golang-jwt/jwt/v5/test"
|
2012-04-18 03:49:21 +04:00
|
|
|
)
|
|
|
|
|
2021-08-03 16:51:01 +03:00
|
|
|
var errKeyFuncError error = fmt.Errorf("error loading key")
|
2015-12-31 18:48:39 +03:00
|
|
|
|
2014-10-12 00:54:16 +04:00
|
|
|
var (
|
2021-09-24 22:32:29 +03:00
|
|
|
jwtTestDefaultKey *rsa.PublicKey
|
|
|
|
jwtTestRSAPrivateKey *rsa.PrivateKey
|
|
|
|
jwtTestEC256PublicKey crypto.PublicKey
|
|
|
|
jwtTestEC256PrivateKey crypto.PrivateKey
|
2021-11-06 14:21:20 +03:00
|
|
|
paddedKey crypto.PublicKey
|
2021-09-24 22:32:29 +03:00
|
|
|
defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil }
|
|
|
|
ecdsaKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestEC256PublicKey, nil }
|
2021-11-06 14:21:20 +03:00
|
|
|
paddedKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return paddedKey, nil }
|
2021-09-24 22:32:29 +03:00
|
|
|
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
|
2014-10-12 00:54:16 +04:00
|
|
|
)
|
|
|
|
|
2016-04-08 23:01:55 +03:00
|
|
|
func init() {
|
2021-09-24 22:32:29 +03:00
|
|
|
// Load public keys
|
2016-04-08 23:01:55 +03:00
|
|
|
jwtTestDefaultKey = test.LoadRSAPublicKeyFromDisk("test/sample_key.pub")
|
2021-09-24 22:32:29 +03:00
|
|
|
jwtTestEC256PublicKey = test.LoadECPublicKeyFromDisk("test/ec256-public.pem")
|
|
|
|
|
2021-11-06 14:21:20 +03:00
|
|
|
// Load padded public key - note there is only a public key for this key pair and should only be used for the
|
|
|
|
// two test cases below.
|
|
|
|
paddedKey = test.LoadECPublicKeyFromDisk("test/examplePaddedKey-public.pem")
|
|
|
|
|
2021-09-24 22:32:29 +03:00
|
|
|
// Load private keys
|
|
|
|
jwtTestRSAPrivateKey = test.LoadRSAPrivateKeyFromDisk("test/sample_key")
|
|
|
|
jwtTestEC256PrivateKey = test.LoadECPrivateKeyFromDisk("test/ec256-private.pem")
|
2021-11-06 14:21:20 +03:00
|
|
|
|
2016-04-08 23:01:55 +03:00
|
|
|
}
|
|
|
|
|
2012-04-18 23:59:37 +04:00
|
|
|
var jwtTestData = []struct {
|
2021-09-24 22:32:29 +03:00
|
|
|
name string
|
|
|
|
tokenString string
|
|
|
|
keyfunc jwt.Keyfunc
|
|
|
|
claims jwt.Claims
|
|
|
|
valid bool
|
|
|
|
errors uint32
|
2022-01-20 00:55:19 +03:00
|
|
|
err []error
|
2021-09-24 22:32:29 +03:00
|
|
|
parser *jwt.Parser
|
|
|
|
signingMethod jwt.SigningMethod // The method to sign the JWT token for test purpose
|
2012-04-18 23:18:31 +04:00
|
|
|
}{
|
New Validation API
Some guidelines in designing the new validation API
* Previously, the `Valid` method was placed on the claim, which was always not entirely semantically correct, since the validity is concerning the token, not the claims. Although the validity of the token is based on the processing of the claims (such as `exp`). Therefore, the function `Valid` was removed from the `Claims` interface and the single canonical way to retrieve the validity of the token is to retrieve the `Valid` property of the `Token` struct.
* The previous fact was enhanced by the fact that most claims implementations had additional exported `VerifyXXX` functions, which are now removed
* All validation errors should be comparable with `errors.Is` to determine, why a particular validation has failed
* Developers want to adjust validation options. Popular options include:
* Leeway when processing exp, nbf, iat
* Not verifying `iat`, since this is actually just an informational claim. When purely looking at the standard, this should probably the default
* Verifying `aud` by default, which actually the standard sort of demands. We need to see how strong we want to enforce this
* Developers want to create their own claim types, mostly by embedding one of the existing types such as `RegisteredClaims`.
* Sometimes there is the need to further tweak the validation of a token by checking the value of a custom claim. Previously, this was possibly by overriding `Valid`. However, this was error-prone, e.g., if the original `Valid` was not called. Therefore, we should provide an easy way for *additional* checks, without by-passing the necessary validations
This leads to the following two major changes:
* The `Claims` interface now represents a set of functions that return the mandatory claims represented in a token, rather than just a `Valid` function. This is also more semantically correct.
* All validation tasks are offloaded to a new (optional) `Validator`, which can also be configured with appropriate options. If no custom validator was supplied, a default one is used.
2022-08-27 13:07:09 +03:00
|
|
|
/*{
|
2012-04-18 23:35:16 +04:00
|
|
|
"basic",
|
2012-04-18 23:18:31 +04:00
|
|
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
2014-10-12 00:54:16 +04:00
|
|
|
defaultKeyFunc,
|
2015-08-18 20:18:57 +03:00
|
|
|
jwt.MapClaims{"foo": "bar"},
|
2012-04-18 23:18:31 +04:00
|
|
|
true,
|
2014-12-28 23:24:41 +03:00
|
|
|
0,
|
2015-11-03 02:22:08 +03:00
|
|
|
nil,
|
2022-01-20 00:55:19 +03:00
|
|
|
nil,
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2014-03-08 02:43:11 +04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"basic expired",
|
|
|
|
"", // autogen
|
2014-10-12 00:54:16 +04:00
|
|
|
defaultKeyFunc,
|
2015-08-18 20:18:57 +03:00
|
|
|
jwt.MapClaims{"foo": "bar", "exp": float64(time.Now().Unix() - 100)},
|
2014-03-08 02:43:11 +04:00
|
|
|
false,
|
2014-12-28 23:24:41 +03:00
|
|
|
jwt.ValidationErrorExpired,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenExpired},
|
2015-11-03 02:22:08 +03:00
|
|
|
nil,
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2014-03-08 02:43:11 +04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"basic nbf",
|
|
|
|
"", // autogen
|
2014-10-12 00:54:16 +04:00
|
|
|
defaultKeyFunc,
|
2015-08-18 20:18:57 +03:00
|
|
|
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)},
|
2014-03-08 02:43:11 +04:00
|
|
|
false,
|
2014-12-28 23:24:41 +03:00
|
|
|
jwt.ValidationErrorNotValidYet,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenNotValidYet},
|
2015-11-03 02:22:08 +03:00
|
|
|
nil,
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2014-03-09 23:24:51 +04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"expired and nbf",
|
|
|
|
"", // autogen
|
2014-10-12 00:54:16 +04:00
|
|
|
defaultKeyFunc,
|
2015-08-18 20:18:57 +03:00
|
|
|
jwt.MapClaims{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)},
|
2014-03-09 23:24:51 +04:00
|
|
|
false,
|
2014-12-28 23:24:41 +03:00
|
|
|
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenNotValidYet},
|
2015-11-03 02:22:08 +03:00
|
|
|
nil,
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2012-04-18 23:18:31 +04:00
|
|
|
},
|
|
|
|
{
|
2012-04-18 23:35:16 +04:00
|
|
|
"basic invalid",
|
2012-04-18 23:18:31 +04:00
|
|
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
2014-10-12 00:54:16 +04:00
|
|
|
defaultKeyFunc,
|
2015-08-18 20:18:57 +03:00
|
|
|
jwt.MapClaims{"foo": "bar"},
|
2014-10-12 00:54:16 +04:00
|
|
|
false,
|
2014-12-28 23:24:41 +03:00
|
|
|
jwt.ValidationErrorSignatureInvalid,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenSignatureInvalid, rsa.ErrVerification},
|
2015-11-03 02:22:08 +03:00
|
|
|
nil,
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2014-10-12 00:54:16 +04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"basic nokeyfunc",
|
|
|
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
|
|
|
nilKeyFunc,
|
2015-08-18 20:18:57 +03:00
|
|
|
jwt.MapClaims{"foo": "bar"},
|
2014-10-12 00:54:16 +04:00
|
|
|
false,
|
2014-12-28 23:24:41 +03:00
|
|
|
jwt.ValidationErrorUnverifiable,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenUnverifiable},
|
2015-11-03 02:22:08 +03:00
|
|
|
nil,
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2014-10-12 00:54:16 +04:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"basic nokey",
|
|
|
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
|
|
|
emptyKeyFunc,
|
2015-08-18 20:18:57 +03:00
|
|
|
jwt.MapClaims{"foo": "bar"},
|
2012-04-18 23:18:31 +04:00
|
|
|
false,
|
2014-12-28 23:24:41 +03:00
|
|
|
jwt.ValidationErrorSignatureInvalid,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenSignatureInvalid},
|
2015-11-03 02:22:08 +03:00
|
|
|
nil,
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2012-04-18 23:18:31 +04:00
|
|
|
},
|
2014-10-12 00:54:16 +04:00
|
|
|
{
|
|
|
|
"basic errorkey",
|
|
|
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
|
|
|
|
errorKeyFunc,
|
2015-08-18 20:18:57 +03:00
|
|
|
jwt.MapClaims{"foo": "bar"},
|
2014-10-12 00:54:16 +04:00
|
|
|
false,
|
2014-12-28 23:24:41 +03:00
|
|
|
jwt.ValidationErrorUnverifiable,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenUnverifiable, errKeyFuncError},
|
2015-11-03 02:22:08 +03:00
|
|
|
nil,
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2015-11-03 02:22:08 +03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"invalid signing method",
|
|
|
|
"",
|
|
|
|
defaultKeyFunc,
|
2016-04-13 00:32:24 +03:00
|
|
|
jwt.MapClaims{"foo": "bar"},
|
2015-11-03 02:22:08 +03:00
|
|
|
false,
|
|
|
|
jwt.ValidationErrorSignatureInvalid,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenSignatureInvalid},
|
2015-11-03 02:22:08 +03:00
|
|
|
&jwt.Parser{ValidMethods: []string{"HS256"}},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2015-11-03 02:22:08 +03:00
|
|
|
},
|
2015-11-03 02:24:32 +03:00
|
|
|
{
|
2021-09-24 22:32:29 +03:00
|
|
|
"valid RSA signing method",
|
2015-11-03 02:24:32 +03:00
|
|
|
"",
|
|
|
|
defaultKeyFunc,
|
2016-04-13 00:32:24 +03:00
|
|
|
jwt.MapClaims{"foo": "bar"},
|
2015-11-03 02:24:32 +03:00
|
|
|
true,
|
|
|
|
0,
|
2022-01-20 00:55:19 +03:00
|
|
|
nil,
|
2015-11-03 02:24:32 +03:00
|
|
|
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"ECDSA signing method not accepted",
|
|
|
|
"",
|
|
|
|
ecdsaKeyFunc,
|
|
|
|
jwt.MapClaims{"foo": "bar"},
|
|
|
|
false,
|
|
|
|
jwt.ValidationErrorSignatureInvalid,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenSignatureInvalid},
|
2021-09-24 22:32:29 +03:00
|
|
|
&jwt.Parser{ValidMethods: []string{"RS256", "HS256"}},
|
|
|
|
jwt.SigningMethodES256,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"valid ECDSA signing method",
|
|
|
|
"",
|
|
|
|
ecdsaKeyFunc,
|
|
|
|
jwt.MapClaims{"foo": "bar"},
|
|
|
|
true,
|
|
|
|
0,
|
2022-01-20 00:55:19 +03:00
|
|
|
nil,
|
2021-09-24 22:32:29 +03:00
|
|
|
&jwt.Parser{ValidMethods: []string{"HS256", "ES256"}},
|
|
|
|
jwt.SigningMethodES256,
|
2015-11-03 02:24:32 +03:00
|
|
|
},
|
2015-11-03 02:22:08 +03:00
|
|
|
{
|
|
|
|
"JSON Number",
|
|
|
|
"",
|
|
|
|
defaultKeyFunc,
|
2016-04-13 00:32:24 +03:00
|
|
|
jwt.MapClaims{"foo": json.Number("123.4")},
|
|
|
|
true,
|
|
|
|
0,
|
2022-01-20 00:55:19 +03:00
|
|
|
nil,
|
2016-04-13 00:32:24 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2016-04-13 00:32:24 +03:00
|
|
|
},
|
2016-04-05 00:42:10 +03:00
|
|
|
{
|
|
|
|
"JSON Number - basic expired",
|
|
|
|
"", // autogen
|
|
|
|
defaultKeyFunc,
|
2016-04-13 02:19:20 +03:00
|
|
|
jwt.MapClaims{"foo": "bar", "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
|
2016-04-05 00:42:10 +03:00
|
|
|
false,
|
|
|
|
jwt.ValidationErrorExpired,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenExpired},
|
2016-04-05 00:42:10 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2016-04-05 00:42:10 +03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"JSON Number - basic nbf",
|
|
|
|
"", // autogen
|
|
|
|
defaultKeyFunc,
|
2016-04-13 02:19:20 +03:00
|
|
|
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
|
2016-04-05 00:42:10 +03:00
|
|
|
false,
|
|
|
|
jwt.ValidationErrorNotValidYet,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenNotValidYet},
|
2016-04-05 00:42:10 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2016-04-05 00:42:10 +03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"JSON Number - expired and nbf",
|
|
|
|
"", // autogen
|
|
|
|
defaultKeyFunc,
|
2016-04-13 02:19:20 +03:00
|
|
|
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100)), "exp": json.Number(fmt.Sprintf("%v", time.Now().Unix()-100))},
|
2016-04-05 00:42:10 +03:00
|
|
|
false,
|
|
|
|
jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenNotValidYet},
|
2016-04-05 00:42:10 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2016-04-05 00:42:10 +03:00
|
|
|
},
|
2016-06-21 23:11:54 +03:00
|
|
|
{
|
|
|
|
"SkipClaimsValidation during token parsing",
|
|
|
|
"", // autogen
|
|
|
|
defaultKeyFunc,
|
|
|
|
jwt.MapClaims{"foo": "bar", "nbf": json.Number(fmt.Sprintf("%v", time.Now().Unix()+100))},
|
|
|
|
true,
|
|
|
|
0,
|
2022-01-20 00:55:19 +03:00
|
|
|
nil,
|
2016-06-21 23:11:54 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true, SkipClaimsValidation: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2016-06-21 23:11:54 +03:00
|
|
|
},
|
2021-08-22 20:23:13 +03:00
|
|
|
{
|
|
|
|
"RFC7519 Claims",
|
|
|
|
"",
|
|
|
|
defaultKeyFunc,
|
|
|
|
&jwt.RegisteredClaims{
|
|
|
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * 10)),
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
0,
|
2022-01-20 00:55:19 +03:00
|
|
|
nil,
|
2021-08-22 20:23:13 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2021-08-22 20:23:13 +03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"RFC7519 Claims - single aud",
|
|
|
|
"",
|
|
|
|
defaultKeyFunc,
|
|
|
|
&jwt.RegisteredClaims{
|
|
|
|
Audience: jwt.ClaimStrings{"test"},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
0,
|
2022-01-20 00:55:19 +03:00
|
|
|
nil,
|
2021-08-22 20:23:13 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2021-08-22 20:23:13 +03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"RFC7519 Claims - multiple aud",
|
|
|
|
"",
|
|
|
|
defaultKeyFunc,
|
|
|
|
&jwt.RegisteredClaims{
|
|
|
|
Audience: jwt.ClaimStrings{"test", "test"},
|
|
|
|
},
|
|
|
|
true,
|
|
|
|
0,
|
2022-01-20 00:55:19 +03:00
|
|
|
nil,
|
2021-08-22 20:23:13 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2021-08-22 20:23:13 +03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"RFC7519 Claims - single aud with wrong type",
|
|
|
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOjF9.8mAIDUfZNQT3TGm1QFIQp91OCpJpQpbB1-m9pA2mkHc", // { "aud": 1 }
|
|
|
|
defaultKeyFunc,
|
|
|
|
&jwt.RegisteredClaims{
|
|
|
|
Audience: nil, // because of the unmarshal error, this will be empty
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
jwt.ValidationErrorMalformed,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenMalformed},
|
2021-08-22 20:23:13 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2021-08-22 20:23:13 +03:00
|
|
|
},
|
|
|
|
{
|
|
|
|
"RFC7519 Claims - multiple aud with wrong types",
|
|
|
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdCIsMV19.htEBUf7BVbfSmVoTFjXf3y6DLmDUuLy1vTJ14_EX7Ws", // { "aud": ["test", 1] }
|
|
|
|
defaultKeyFunc,
|
|
|
|
&jwt.RegisteredClaims{
|
|
|
|
Audience: nil, // because of the unmarshal error, this will be empty
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
jwt.ValidationErrorMalformed,
|
2022-01-20 00:55:19 +03:00
|
|
|
[]error{jwt.ErrTokenMalformed},
|
2021-08-22 20:23:13 +03:00
|
|
|
&jwt.Parser{UseJSONNumber: true},
|
2021-09-24 22:32:29 +03:00
|
|
|
jwt.SigningMethodRS256,
|
2021-08-22 20:23:13 +03:00
|
|
|
},
|
New Validation API
Some guidelines in designing the new validation API
* Previously, the `Valid` method was placed on the claim, which was always not entirely semantically correct, since the validity is concerning the token, not the claims. Although the validity of the token is based on the processing of the claims (such as `exp`). Therefore, the function `Valid` was removed from the `Claims` interface and the single canonical way to retrieve the validity of the token is to retrieve the `Valid` property of the `Token` struct.
* The previous fact was enhanced by the fact that most claims implementations had additional exported `VerifyXXX` functions, which are now removed
* All validation errors should be comparable with `errors.Is` to determine, why a particular validation has failed
* Developers want to adjust validation options. Popular options include:
* Leeway when processing exp, nbf, iat
* Not verifying `iat`, since this is actually just an informational claim. When purely looking at the standard, this should probably the default
* Verifying `aud` by default, which actually the standard sort of demands. We need to see how strong we want to enforce this
* Developers want to create their own claim types, mostly by embedding one of the existing types such as `RegisteredClaims`.
* Sometimes there is the need to further tweak the validation of a token by checking the value of a custom claim. Previously, this was possibly by overriding `Valid`. However, this was error-prone, e.g., if the original `Valid` was not called. Therefore, we should provide an easy way for *additional* checks, without by-passing the necessary validations
This leads to the following two major changes:
* The `Claims` interface now represents a set of functions that return the mandatory claims represented in a token, rather than just a `Valid` function. This is also more semantically correct.
* All validation tasks are offloaded to a new (optional) `Validator`, which can also be configured with appropriate options. If no custom validator was supplied, a default one is used.
2022-08-27 13:07:09 +03:00
|
|
|
{
|
|
|
|
"RFC7519 Claims - nbf with 60s skew",
|
|
|
|
"", // autogen
|
|
|
|
defaultKeyFunc,
|
|
|
|
&jwt.RegisteredClaims{NotBefore: jwt.NewNumericDate(time.Now().Add(time.Second * 100))},
|
|
|
|
false,
|
|
|
|
jwt.ValidationErrorNotValidYet,
|
|
|
|
[]error{jwt.ErrTokenNotValidYet},
|
|
|
|
jwt.NewParser(jwt.WithValidator(jwt.NewValidator(jwt.WithLeeway(time.Minute)))),
|
|
|
|
jwt.SigningMethodRS256,
|
|
|
|
},*/
|
|
|
|
{
|
|
|
|
"RFC7519 Claims - nbf with 120s skew",
|
|
|
|
"", // autogen
|
|
|
|
defaultKeyFunc,
|
|
|
|
&jwt.RegisteredClaims{NotBefore: jwt.NewNumericDate(time.Now().Add(time.Second * 100))},
|
|
|
|
true,
|
|
|
|
0,
|
|
|
|
nil,
|
|
|
|
jwt.NewParser(jwt.WithValidator(jwt.NewValidator(jwt.WithLeeway(2 * time.Minute)))),
|
|
|
|
jwt.SigningMethodRS256,
|
|
|
|
},
|
2014-10-12 00:54:16 +04:00
|
|
|
}
|
|
|
|
|
2021-09-24 22:32:29 +03:00
|
|
|
// signToken creates and returns a signed JWT token using signingMethod.
|
|
|
|
func signToken(claims jwt.Claims, signingMethod jwt.SigningMethod) string {
|
|
|
|
var privateKey interface{}
|
|
|
|
switch signingMethod {
|
|
|
|
case jwt.SigningMethodRS256:
|
|
|
|
privateKey = jwtTestRSAPrivateKey
|
|
|
|
case jwt.SigningMethodES256:
|
|
|
|
privateKey = jwtTestEC256PrivateKey
|
|
|
|
default:
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return test.MakeSampleToken(claims, signingMethod, privateKey)
|
|
|
|
}
|
|
|
|
|
2015-11-03 02:22:08 +03:00
|
|
|
func TestParser_Parse(t *testing.T) {
|
2016-04-08 23:01:55 +03:00
|
|
|
|
2016-04-13 00:32:24 +03:00
|
|
|
// Iterate over test data set and run tests
|
2012-04-18 23:18:31 +04:00
|
|
|
for _, data := range jwtTestData {
|
2021-08-22 20:23:13 +03:00
|
|
|
t.Run(data.name, func(t *testing.T) {
|
2021-09-24 22:32:29 +03:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
// If the token string is blank, use helper function to generate string
|
|
|
|
if data.tokenString == "" {
|
2021-09-24 22:32:29 +03:00
|
|
|
data.tokenString = signToken(data.claims, data.signingMethod)
|
2021-08-22 20:23:13 +03:00
|
|
|
}
|
2015-11-03 02:22:08 +03:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
// Parse the token
|
|
|
|
var token *jwt.Token
|
2022-01-20 00:55:19 +03:00
|
|
|
var ve *jwt.ValidationError
|
2021-08-22 20:23:13 +03:00
|
|
|
var err error
|
|
|
|
var parser = data.parser
|
|
|
|
if parser == nil {
|
New Validation API
Some guidelines in designing the new validation API
* Previously, the `Valid` method was placed on the claim, which was always not entirely semantically correct, since the validity is concerning the token, not the claims. Although the validity of the token is based on the processing of the claims (such as `exp`). Therefore, the function `Valid` was removed from the `Claims` interface and the single canonical way to retrieve the validity of the token is to retrieve the `Valid` property of the `Token` struct.
* The previous fact was enhanced by the fact that most claims implementations had additional exported `VerifyXXX` functions, which are now removed
* All validation errors should be comparable with `errors.Is` to determine, why a particular validation has failed
* Developers want to adjust validation options. Popular options include:
* Leeway when processing exp, nbf, iat
* Not verifying `iat`, since this is actually just an informational claim. When purely looking at the standard, this should probably the default
* Verifying `aud` by default, which actually the standard sort of demands. We need to see how strong we want to enforce this
* Developers want to create their own claim types, mostly by embedding one of the existing types such as `RegisteredClaims`.
* Sometimes there is the need to further tweak the validation of a token by checking the value of a custom claim. Previously, this was possibly by overriding `Valid`. However, this was error-prone, e.g., if the original `Valid` was not called. Therefore, we should provide an easy way for *additional* checks, without by-passing the necessary validations
This leads to the following two major changes:
* The `Claims` interface now represents a set of functions that return the mandatory claims represented in a token, rather than just a `Valid` function. This is also more semantically correct.
* All validation tasks are offloaded to a new (optional) `Validator`, which can also be configured with appropriate options. If no custom validator was supplied, a default one is used.
2022-08-27 13:07:09 +03:00
|
|
|
parser = jwt.NewParser()
|
2021-08-22 20:23:13 +03:00
|
|
|
}
|
|
|
|
// Figure out correct claims type
|
|
|
|
switch data.claims.(type) {
|
|
|
|
case jwt.MapClaims:
|
|
|
|
token, err = parser.ParseWithClaims(data.tokenString, jwt.MapClaims{}, data.keyfunc)
|
|
|
|
case *jwt.RegisteredClaims:
|
|
|
|
token, err = parser.ParseWithClaims(data.tokenString, &jwt.RegisteredClaims{}, data.keyfunc)
|
|
|
|
}
|
2012-04-18 23:59:37 +04:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
// Verify result matches expectation
|
|
|
|
if !reflect.DeepEqual(data.claims, token.Claims) {
|
|
|
|
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
|
|
|
|
}
|
2015-07-17 20:28:08 +03:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
if data.valid && err != nil {
|
|
|
|
t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err)
|
|
|
|
}
|
2015-07-17 20:28:08 +03:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
if !data.valid && err == nil {
|
|
|
|
t.Errorf("[%v] Invalid token passed validation", data.name)
|
|
|
|
}
|
2015-07-17 20:28:08 +03:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
if (err == nil && !token.Valid) || (err != nil && token.Valid) {
|
|
|
|
t.Errorf("[%v] Inconsistent behavior between returned error and token.Valid", data.name)
|
|
|
|
}
|
2016-04-13 00:52:39 +03:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
if data.errors != 0 {
|
|
|
|
if err == nil {
|
2021-09-24 22:32:29 +03:00
|
|
|
t.Errorf("[%v] Expecting error. Didn't get one.", data.name)
|
2021-08-22 20:23:13 +03:00
|
|
|
} else {
|
2022-01-20 00:55:19 +03:00
|
|
|
if errors.As(err, &ve) {
|
|
|
|
// compare the bitfield part of the error
|
|
|
|
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() == errKeyFuncError.Error() && ve.Inner != errKeyFuncError {
|
|
|
|
t.Errorf("[%v] Inner error does not match expectation. %v != %v", data.name, ve.Inner, errKeyFuncError)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-12-31 18:48:39 +03:00
|
|
|
|
2022-01-20 00:55:19 +03:00
|
|
|
if data.err != nil {
|
|
|
|
if err == nil {
|
|
|
|
t.Errorf("[%v] Expecting error(s). Didn't get one.", data.name)
|
|
|
|
} else {
|
|
|
|
var all = false
|
|
|
|
for _, e := range data.err {
|
|
|
|
all = errors.Is(err, e)
|
2021-08-22 20:23:13 +03:00
|
|
|
}
|
2015-12-31 18:48:39 +03:00
|
|
|
|
2022-01-20 00:55:19 +03:00
|
|
|
if !all {
|
|
|
|
t.Errorf("[%v] Errors don't match expectation. %v should contain all of %v", data.name, err, data.err)
|
2021-08-22 20:23:13 +03:00
|
|
|
}
|
2015-12-31 18:48:39 +03:00
|
|
|
}
|
2014-03-08 02:43:11 +04:00
|
|
|
}
|
2022-01-20 00:55:19 +03:00
|
|
|
|
2021-09-24 22:32:29 +03:00
|
|
|
if data.valid {
|
|
|
|
if token.Signature == "" {
|
|
|
|
t.Errorf("[%v] Signature is left unpopulated after parsing", data.name)
|
|
|
|
}
|
|
|
|
if !token.Valid {
|
|
|
|
// The 'Valid' field should be set to true when invoking Parse()
|
|
|
|
t.Errorf("[%v] Token.Valid field mismatch. Expecting true, got %v", data.name, token.Valid)
|
|
|
|
}
|
2021-08-22 20:23:13 +03:00
|
|
|
}
|
|
|
|
})
|
2012-04-18 23:18:31 +04:00
|
|
|
}
|
2012-04-18 03:49:21 +04:00
|
|
|
}
|
2012-04-18 23:35:16 +04:00
|
|
|
|
2016-07-30 14:44:44 +03:00
|
|
|
func TestParser_ParseUnverified(t *testing.T) {
|
|
|
|
|
|
|
|
// Iterate over test data set and run tests
|
|
|
|
for _, data := range jwtTestData {
|
2021-08-22 20:23:13 +03:00
|
|
|
// Skip test data, that intentionally contains malformed tokens, as they would lead to an error
|
|
|
|
if data.errors&jwt.ValidationErrorMalformed != 0 {
|
|
|
|
continue
|
2016-07-30 14:44:44 +03:00
|
|
|
}
|
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
t.Run(data.name, func(t *testing.T) {
|
|
|
|
// If the token string is blank, use helper function to generate string
|
|
|
|
if data.tokenString == "" {
|
2021-09-24 22:32:29 +03:00
|
|
|
data.tokenString = signToken(data.claims, data.signingMethod)
|
2021-08-22 20:23:13 +03:00
|
|
|
}
|
2016-07-30 14:44:44 +03:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
// Parse the token
|
|
|
|
var token *jwt.Token
|
|
|
|
var err error
|
|
|
|
var parser = data.parser
|
|
|
|
if parser == nil {
|
|
|
|
parser = new(jwt.Parser)
|
|
|
|
}
|
|
|
|
// Figure out correct claims type
|
|
|
|
switch data.claims.(type) {
|
|
|
|
case jwt.MapClaims:
|
|
|
|
token, _, err = parser.ParseUnverified(data.tokenString, jwt.MapClaims{})
|
|
|
|
case *jwt.RegisteredClaims:
|
|
|
|
token, _, err = parser.ParseUnverified(data.tokenString, &jwt.RegisteredClaims{})
|
|
|
|
}
|
2016-07-30 14:44:44 +03:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("[%v] Invalid token", data.name)
|
|
|
|
}
|
2016-07-30 14:44:44 +03:00
|
|
|
|
2021-08-22 20:23:13 +03:00
|
|
|
// Verify result matches expectation
|
|
|
|
if !reflect.DeepEqual(data.claims, token.Claims) {
|
|
|
|
t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims)
|
|
|
|
}
|
|
|
|
|
|
|
|
if data.valid && err != nil {
|
|
|
|
t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err)
|
|
|
|
}
|
2021-09-24 22:32:29 +03:00
|
|
|
if token.Valid {
|
|
|
|
// The 'Valid' field should not be set to true when invoking ParseUnverified()
|
|
|
|
t.Errorf("[%v] Token.Valid field mismatch. Expecting false, got %v", data.name, token.Valid)
|
|
|
|
}
|
|
|
|
if token.Signature != "" {
|
|
|
|
// The signature was not validated, hence the 'Signature' field is not populated.
|
|
|
|
t.Errorf("[%v] Token.Signature field mismatch. Expecting '', got %v", data.name, token.Signature)
|
|
|
|
}
|
2021-08-22 20:23:13 +03:00
|
|
|
})
|
2016-07-30 14:44:44 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-06 14:21:20 +03:00
|
|
|
var setPaddingTestData = []struct {
|
|
|
|
name string
|
|
|
|
tokenString string
|
|
|
|
claims jwt.Claims
|
|
|
|
paddedDecode bool
|
|
|
|
signingMethod jwt.SigningMethod
|
|
|
|
keyfunc jwt.Keyfunc
|
|
|
|
valid bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Validated non-padded token with padding disabled",
|
|
|
|
tokenString: "",
|
|
|
|
claims: jwt.MapClaims{"foo": "paddedbar"},
|
|
|
|
paddedDecode: false,
|
|
|
|
signingMethod: jwt.SigningMethodRS256,
|
|
|
|
keyfunc: defaultKeyFunc,
|
|
|
|
valid: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Validated non-padded token with padding enabled",
|
|
|
|
tokenString: "",
|
|
|
|
claims: jwt.MapClaims{"foo": "paddedbar"},
|
|
|
|
paddedDecode: true,
|
|
|
|
signingMethod: jwt.SigningMethodRS256,
|
|
|
|
keyfunc: defaultKeyFunc,
|
|
|
|
valid: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Error for padded token with padding disabled",
|
|
|
|
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ==.20kGGJaYekGTRFf8b0TwhuETcR8lv5z2363X5jf7G1yTWVTwOmte5Ii8L8_OQbYwPoiVHmZY6iJPbt_DhCN42AeFY74BcsUhR-BVrYUVhKK0RppuzEcSlILDNeQsJDLEL035CPm1VO6Jrgk7enQPIctVxUesRgswP71OpGvJxy3j1k_J8p0WzZvRZTe1D_2Misa0UDGwnEIHhmr97fIpMSZjFxlcygQw8QN34IHLHIXMaTY1eiCf4CCr6rOS9wUeu7P3CPkmFq9XhxBT_LLCmIMhHnxP5x27FUJE_JZlfek0MmARcrhpsZS2sFhHAiWrjxjOE27jkDtv1nEwn65wMw==",
|
|
|
|
claims: jwt.MapClaims{"foo": "paddedbar"},
|
|
|
|
paddedDecode: false,
|
|
|
|
signingMethod: jwt.SigningMethodRS256,
|
|
|
|
keyfunc: defaultKeyFunc,
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Validated padded token with padding enabled",
|
|
|
|
tokenString: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJwYWRkZWRiYXIifQ==.20kGGJaYekGTRFf8b0TwhuETcR8lv5z2363X5jf7G1yTWVTwOmte5Ii8L8_OQbYwPoiVHmZY6iJPbt_DhCN42AeFY74BcsUhR-BVrYUVhKK0RppuzEcSlILDNeQsJDLEL035CPm1VO6Jrgk7enQPIctVxUesRgswP71OpGvJxy3j1k_J8p0WzZvRZTe1D_2Misa0UDGwnEIHhmr97fIpMSZjFxlcygQw8QN34IHLHIXMaTY1eiCf4CCr6rOS9wUeu7P3CPkmFq9XhxBT_LLCmIMhHnxP5x27FUJE_JZlfek0MmARcrhpsZS2sFhHAiWrjxjOE27jkDtv1nEwn65wMw==",
|
|
|
|
claims: jwt.MapClaims{"foo": "paddedbar"},
|
|
|
|
paddedDecode: true,
|
|
|
|
signingMethod: jwt.SigningMethodRS256,
|
|
|
|
keyfunc: defaultKeyFunc,
|
|
|
|
valid: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Error for example padded token with padding disabled",
|
|
|
|
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3grw==",
|
|
|
|
claims: nil,
|
|
|
|
paddedDecode: false,
|
|
|
|
signingMethod: jwt.SigningMethodES256,
|
|
|
|
keyfunc: paddedKeyFunc,
|
|
|
|
valid: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Validated example padded token with padding enabled",
|
|
|
|
tokenString: "eyJ0eXAiOiJKV1QiLCJraWQiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJhbGciOiJFUzI1NiIsImlzcyI6Imh0dHBzOi8vY29nbml0by1pZHAuZXUtd2VzdC0yLmFtYXpvbmF3cy5jb20vIiwiY2xpZW50IjoiN0xUY29QWnJWNDR6ZVg2WUs5VktBcHZPM3EiLCJzaWduZXIiOiJhcm46YXdzOmVsYXN0aWNsb2FkYmFsYW5jaW5nIiwiZXhwIjoxNjI5NDcwMTAxfQ==.eyJzdWIiOiIxMjM0NTY3OC1hYmNkLTEyMzQtYWJjZC0xMjM0NTY3OGFiY2QiLCJlbWFpbF92ZXJpZmllZCI6InRydWUiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJ1c2VybmFtZSI6IjEyMzQ1Njc4LWFiY2QtMTIzNC1hYmNkLTEyMzQ1Njc4YWJjZCIsImV4cCI6MTYyOTQ3MDEwMSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkcC5ldS13ZXN0LTIuYW1hem9uYXdzLmNvbS8ifQ==.sx0muJ754glJvwWgkHaPrOI3L1gaPjRLLUvOQRk0WitnqC5Dtt1knorcbOzlEcH9zwPM2jYYIAYQz_qEyM3grw==",
|
|
|
|
claims: nil,
|
|
|
|
paddedDecode: true,
|
|
|
|
signingMethod: jwt.SigningMethodES256,
|
|
|
|
keyfunc: paddedKeyFunc,
|
|
|
|
valid: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extension of Parsing, this is to test out functionality specific to switching codecs with padding.
|
|
|
|
func TestSetPadding(t *testing.T) {
|
|
|
|
for _, data := range setPaddingTestData {
|
|
|
|
t.Run(data.name, func(t *testing.T) {
|
|
|
|
|
|
|
|
// If the token string is blank, use helper function to generate string
|
|
|
|
jwt.DecodePaddingAllowed = data.paddedDecode
|
|
|
|
|
|
|
|
if data.tokenString == "" {
|
|
|
|
data.tokenString = signToken(data.claims, data.signingMethod)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the token
|
|
|
|
var token *jwt.Token
|
|
|
|
var err error
|
|
|
|
parser := new(jwt.Parser)
|
|
|
|
parser.SkipClaimsValidation = true
|
|
|
|
|
|
|
|
// Figure out correct claims type
|
|
|
|
token, err = parser.ParseWithClaims(data.tokenString, jwt.MapClaims{}, data.keyfunc)
|
|
|
|
|
|
|
|
if (err == nil) != data.valid || token.Valid != data.valid {
|
|
|
|
t.Errorf("[%v] Error Parsing Token with decoding padding set to %v: %v",
|
|
|
|
data.name,
|
|
|
|
data.paddedDecode,
|
|
|
|
err,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
jwt.DecodePaddingAllowed = false
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-03 23:57:36 +03:00
|
|
|
func BenchmarkParseUnverified(b *testing.B) {
|
|
|
|
|
|
|
|
// Iterate over test data set and run tests
|
|
|
|
for _, data := range jwtTestData {
|
|
|
|
// If the token string is blank, use helper function to generate string
|
|
|
|
if data.tokenString == "" {
|
2021-09-24 22:32:29 +03:00
|
|
|
data.tokenString = signToken(data.claims, data.signingMethod)
|
2021-08-03 23:57:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse the token
|
|
|
|
var parser = data.parser
|
|
|
|
if parser == nil {
|
|
|
|
parser = new(jwt.Parser)
|
|
|
|
}
|
|
|
|
// Figure out correct claims type
|
|
|
|
switch data.claims.(type) {
|
|
|
|
case jwt.MapClaims:
|
|
|
|
b.Run("map_claims", func(b *testing.B) {
|
|
|
|
benchmarkParsing(b, parser, data.tokenString, jwt.MapClaims{})
|
|
|
|
})
|
New Validation API
Some guidelines in designing the new validation API
* Previously, the `Valid` method was placed on the claim, which was always not entirely semantically correct, since the validity is concerning the token, not the claims. Although the validity of the token is based on the processing of the claims (such as `exp`). Therefore, the function `Valid` was removed from the `Claims` interface and the single canonical way to retrieve the validity of the token is to retrieve the `Valid` property of the `Token` struct.
* The previous fact was enhanced by the fact that most claims implementations had additional exported `VerifyXXX` functions, which are now removed
* All validation errors should be comparable with `errors.Is` to determine, why a particular validation has failed
* Developers want to adjust validation options. Popular options include:
* Leeway when processing exp, nbf, iat
* Not verifying `iat`, since this is actually just an informational claim. When purely looking at the standard, this should probably the default
* Verifying `aud` by default, which actually the standard sort of demands. We need to see how strong we want to enforce this
* Developers want to create their own claim types, mostly by embedding one of the existing types such as `RegisteredClaims`.
* Sometimes there is the need to further tweak the validation of a token by checking the value of a custom claim. Previously, this was possibly by overriding `Valid`. However, this was error-prone, e.g., if the original `Valid` was not called. Therefore, we should provide an easy way for *additional* checks, without by-passing the necessary validations
This leads to the following two major changes:
* The `Claims` interface now represents a set of functions that return the mandatory claims represented in a token, rather than just a `Valid` function. This is also more semantically correct.
* All validation tasks are offloaded to a new (optional) `Validator`, which can also be configured with appropriate options. If no custom validator was supplied, a default one is used.
2022-08-27 13:07:09 +03:00
|
|
|
case *jwt.RegisteredClaims:
|
|
|
|
b.Run("registered_claims", func(b *testing.B) {
|
|
|
|
benchmarkParsing(b, parser, data.tokenString, &jwt.RegisteredClaims{})
|
2021-08-03 23:57:36 +03:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper method for benchmarking various parsing methods
|
|
|
|
func benchmarkParsing(b *testing.B, parser *jwt.Parser, tokenString string, claims jwt.Claims) {
|
|
|
|
b.Helper()
|
|
|
|
b.ReportAllocs()
|
|
|
|
b.ResetTimer()
|
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
|
|
for pb.Next() {
|
|
|
|
_, _, err := parser.ParseUnverified(tokenString, jwt.MapClaims{})
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper method for benchmarking various signing methods
|
2015-04-11 23:53:09 +03:00
|
|
|
func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) {
|
2021-08-03 23:57:36 +03:00
|
|
|
b.Helper()
|
2015-04-11 23:53:09 +03:00
|
|
|
t := jwt.New(method)
|
2021-07-13 09:31:42 +03:00
|
|
|
b.ReportAllocs()
|
|
|
|
b.ResetTimer()
|
2015-04-11 23:53:09 +03:00
|
|
|
b.RunParallel(func(pb *testing.PB) {
|
|
|
|
for pb.Next() {
|
2015-04-12 00:04:22 +03:00
|
|
|
if _, err := t.SignedString(key); err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
2015-04-11 23:53:09 +03:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|