diff --git a/api/http/api_useradm.go b/api/http/api_useradm.go index 32ecbd43..f30c9a2f 100644 --- a/api/http/api_useradm.go +++ b/api/http/api_useradm.go @@ -72,7 +72,7 @@ var ( type UserAdmApiHandlers struct { userAdm useradm.App db store.DataStore - jwth jwt.Handler + jwth map[int]jwt.Handler config Config } @@ -85,7 +85,7 @@ type Config struct { func NewUserAdmApiHandlers( userAdm useradm.App, db store.DataStore, - jwth jwt.Handler, + jwth map[int]jwt.Handler, config Config, ) ApiHandler { return &UserAdmApiHandlers{ @@ -205,7 +205,13 @@ func (u *UserAdmApiHandlers) AuthLogoutHandler(w rest.ResponseWriter, r *rest.Re l := log.FromContext(ctx) if tokenStr, err := authz.ExtractToken(r.Request); err == nil { - token, err := u.jwth.FromJWT(tokenStr) + keyId := jwt.GetKeyId(tokenStr) + if _, ok := u.jwth[keyId]; !ok { + rest_utils.RestErrWithLogInternal(w, r, l, errors.New("internal error")) + return + } + + token, err := u.jwth[keyId].FromJWT(tokenStr) if err != nil { rest_utils.RestErrWithLogInternal(w, r, l, err) return @@ -374,7 +380,13 @@ func (u *UserAdmApiHandlers) UpdateUserHandler(w rest.ResponseWriter, r *rest.Re // extract the token used to update the user if tokenStr, err := authz.ExtractToken(r.Request); err == nil { - token, err := u.jwth.FromJWT(tokenStr) + keyId := jwt.GetKeyId(tokenStr) + if _, ok := u.jwth[keyId]; !ok { + rest_utils.RestErrWithLogInternal(w, r, l, errors.New("internal error")) + return + } + + token, err := u.jwth[keyId].FromJWT(tokenStr) if err != nil { rest_utils.RestErrWithLogInternal(w, r, l, err) return diff --git a/api/http/api_useradm_test.go b/api/http/api_useradm_test.go index 440bc7b5..ce50fbef 100644 --- a/api/http/api_useradm_test.go +++ b/api/http/api_useradm_test.go @@ -852,10 +852,10 @@ func makeMockApiHandler(t *testing.T, uadm useradm.App, db store.DataStore) http key, err := x509.ParsePKCS1PrivateKey(block.Bytes) assert.NoError(t, err) - jwth := jwt.NewJWTHandlerRS256(key) + jwth := jwt.NewJWTHandlerRS256(key, 0) // API handler - handlers := NewUserAdmApiHandlers(uadm, db, jwth, Config{}) + handlers := NewUserAdmApiHandlers(uadm, db, map[int]jwt.Handler{0: jwth}, Config{}) assert.NotNil(t, handlers) app, err := handlers.GetApp() @@ -879,9 +879,9 @@ func makeMockApiHandler(t *testing.T, uadm useradm.App, db store.DataStore) http mock.AnythingOfType("*log.Logger")).Return(authorizer) authzmw := &authz.AuthzMiddleware{ - Authz: authorizer, - ResFunc: ExtractResourceAction, - JWTHandler: jwth, + Authz: authorizer, + ResFunc: ExtractResourceAction, + JWTHandlers: map[int]jwt.Handler{0: jwth}, } ifmw := &rest.IfMiddleware{ diff --git a/authz/middleware.go b/authz/middleware.go index a0b03223..9830059e 100644 --- a/authz/middleware.go +++ b/authz/middleware.go @@ -35,7 +35,7 @@ const ( type AuthzMiddleware struct { Authz Authorizer ResFunc ResourceActionExtractor - JWTHandler jwt.Handler + JWTHandlers map[int]jwt.Handler JWTFallbackHandler jwt.Handler } @@ -60,8 +60,18 @@ func (mw *AuthzMiddleware) MiddlewareFunc(h rest.HandlerFunc) rest.HandlerFunc { return } + keyId := jwt.GetKeyId(tokstr) + + if _, ok := mw.JWTHandlers[keyId]; !ok { + // we have not found the corresponding handler for the key by id + // on purpose we do not return common.ErrKeyIdNotFound -- to not allow + // the enumeration attack + rest_utils.RestErrWithLog(w, r, l, ErrAuthzTokenInvalid, http.StatusUnauthorized) + return + } + // parse token, insert into env - token, err := mw.JWTHandler.FromJWT(tokstr) + token, err := mw.JWTHandlers[keyId].FromJWT(tokstr) if err != nil && mw.JWTFallbackHandler != nil { token, err = mw.JWTFallbackHandler.FromJWT(tokstr) } diff --git a/authz/middleware_test.go b/authz/middleware_test.go index f706da9f..7c42bd6a 100644 --- a/authz/middleware_test.go +++ b/authz/middleware_test.go @@ -331,11 +331,11 @@ func TestAuthzMiddleware(t *testing.T) { //finish setting up the middleware privkey := loadPrivKey("../crypto/private.pem", t) - jwth := jwt.NewJWTHandlerRS256(privkey) + jwth := jwt.NewJWTHandlerRS256(privkey, 0) mw := AuthzMiddleware{ - Authz: a, - ResFunc: resfunc, - JWTHandler: jwth, + Authz: a, + ResFunc: resfunc, + JWTHandlers: map[int]jwt.Handler{0: jwth}, } api.Use(&mw) diff --git a/common/keys.go b/common/keys.go new file mode 100644 index 00000000..5c6bd8b4 --- /dev/null +++ b/common/keys.go @@ -0,0 +1,45 @@ +// Copyright 2023 Northern.tech AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "path/filepath" + "regexp" + "strconv" + + "github.com/pkg/errors" +) + +const KeyIdZero = 0 + +var ( + ErrKeyIdNotFound = errors.New("cant locate key by key id") + ErrKeyIdCollision = errors.New("key id already loaded") +) + +func KeyIdFromPath(privateKeyPath string, privateKeyFilenamePattern string) (keyId int) { + fileName := filepath.Base(privateKeyPath) + r, _ := regexp.Compile(privateKeyFilenamePattern) + b := []byte(fileName) + indices := r.FindAllSubmatchIndex(b, -1) + keyId = KeyIdZero + if len(indices) > 0 && len(indices[0]) > 3 { + k, err := strconv.Atoi(string(b[indices[0][2]:indices[0][3]])) + if err == nil { + keyId = k + } + } + return keyId +} diff --git a/common/keys_test.go b/common/keys_test.go new file mode 100644 index 00000000..2920d895 --- /dev/null +++ b/common/keys_test.go @@ -0,0 +1,34 @@ +// Copyright 2023 Northern.tech AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "strconv" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKeyIdFromPath(t *testing.T) { + var keyId int + for i := 1; i < 1024; i++ { + keyId = KeyIdFromPath("/etc/useradm/rsa/private.id."+strconv.Itoa(i)+".pem", "private\\.id\\.([0-9]*)\\.pem") + assert.Equal(t, i, keyId) + } + for i := 1; i < 1024; i++ { + keyId = KeyIdFromPath("/etc/useradm/rsa/private.id-"+strconv.Itoa(i)+".pem", "private\\.id\\.([0-9]*)\\.pem") + assert.Equal(t, KeyIdZero, keyId) + } +} diff --git a/config.yaml b/config.yaml index e0e9ca00..397a8625 100644 --- a/config.yaml +++ b/config.yaml @@ -17,6 +17,14 @@ listen: :8080 # Overwrite with environment variable: USERADM_SERVER_PRIV_KEY_PATH # server_priv_key_path: /etc/useradm/rsa/private.pem +# Private key filename pattern - used to support multiple keys and key rotation +# Each file in a directory where server_priv_key_path reside the service checks +# against the pattern. If the file matches, then it is loaded as a private key +# identified with an id which exists in the file name. +# Defaults to: "private\\.id\\.([0-9]*)\\.pem" +# Overwrite with environment variable: USERADM_SERVER_PRIV_KEY_FILENAME_PATTERN +# server_priv_key_filename_pattern: "private\\.id\\.([0-9]*)\\.pem" + # Fallback private key path - used for JWT verification # Defaults to: none # Overwrite with environment variable: USERADM_SERVER_FALLBACK_PRIV_KEY_PATH diff --git a/config/config.go b/config/config.go index a5c3c432..44b4bc06 100644 --- a/config/config.go +++ b/config/config.go @@ -25,8 +25,10 @@ const ( SettingMiddleware = "middleware" SettingMiddlewareDefault = "prod" - SettingServerPrivKeyPath = "server_priv_key_path" - SettingServerPrivKeyPathDefault = "/etc/useradm/rsa/private.pem" + SettingServerPrivKeyPath = "server_priv_key_path" + SettingServerPrivKeyPathDefault = "/etc/useradm/rsa/private.pem" + SettingServerPrivKeyFileNamePattern = "server_priv_key_filename_pattern" + SettingServerPrivKeyFileNamePatternDefault = "private\\.id\\.([0-9]*)\\.pem" SettingServerFallbackPrivKeyPath = "server_fallback_priv_key_path" SettingServerFallbackPrivKeyPathDefault = "" @@ -73,6 +75,8 @@ var ( {Key: SettingListen, Value: SettingListenDefault}, {Key: SettingMiddleware, Value: SettingMiddlewareDefault}, {Key: SettingServerPrivKeyPath, Value: SettingServerPrivKeyPathDefault}, + {Key: SettingServerPrivKeyFileNamePattern, + Value: SettingServerPrivKeyFileNamePatternDefault}, {Key: SettingServerFallbackPrivKeyPath, Value: SettingServerFallbackPrivKeyPathDefault}, {Key: SettingJWTIssuer, Value: SettingJWTIssuerDefault}, {Key: SettingJWTExpirationTimeout, Value: SettingJWTExpirationTimeoutDefault}, diff --git a/jwt/jwt.go b/jwt/jwt.go index 7117e405..33fc5d50 100644 --- a/jwt/jwt.go +++ b/jwt/jwt.go @@ -21,6 +21,10 @@ import ( "os" "github.com/pkg/errors" + + jwtv4 "github.com/golang-jwt/jwt/v4" + + "github.com/mendersoftware/useradm/common" ) var ( @@ -45,7 +49,7 @@ type Handler interface { FromJWT(string) (*Token, error) } -func NewJWTHandler(privateKeyPath string) (Handler, error) { +func NewJWTHandler(privateKeyPath string, privateKeyFilenamePattern string) (Handler, error) { priv, err := os.ReadFile(privateKeyPath) block, _ := pem.Decode(priv) if block == nil { @@ -57,7 +61,11 @@ func NewJWTHandler(privateKeyPath string) (Handler, error) { if err != nil { return nil, errors.Wrap(err, "failed to read rsa private key") } - return NewJWTHandlerRS256(privKey), nil + return NewJWTHandlerRS256( + privKey, + common.KeyIdFromPath(privateKeyPath, privateKeyFilenamePattern), + ), + nil case pemHeaderPKCS8: key, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { @@ -65,10 +73,42 @@ func NewJWTHandler(privateKeyPath string) (Handler, error) { } switch v := key.(type) { case *rsa.PrivateKey: - return NewJWTHandlerRS256(v), nil + return NewJWTHandlerRS256( + v, + common.KeyIdFromPath(privateKeyPath, privateKeyFilenamePattern), + ), + nil case ed25519.PrivateKey: - return NewJWTHandlerEd25519(&v), nil + return NewJWTHandlerEd25519( + &v, + common.KeyIdFromPath(privateKeyPath, privateKeyFilenamePattern), + ), + nil } } return nil, errors.Errorf("unsupported server private key type") } + +func GetKeyId(tokenString string) int { + token, _, err := jwtv4.NewParser().ParseUnverified(tokenString, &Claims{}) + + if err != nil { + return common.KeyIdZero + } + + if _, ok := token.Header["kid"]; ok { + if _, ok := token.Header["kid"]; ok { + if _, isFloat := token.Header["kid"].(float64); isFloat { + return int(token.Header["kid"].(float64)) + } + if _, isInt := token.Header["kid"].(int64); isInt { + return int(token.Header["kid"].(int64)) + } + if _, isInt := token.Header["kid"].(int); isInt { + return token.Header["kid"].(int) + } + } + } + + return common.KeyIdZero +} diff --git a/jwt/jwt_ed25519.go b/jwt/jwt_ed25519.go index 29d683f2..34012204 100644 --- a/jwt/jwt_ed25519.go +++ b/jwt/jwt_ed25519.go @@ -15,38 +15,61 @@ package jwt import ( "crypto/ed25519" + "strconv" "github.com/golang-jwt/jwt/v4" "github.com/pkg/errors" + + "github.com/mendersoftware/useradm/common" ) // JWTHandlerEd25519 is an Ed25519-specific JWTHandler type JWTHandlerEd25519 struct { - privKey *ed25519.PrivateKey + privKey map[int]*ed25519.PrivateKey + currentKeyId int } -func NewJWTHandlerEd25519(privKey *ed25519.PrivateKey) *JWTHandlerEd25519 { +func NewJWTHandlerEd25519(privKey *ed25519.PrivateKey, keyId int) *JWTHandlerEd25519 { return &JWTHandlerEd25519{ - privKey: privKey, + privKey: map[int]*ed25519.PrivateKey{keyId: privKey}, + currentKeyId: keyId, } } func (j *JWTHandlerEd25519) ToJWT(token *Token) (string, error) { //generate jt := jwt.NewWithClaims(jwt.SigningMethodEdDSA, &token.Claims) - + jt.Header["kid"] = token.KeyId + if _, exists := j.privKey[token.KeyId]; !exists { + return "", common.ErrKeyIdNotFound + } //sign - data, err := jt.SignedString(j.privKey) + data, err := jt.SignedString(j.privKey[token.KeyId]) return data, err } func (j *JWTHandlerEd25519) FromJWT(tokstr string) (*Token, error) { jwttoken, err := jwt.ParseWithClaims(tokstr, &Claims{}, func(token *jwt.Token) (interface{}, error) { + keyId := common.KeyIdZero + if _, ok := token.Header["kid"]; ok { + if _, isFloat := token.Header["kid"].(float64); isFloat { + keyId = int(token.Header["kid"].(float64)) + } + if _, isInt := token.Header["kid"].(int64); isInt { + keyId = int(token.Header["kid"].(int64)) + } + if _, isInt := token.Header["kid"].(int); isInt { + keyId = token.Header["kid"].(int) + } + } if _, ok := token.Method.(*jwt.SigningMethodEd25519); !ok { return nil, errors.New("unexpected signing method: " + token.Method.Alg()) } - return j.privKey.Public(), nil + if _, exists := j.privKey[keyId]; !exists { + return nil, errors.New("cannot find the key with id " + strconv.Itoa(keyId)) + } + return j.privKey[keyId].Public(), nil }, ) diff --git a/jwt/jwt_ed25519_test.go b/jwt/jwt_ed25519_test.go index 457f2807..cc6051f6 100644 --- a/jwt/jwt_ed25519_test.go +++ b/jwt/jwt_ed25519_test.go @@ -29,7 +29,7 @@ import ( func TestNewJWTHandlerEd25519(t *testing.T) { privKey := loadEd25519PrivKey("./testdata/ed25519.pem", t) - jwtHandler := NewJWTHandlerEd25519(privKey) + jwtHandler := NewJWTHandlerEd25519(privKey, 0) assert.NotNil(t, jwtHandler) } @@ -67,7 +67,7 @@ func TestJWTHandlerEd25519GenerateToken(t *testing.T) { for name, tc := range testCases { t.Logf("test case: %s", name) - jwtHandler := NewJWTHandlerEd25519(tc.privKey) + jwtHandler := NewJWTHandlerEd25519(tc.privKey, 0) raw, err := jwtHandler.ToJWT(&Token{ Claims: tc.claims, @@ -165,6 +165,32 @@ func TestJWTHandlerEd25519FromJWT(t *testing.T) { }, }, }, + "ok (with key id 0)": { + privKey: key, + + inToken: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiIyNjRjMDljYS01N2ViLTQ2ZDctOTc3Yy03NjRiYzc1ZDYwOTIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE4MTI4MjgsImlhdCI6MTcwMTIwODAyOCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMjA4MDI4fQ.oz4f56jA1I4eGv_p2Mcmoof-EJ-I1A0qvTNU1E93HaIUsp6F5OUiZAwRM-SbauZV284A1fUjlmLPjTxSvhgyBg", + + outToken: Token{ + KeyId: 0, + Claims: Claims{ + ID: oid.FromString("264c09ca-57eb-46d7-977c-764bc75d6092"), + Subject: oid.FromString("78d27eb1-6cab-44dc-879b-ce7ee61385fe"), + ExpiresAt: &Time{ + Time: time.Unix(1701812828, 0), + }, + IssuedAt: Time{ + Time: time.Unix(1701208028, 0), + }, + NotBefore: Time{ + Time: time.Unix(1701208028, 0), + }, + Issuer: "mender.useradm", + Scope: "mender.*", + Tenant: "5abcb6de7a673a0001287c71", + User: true, + }, + }, + }, "error - bad claims": { privKey: key, @@ -208,7 +234,7 @@ func TestJWTHandlerEd25519FromJWT(t *testing.T) { for name, tc := range testCases { t.Logf("test case: %s", name) - jwtHandler := NewJWTHandlerEd25519(tc.privKey) + jwtHandler := NewJWTHandlerEd25519(tc.privKey, 0) token, err := jwtHandler.FromJWT(tc.inToken) if tc.outErr == nil { diff --git a/jwt/jwt_keys_common_test.go b/jwt/jwt_keys_common_test.go new file mode 100644 index 00000000..bb370745 --- /dev/null +++ b/jwt/jwt_keys_common_test.go @@ -0,0 +1,126 @@ +// Copyright 2023 Northern.tech AS +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package jwt + +var ( + // tokens* maps hold tokens gined by keys of given id and carrying kid, KeyIdZero, or no kid respectively + tokensEdKeyNoKid = map[int]string{ + 22899: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzYmM3NGQwZC02YjhhLTQ3OWMtOTViOS1iMGY2YTFiOWY0YWYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1MTksImlhdCI6MTcwMTE3MzcxOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzE5fQ.3VNlSrIAhvFImp8rQ-fS8R22pMeOwbGPmGYiuw7Qir_oQl9klzIVUVu07wa4zUu72sUDNfKkRbbiJtJhbZx2AQ", + 14211: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5YjIyOWMxMy0wNmFiLTRmOTctYTQ3OC1mMjUzOTNmNzdlODkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1MzMsImlhdCI6MTcwMTE3MzczMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzMzfQ.1bQJP5YuoriJc94ZwW5ssbx_BJ5SWtBVsJ6s_OzkuK8UJfiQiW1-oLgwW2bDF1HdNTN9KV3E_Xai9bC8qYTkDQ", + 5539: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0NTUzNTkzMi0wZTI5LTQ0ZGYtOGQzYi1lY2M1OTc0YmNiNWQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1NDUsImlhdCI6MTcwMTE3Mzc0NSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzQ1fQ.CefYAqWLZ1W97ImXdSRStm26eB9Zq6Kq9awl98fc23oqguJ_I6rUi8ebG65K49XJeNq-S273gM_JVOk-nrsjBA", + 826: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJmZjJjZDBmMy00ODA3LTQ2ZjYtYmQ4Yi1kY2Y5NzY5MDcyZTUiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1NTMsImlhdCI6MTcwMTE3Mzc1MywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzUzfQ.GPiEB_u71NNaiu8bh5x5QSXrk7kIAJ6e_RSQYxS6JSl_XUdy0VVbhEND5EzTJDrOSedV-uVPqRWcCT18dWx_DQ", + } + tokensRSAKeyNoKid = map[int]string{ + 21172: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxY2Q2OTI3NS1jMGFlLTQzMjEtYjZlYy04NGQxNzk2Y2Y4MzciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc4MTgsImlhdCI6MTcwMTE3MzAxOCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMDE4fQ.irr7e5MUIffqOS2oVRnBi0yT96qisEgngdwNdfIW7Z_9ldsmaqUEidHraBIfDwNP-U3LTVl1scqt4aKCgko6iuGqKxhyrRSdk0k_LaxdH0GgoUMbnmO80x2JMOwGQGm9BQAhzyqAMWBk70GL9GkAtblBUjFy2-9FY6V48O1UVLisQumLqs1PKKZU1KIvvWdzTFDPRj7Luhe0h9fqHi7Z7JXjH0q4c_s1QHMkvjOsIPHsz1qmoS8PFLGALq39Z2iqsF0ZSAbInmYLGB6tjqc-gmxZOdkdiDXb3NDC5Qx0shkqacY3vTi0CfcJqaChwlWVnLwoX-BmqRcGoTWmOzaMeg", + 13102: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjYzFjYTc0ZC05YmUwLTQ3ZjctYTg2MC1iYTI0ZTU5NDIzNDAiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc4ODEsImlhdCI6MTcwMTE3MzA4MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMDgxfQ.bbHZepFGjHJqBSSLdcGWQ9cqBAlUm_WUXyi_T3lWjXZWadP6fCMDRUn23I-fp0ZxjAiFJ8vpC9LugA75tn6sHLejiRztjZ6RLG6v7olu7V1oFLZPRfM9wn2X4MnYzaA1DI9Shy-Eo3-Sr1a3lV8Vv1i3Ts4m8oe6kFu1ehtZN8HKE0Qn2m6x8qcDtx4K-GMHoT6Q0DTmyg8d-56VgKPL5xyHq7AEDR4oG9LwCeOmCxH-WrrlZzaGnRRY28ew__VQ22eDtq3aFkBT3zsBasXW6bQmq406u86QeXxQogcXwvn8EboAcJTwMl0axvw2bZiRHx4EqG0SWUzxk15mwLNN0g", + 9478: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMWIyYWZmNC02N2UyLTQzNGEtYmI0ZS1iMjYyNWVlYWNiYjAiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc5MDcsImlhdCI6MTcwMTE3MzEwNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMTA3fQ.f1Litghr0k2CAUYVpe7ukuilCxQUgubqJoY2ovcFsMqGf2rA-nED3jXzx7cp9sjKghFmEq4ThTflcV2_ZK6IDNxGtNU_BO18e_dnPYZYkxkFJfz9fTvp9kkIcUYloDI8Z_LMKZvQhgsdphmQEiJd8v5BkAYc61J7Rj9Rr8bdZHyl1IDK3pqhNn8F7jQ7fNMhSgHq-5k_RZsQdG8OVZy9q_Ne5DLEs4E09Vo1xsFbd1471h5XjNVRMEOQysPdx_hs6pA81Z1YNN7NcWim7clvlO3xo9xEzQ_cCSneQ2cZxfLhDbMWgE0lnCKzWuq9pt8eDIMSguonjv7Yf3XmP9BMAg", + 20433: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ODUzNDY3OS01OGZiLTQzN2QtOTMwYS1mOGM0Zjg1M2Y5YWEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc5MTUsImlhdCI6MTcwMTE3MzExNSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMTE1fQ.j7gFMkIreU1x8MLVYBor1Q-zwAI81YlNnQMknAmjyB_4IrGI-_agcmcRG7xeOS9LiM9Eqvlt-HqY4NGnbJENRwqx-KejV29z0R0OP53kAgpsIACSYZKLMAw58yXmNAhddkpHltKColYAYj_TL67JHwSSb8wDZkKOTbwBJAbA4bbMkkodMyKs_udcSgsKph-yCf96LDKcW3R76lKhSL8hPPInQGoiU4VuOn_TNVs6wY5fO4Bhie4VOaOL4kAYy9ULwT_lyDfyOf-nyzChz7M_4qbOjpXKOd6QMIw_1h1yFf_5fCu9mPMfRmQ2tPY1oxRbeNdf8IB-ZyUvMYNuvZZLrw", + } + tokensByKeyIdNoKid = map[int]string{ + 22899: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzYmM3NGQwZC02YjhhLTQ3OWMtOTViOS1iMGY2YTFiOWY0YWYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1MTksImlhdCI6MTcwMTE3MzcxOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzE5fQ.3VNlSrIAhvFImp8rQ-fS8R22pMeOwbGPmGYiuw7Qir_oQl9klzIVUVu07wa4zUu72sUDNfKkRbbiJtJhbZx2AQ", + 14211: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5YjIyOWMxMy0wNmFiLTRmOTctYTQ3OC1mMjUzOTNmNzdlODkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1MzMsImlhdCI6MTcwMTE3MzczMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzMzfQ.1bQJP5YuoriJc94ZwW5ssbx_BJ5SWtBVsJ6s_OzkuK8UJfiQiW1-oLgwW2bDF1HdNTN9KV3E_Xai9bC8qYTkDQ", + 5539: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0NTUzNTkzMi0wZTI5LTQ0ZGYtOGQzYi1lY2M1OTc0YmNiNWQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1NDUsImlhdCI6MTcwMTE3Mzc0NSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzQ1fQ.CefYAqWLZ1W97ImXdSRStm26eB9Zq6Kq9awl98fc23oqguJ_I6rUi8ebG65K49XJeNq-S273gM_JVOk-nrsjBA", + 826: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJmZjJjZDBmMy00ODA3LTQ2ZjYtYmQ4Yi1kY2Y5NzY5MDcyZTUiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1NTMsImlhdCI6MTcwMTE3Mzc1MywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzUzfQ.GPiEB_u71NNaiu8bh5x5QSXrk7kIAJ6e_RSQYxS6JSl_XUdy0VVbhEND5EzTJDrOSedV-uVPqRWcCT18dWx_DQ", + 21172: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxY2Q2OTI3NS1jMGFlLTQzMjEtYjZlYy04NGQxNzk2Y2Y4MzciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc4MTgsImlhdCI6MTcwMTE3MzAxOCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMDE4fQ.irr7e5MUIffqOS2oVRnBi0yT96qisEgngdwNdfIW7Z_9ldsmaqUEidHraBIfDwNP-U3LTVl1scqt4aKCgko6iuGqKxhyrRSdk0k_LaxdH0GgoUMbnmO80x2JMOwGQGm9BQAhzyqAMWBk70GL9GkAtblBUjFy2-9FY6V48O1UVLisQumLqs1PKKZU1KIvvWdzTFDPRj7Luhe0h9fqHi7Z7JXjH0q4c_s1QHMkvjOsIPHsz1qmoS8PFLGALq39Z2iqsF0ZSAbInmYLGB6tjqc-gmxZOdkdiDXb3NDC5Qx0shkqacY3vTi0CfcJqaChwlWVnLwoX-BmqRcGoTWmOzaMeg", + 13102: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjYzFjYTc0ZC05YmUwLTQ3ZjctYTg2MC1iYTI0ZTU5NDIzNDAiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc4ODEsImlhdCI6MTcwMTE3MzA4MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMDgxfQ.bbHZepFGjHJqBSSLdcGWQ9cqBAlUm_WUXyi_T3lWjXZWadP6fCMDRUn23I-fp0ZxjAiFJ8vpC9LugA75tn6sHLejiRztjZ6RLG6v7olu7V1oFLZPRfM9wn2X4MnYzaA1DI9Shy-Eo3-Sr1a3lV8Vv1i3Ts4m8oe6kFu1ehtZN8HKE0Qn2m6x8qcDtx4K-GMHoT6Q0DTmyg8d-56VgKPL5xyHq7AEDR4oG9LwCeOmCxH-WrrlZzaGnRRY28ew__VQ22eDtq3aFkBT3zsBasXW6bQmq406u86QeXxQogcXwvn8EboAcJTwMl0axvw2bZiRHx4EqG0SWUzxk15mwLNN0g", + 9478: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMWIyYWZmNC02N2UyLTQzNGEtYmI0ZS1iMjYyNWVlYWNiYjAiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc5MDcsImlhdCI6MTcwMTE3MzEwNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMTA3fQ.f1Litghr0k2CAUYVpe7ukuilCxQUgubqJoY2ovcFsMqGf2rA-nED3jXzx7cp9sjKghFmEq4ThTflcV2_ZK6IDNxGtNU_BO18e_dnPYZYkxkFJfz9fTvp9kkIcUYloDI8Z_LMKZvQhgsdphmQEiJd8v5BkAYc61J7Rj9Rr8bdZHyl1IDK3pqhNn8F7jQ7fNMhSgHq-5k_RZsQdG8OVZy9q_Ne5DLEs4E09Vo1xsFbd1471h5XjNVRMEOQysPdx_hs6pA81Z1YNN7NcWim7clvlO3xo9xEzQ_cCSneQ2cZxfLhDbMWgE0lnCKzWuq9pt8eDIMSguonjv7Yf3XmP9BMAg", + 20433: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ODUzNDY3OS01OGZiLTQzN2QtOTMwYS1mOGM0Zjg1M2Y5YWEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc5MTUsImlhdCI6MTcwMTE3MzExNSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMTE1fQ.j7gFMkIreU1x8MLVYBor1Q-zwAI81YlNnQMknAmjyB_4IrGI-_agcmcRG7xeOS9LiM9Eqvlt-HqY4NGnbJENRwqx-KejV29z0R0OP53kAgpsIACSYZKLMAw58yXmNAhddkpHltKColYAYj_TL67JHwSSb8wDZkKOTbwBJAbA4bbMkkodMyKs_udcSgsKph-yCf96LDKcW3R76lKhSL8hPPInQGoiU4VuOn_TNVs6wY5fO4Bhie4VOaOL4kAYy9ULwT_lyDfyOf-nyzChz7M_4qbOjpXKOd6QMIw_1h1yFf_5fCu9mPMfRmQ2tPY1oxRbeNdf8IB-ZyUvMYNuvZZLrw", + } + tokensByKid = map[int]string{ + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MjI4OTksInR5cCI6IkpXVCJ9.eyJqdGkiOiJiZWI5MDI5Mi1jMDdmLTRkY2QtYjU4MS1jYWViYWI3Mzk3MzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODI5NzQsImlhdCI6MTcwMTE3ODE3NCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MTc0fQ.WJ6J97ITaWdE5XBmw530eIGc6KOaBjAuPJwf-XnuwlqFKfDRZ01GOapF4PaUojdgFHwGVpcnVp8-cSMujQARDw", + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MTQyMTEsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ZTlkNWJmZC1lNjFiLTQzZmEtOTVlMC1jYmE5MWYwMDlkYTYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMjQsImlhdCI6MTcwMTE3ODIyNCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjI0fQ.g0ksruHmBp-74rk9Dm8pU39qw1Vfxou-xsVeIg-FSLm7gDWRWN-XovmkwAamrEYqbjSR7ANQK0ykNOSgSk-1Aw", + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6NTUzOSwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZDg4OGNmZi1jMjBkLTRhZTktYjdlZi00OWQxNjUwOTA4MTQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMzYsImlhdCI6MTcwMTE3ODIzNiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjM2fQ.3kkEuku2iGyWcn2FS3evHcCIGHSm_2QHjS-u_bSSiSbmc7pvqJi1Wb7NIpwfivPxDH10GCfDjSsihEavJ7y8AA", + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6ODI2LCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIxNjU4ZGRhNi0xOGE3LTRlNWUtOWE2OC1lMWM1YzRjOGVhNzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwNTEsImlhdCI6MTcwMTE3ODI1MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjUxfQ.oe0_DlGIPJ0AjdzCB_u58VAb912YltxiBwgeu1JIEhHSycpKeS2DXKo_6spa7DL1z7hyLBS6LoRDV53B4EdzDg", + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjExNzIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNDQzNjJhZS01MjZiLTRmZGQtOWM0Zi1jNmQ5ZWU2NzI4NjciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMDksImlhdCI6MTcwMTE3ODQwOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDA5fQ.X4Vzz7JpjO38okLU7wroL80UwAJi4gR42WdBZ3jWx-4hRYk36NjkN14kbUb9z_JtPftvLI008FQd4kriFh5dAHMlIcXnE0NUKaOIdd07aiLysUZ2i7ojr3WitZFawiPcsGWo8tkCNCR3Qz-0CqLvCfI30e6eKRuWwrkq0alVWbTZtU1M6vYnkBasRFO7X0-x0Bo3Kt64owgGLzwTun2sKKHXsIFU9s04ATEKNYuvGG5j6HMcM05QEXJPPTw68Wu7hHvgDFM9y3mbCFgPd2m8YTE5bjreTo5ZtNLob79JJsynUkj8ZTju9jP5c6YUP7pcVmuLfUziwh6J3KXCqCmmvg", + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MTMxMDIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1YmY2ODU4Yi01NjcyLTQ4YjUtOWQzZS0yYmQ4NDU2ZDRlMzkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMjMsImlhdCI6MTcwMTE3ODQyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDIzfQ.NSwcvYPZC9WLZm-g1fN31SPIeHeEAvO9WgoliRRMYSF85UZQNTJV41oDi5TmVbLGYAVoIlglCL5guUZII4nVfphvA2DG_PFZLwWxzlGitSYWWQGj1ecO-Yv8lHz2LGJwYkka5wYpLh0Y7a3iFiKD0orqMC2QWlTlLQV-Vlm3PfnzEehAPHydHBRr2wLTe2e1KisCsd1yX-oET13Oty0ENZio9CdkBB8JO4GheKeTXRGzCmfpSqF_f-GrQxCYDoNgnS981RwOWmcQRzBbi3Dm-cUCO0Pryc0gK5oyvisvtkfFZoNliZtYmbckzPHInXVhccw_87dbRquIB_znFZpUtQ", + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6OTQ3OCwidHlwIjoiSldUIn0.eyJqdGkiOiI2ZDc3MGE5Yi00MTA3LTQ0YzktYmExNy01ZTE1MzBkNDI3OWIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMzcsImlhdCI6MTcwMTE3ODQzNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDM3fQ.Fsxnu5w-B4q-xeWAmLYBzUl5sgrSCXGmLMeq9-fexkO8aj5HEpcSTpnX3sKKNbS93Zxj2tnPxArpUqUh03QjFa3rmVFlK_DSY5pAr3UUSTq7wYTG9Qnvf_cA_Att1qYIhEbrnRn1mn4FGsaBA0n_92fvQ0m6cUykhyieK0Tk3p5vQ1kXtiaMWH2Y6oW3F0yoNGDshmMdpa3m66TDgmQCDEH95phB5dRKxMqvTfjzxvu7JeBmzTcVfwvuZOaB8WX8HhrkXr2D2UyYmGrXpcY7eJ_rnOJMQPgCp2sOQkIS7yMkJWW4MY9scmOXTTYbmRMbWJ0eEiuS_4G6rem7wt8twA", + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjA0MzMsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MjQ5ZDA0Ni00NGUzLTRjNmMtOTVkZC03Y2YwMTc0ZGNlZjMiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyNDgsImlhdCI6MTcwMTE3ODQ0OCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDQ4fQ.tVSKEOGIkMwPVC2XNc1Re3u2MJ1Lg3n6mv4xJgpYBG1CdPWnDE_rxH_FG2gDDU4O-vK_NJIBlhJM3m4k_GwKNbkGmUuONUoTul65yM_SbVmGXI04OgRErTuXSAAg1r8jQq-eVxWxUX4M0gZcM7c5s7YvS72GO8yYU2LzHIlGBAyliE4v6MosXHqSZTeeHdrToyjmdpRHEFPkUcxb2Oi1YDNDklOnmCDKjcdYcdi9TYvinQJZJYLCHBDlZYbw1KnOu_W-lefh-9k_rxiY6SEaITqxP3uNy3q3j_4-6O_McOqEVd-rvfBjbpmbV3vjkNdlI_UJW_gpP3o8RBtfsNjs1g", + } + tokensByKidAll = []map[int]string{ + 0: { + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZjNiZTIwZS1jN2JmLTQwMjUtOWRlYS01M2I5ZWE3ZTFkNDEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMTAsImlhdCI6MTcwMTE3OTQxMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDEwfQ.o2Vc0FYGJoy01cy2LTINoyCvlca8zs3S_M7fop6D9TtpRdxIltRH3EGMc4GsHWq4p2AwKivACGMRr9zl6GOBCQ", + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiJjNGViNDhhOS01OTI3LTQ5NGQtYTE0MC0yZGE2MTU1ZTJjYzEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMjIsImlhdCI6MTcwMTE3OTQyMiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDIyfQ.mEsojatYxdke2Nvn9pncQ834mKT-m2B96mkuJ57RlojQr1Gh2Ewar_nyGlfMgxg1XpmJVR_w5MD_W_xdEdn-CQ", + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI1YzhmNzRhMy00Yzg1LTQzMTYtYTIwYi0zZGMzZGVmMmI5YWQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMzIsImlhdCI6MTcwMTE3OTQzMiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDMyfQ.z3EhNJTyptDvmpa6G55QtV0doag2UBheZayRtbRuC-7vQ6HpL1TRqyQlNGDjhA4v1EtULrkNYBsZyoSaHgFpBA", + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI0Y2YyMzcwMy04N2I2LTRlNDktODY2YS02YmE2ZTdhZTE4YzYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyNDMsImlhdCI6MTcwMTE3OTQ0MywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDQzfQ.pyNXs9gR73V4Dz5jf5nm2AUnYtpeXL_RTjaW3qO7A-9RebxUBShDJei_P9oSOchDGuJ4hDHtqS24vIlycqiLBw", + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiJiYTU0MDk3Yi02MDNmLTQ2YjMtYTY4Yi1iOWUxZWMzOTJiMDIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQwNzcsImlhdCI6MTcwMTE3OTI3NywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5Mjc3fQ.hiHmxckoBGFMj8rtU7TBSHeuuVjVYJn3T3ZSurFqCRCvcKNU-2CfnwBZEGIpXClObFKyHKIAMIM_at1HNG4rKSmV9-_go1nLT7r2pAAowRHUFieuhsVmZlEUaXvVYsBhqYkxXW-FatwbkTIrjYsbIxXqEpQCEbo1z35qGpsT-N3NiF9nTdfCSLLyy7lWDxHvcAN78vBNOhIbvh6ULsSkpXNugYithGVZ81iD9mo4Swi8cJbzgB-UjdZc_0DPqQjS2bkKALA3oA_FxMd3dk-gMqiKgUArL_WSr_A8gBSPOSi7o7Jgps_8AnQu4zIqjtG-IZSS_P9qy5XLOPdKSh0w9A", + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI1OGIzYmI5Ny02Y2I2LTQyM2YtYmViMC0xYWI2YzFkNjMzNzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMDAsImlhdCI6MTcwMTE3OTMwMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzAwfQ.MexEDN-eHYMcEFKik4cswbB1cT8K4s_CVBYw8LjLx9ZYizNG6217r5i52vYNjnw297GshGpWUuwci4midI-fFe6-QDTB_72B3s0y-bPSMgO-bevKq_IzbjqNXV5HaIvcj1flusUWD9h5ZxVXfdgu0YwoYwyhFfnHYlW4mwVDzXHObSTGuPiCgmlGAn8SzPP0mWd9ZXpB_8DWC3KB2SxrODVl9GwGeEdYXlsYls1Y8aJ_sf4adQn6Rd2uGWZbKRfxdO7hXfrZPatI4UvsJCYPyTeDG9Z3sh4w4U1dmNQV2lwW9FZxJy-010YvPAHq6xtoyCOrO5SCzCcYiZ16hrZatA", + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiIzMmIwY2VhNy1hN2NmLTRkNTEtYWNjOS04ZDAxYmZjN2Q0ODciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMTAsImlhdCI6MTcwMTE3OTMxMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzEwfQ.cHEYO0raxTWJJnymcm9KlLKJRQsoHd_NWcyYFO5iGE9H1mVtsVDTLuP-odwV6OpG2QkW1V9_neDjRuoxQFhlOrSKdt-iyVhfEns_yUOM69R0rf1SwMEE3O2J3Kwdjh6mC0Qr671QTsrbGySO0tmCpQsX-DZKYwr_zUbcRLlZ8Zz5Q8Z3rdSpTVM3w0K1Lfku_Zk9GuaDg5vd38hllIF-AKsmFvSxX-lfnDNf8SyN-zesLhCPmTaxuw9f3aRGaoRzbWyNjyg9L0lbZyzFJJxrGlKPnCUetmQ0lYbWQTEht4SVdJMX-GVq8j5sOk10Ez2r65POIVgMOMP_t7rtvYZFdA", + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI3YzQ5YjU5ZC01Y2NiLTRiMDktODQ2ZS04MjBkNTYxZDE3YzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMjMsImlhdCI6MTcwMTE3OTMyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzIzfQ.R1-wcS2pN0NUsYvF1V99HbBdvfnpKknWoBaKme_G3JjcP3mW4zKT91o7-0cuUtu3CcCmqLPhDw1w69J89zHifv3zmV_SVwjS3GzprsAdnCp7ELY2ngLbLRMvXK4T1z269SyFxYze6e3KYhNftk05DlyP13uRy_kY0PuDkrAoy4pWWZPRlHqhaG3RGf2HpRbKnCkC_7usAAZLYziLPDSYkkOPyk6hzOWAjYElRzrD5xFvc-V8QruYnkcvNLwHxGcrWVDFaaoMT0rYtH5nhPi2vHu3GrxxmRxvvdRL1NJX0yMJOpytvWMeFGI8wff2zY7-99tF_oysvPgF0rN4SgcxDQ", + }, + 22899: { + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MjI4OTksInR5cCI6IkpXVCJ9.eyJqdGkiOiJiZWI5MDI5Mi1jMDdmLTRkY2QtYjU4MS1jYWViYWI3Mzk3MzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODI5NzQsImlhdCI6MTcwMTE3ODE3NCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MTc0fQ.WJ6J97ITaWdE5XBmw530eIGc6KOaBjAuPJwf-XnuwlqFKfDRZ01GOapF4PaUojdgFHwGVpcnVp8-cSMujQARDw", + }, + 14211: { + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MTQyMTEsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ZTlkNWJmZC1lNjFiLTQzZmEtOTVlMC1jYmE5MWYwMDlkYTYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMjQsImlhdCI6MTcwMTE3ODIyNCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjI0fQ.g0ksruHmBp-74rk9Dm8pU39qw1Vfxou-xsVeIg-FSLm7gDWRWN-XovmkwAamrEYqbjSR7ANQK0ykNOSgSk-1Aw", + }, + 5539: { + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6NTUzOSwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZDg4OGNmZi1jMjBkLTRhZTktYjdlZi00OWQxNjUwOTA4MTQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMzYsImlhdCI6MTcwMTE3ODIzNiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjM2fQ.3kkEuku2iGyWcn2FS3evHcCIGHSm_2QHjS-u_bSSiSbmc7pvqJi1Wb7NIpwfivPxDH10GCfDjSsihEavJ7y8AA", + }, + 826: { + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6ODI2LCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIxNjU4ZGRhNi0xOGE3LTRlNWUtOWE2OC1lMWM1YzRjOGVhNzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwNTEsImlhdCI6MTcwMTE3ODI1MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjUxfQ.oe0_DlGIPJ0AjdzCB_u58VAb912YltxiBwgeu1JIEhHSycpKeS2DXKo_6spa7DL1z7hyLBS6LoRDV53B4EdzDg", + }, + 21172: { + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjExNzIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNDQzNjJhZS01MjZiLTRmZGQtOWM0Zi1jNmQ5ZWU2NzI4NjciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMDksImlhdCI6MTcwMTE3ODQwOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDA5fQ.X4Vzz7JpjO38okLU7wroL80UwAJi4gR42WdBZ3jWx-4hRYk36NjkN14kbUb9z_JtPftvLI008FQd4kriFh5dAHMlIcXnE0NUKaOIdd07aiLysUZ2i7ojr3WitZFawiPcsGWo8tkCNCR3Qz-0CqLvCfI30e6eKRuWwrkq0alVWbTZtU1M6vYnkBasRFO7X0-x0Bo3Kt64owgGLzwTun2sKKHXsIFU9s04ATEKNYuvGG5j6HMcM05QEXJPPTw68Wu7hHvgDFM9y3mbCFgPd2m8YTE5bjreTo5ZtNLob79JJsynUkj8ZTju9jP5c6YUP7pcVmuLfUziwh6J3KXCqCmmvg", + }, + 13102: { + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MTMxMDIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1YmY2ODU4Yi01NjcyLTQ4YjUtOWQzZS0yYmQ4NDU2ZDRlMzkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMjMsImlhdCI6MTcwMTE3ODQyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDIzfQ.NSwcvYPZC9WLZm-g1fN31SPIeHeEAvO9WgoliRRMYSF85UZQNTJV41oDi5TmVbLGYAVoIlglCL5guUZII4nVfphvA2DG_PFZLwWxzlGitSYWWQGj1ecO-Yv8lHz2LGJwYkka5wYpLh0Y7a3iFiKD0orqMC2QWlTlLQV-Vlm3PfnzEehAPHydHBRr2wLTe2e1KisCsd1yX-oET13Oty0ENZio9CdkBB8JO4GheKeTXRGzCmfpSqF_f-GrQxCYDoNgnS981RwOWmcQRzBbi3Dm-cUCO0Pryc0gK5oyvisvtkfFZoNliZtYmbckzPHInXVhccw_87dbRquIB_znFZpUtQ", + }, + 9478: { + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6OTQ3OCwidHlwIjoiSldUIn0.eyJqdGkiOiI2ZDc3MGE5Yi00MTA3LTQ0YzktYmExNy01ZTE1MzBkNDI3OWIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMzcsImlhdCI6MTcwMTE3ODQzNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDM3fQ.Fsxnu5w-B4q-xeWAmLYBzUl5sgrSCXGmLMeq9-fexkO8aj5HEpcSTpnX3sKKNbS93Zxj2tnPxArpUqUh03QjFa3rmVFlK_DSY5pAr3UUSTq7wYTG9Qnvf_cA_Att1qYIhEbrnRn1mn4FGsaBA0n_92fvQ0m6cUykhyieK0Tk3p5vQ1kXtiaMWH2Y6oW3F0yoNGDshmMdpa3m66TDgmQCDEH95phB5dRKxMqvTfjzxvu7JeBmzTcVfwvuZOaB8WX8HhrkXr2D2UyYmGrXpcY7eJ_rnOJMQPgCp2sOQkIS7yMkJWW4MY9scmOXTTYbmRMbWJ0eEiuS_4G6rem7wt8twA", + }, + 20433: { + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjA0MzMsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MjQ5ZDA0Ni00NGUzLTRjNmMtOTVkZC03Y2YwMTc0ZGNlZjMiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyNDgsImlhdCI6MTcwMTE3ODQ0OCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDQ4fQ.tVSKEOGIkMwPVC2XNc1Re3u2MJ1Lg3n6mv4xJgpYBG1CdPWnDE_rxH_FG2gDDU4O-vK_NJIBlhJM3m4k_GwKNbkGmUuONUoTul65yM_SbVmGXI04OgRErTuXSAAg1r8jQq-eVxWxUX4M0gZcM7c5s7YvS72GO8yYU2LzHIlGBAyliE4v6MosXHqSZTeeHdrToyjmdpRHEFPkUcxb2Oi1YDNDklOnmCDKjcdYcdi9TYvinQJZJYLCHBDlZYbw1KnOu_W-lefh-9k_rxiY6SEaITqxP3uNy3q3j_4-6O_McOqEVd-rvfBjbpmbV3vjkNdlI_UJW_gpP3o8RBtfsNjs1g", + }, + } + tokensEdByKid = []map[int]string{ + 0: { + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZjNiZTIwZS1jN2JmLTQwMjUtOWRlYS01M2I5ZWE3ZTFkNDEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMTAsImlhdCI6MTcwMTE3OTQxMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDEwfQ.o2Vc0FYGJoy01cy2LTINoyCvlca8zs3S_M7fop6D9TtpRdxIltRH3EGMc4GsHWq4p2AwKivACGMRr9zl6GOBCQ", + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiJjNGViNDhhOS01OTI3LTQ5NGQtYTE0MC0yZGE2MTU1ZTJjYzEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMjIsImlhdCI6MTcwMTE3OTQyMiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDIyfQ.mEsojatYxdke2Nvn9pncQ834mKT-m2B96mkuJ57RlojQr1Gh2Ewar_nyGlfMgxg1XpmJVR_w5MD_W_xdEdn-CQ", + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI1YzhmNzRhMy00Yzg1LTQzMTYtYTIwYi0zZGMzZGVmMmI5YWQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMzIsImlhdCI6MTcwMTE3OTQzMiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDMyfQ.z3EhNJTyptDvmpa6G55QtV0doag2UBheZayRtbRuC-7vQ6HpL1TRqyQlNGDjhA4v1EtULrkNYBsZyoSaHgFpBA", + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI0Y2YyMzcwMy04N2I2LTRlNDktODY2YS02YmE2ZTdhZTE4YzYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyNDMsImlhdCI6MTcwMTE3OTQ0MywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDQzfQ.pyNXs9gR73V4Dz5jf5nm2AUnYtpeXL_RTjaW3qO7A-9RebxUBShDJei_P9oSOchDGuJ4hDHtqS24vIlycqiLBw", + }, + 22899: { + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MjI4OTksInR5cCI6IkpXVCJ9.eyJqdGkiOiJiZWI5MDI5Mi1jMDdmLTRkY2QtYjU4MS1jYWViYWI3Mzk3MzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODI5NzQsImlhdCI6MTcwMTE3ODE3NCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MTc0fQ.WJ6J97ITaWdE5XBmw530eIGc6KOaBjAuPJwf-XnuwlqFKfDRZ01GOapF4PaUojdgFHwGVpcnVp8-cSMujQARDw", + }, + 14211: { + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MTQyMTEsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ZTlkNWJmZC1lNjFiLTQzZmEtOTVlMC1jYmE5MWYwMDlkYTYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMjQsImlhdCI6MTcwMTE3ODIyNCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjI0fQ.g0ksruHmBp-74rk9Dm8pU39qw1Vfxou-xsVeIg-FSLm7gDWRWN-XovmkwAamrEYqbjSR7ANQK0ykNOSgSk-1Aw", + }, + 5539: { + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6NTUzOSwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZDg4OGNmZi1jMjBkLTRhZTktYjdlZi00OWQxNjUwOTA4MTQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMzYsImlhdCI6MTcwMTE3ODIzNiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjM2fQ.3kkEuku2iGyWcn2FS3evHcCIGHSm_2QHjS-u_bSSiSbmc7pvqJi1Wb7NIpwfivPxDH10GCfDjSsihEavJ7y8AA", + }, + 826: { + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6ODI2LCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIxNjU4ZGRhNi0xOGE3LTRlNWUtOWE2OC1lMWM1YzRjOGVhNzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwNTEsImlhdCI6MTcwMTE3ODI1MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjUxfQ.oe0_DlGIPJ0AjdzCB_u58VAb912YltxiBwgeu1JIEhHSycpKeS2DXKo_6spa7DL1z7hyLBS6LoRDV53B4EdzDg", + }, + } + tokensRSAByKid = []map[int]string{ + 0: { + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiJiYTU0MDk3Yi02MDNmLTQ2YjMtYTY4Yi1iOWUxZWMzOTJiMDIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQwNzcsImlhdCI6MTcwMTE3OTI3NywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5Mjc3fQ.hiHmxckoBGFMj8rtU7TBSHeuuVjVYJn3T3ZSurFqCRCvcKNU-2CfnwBZEGIpXClObFKyHKIAMIM_at1HNG4rKSmV9-_go1nLT7r2pAAowRHUFieuhsVmZlEUaXvVYsBhqYkxXW-FatwbkTIrjYsbIxXqEpQCEbo1z35qGpsT-N3NiF9nTdfCSLLyy7lWDxHvcAN78vBNOhIbvh6ULsSkpXNugYithGVZ81iD9mo4Swi8cJbzgB-UjdZc_0DPqQjS2bkKALA3oA_FxMd3dk-gMqiKgUArL_WSr_A8gBSPOSi7o7Jgps_8AnQu4zIqjtG-IZSS_P9qy5XLOPdKSh0w9A", + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI1OGIzYmI5Ny02Y2I2LTQyM2YtYmViMC0xYWI2YzFkNjMzNzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMDAsImlhdCI6MTcwMTE3OTMwMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzAwfQ.MexEDN-eHYMcEFKik4cswbB1cT8K4s_CVBYw8LjLx9ZYizNG6217r5i52vYNjnw297GshGpWUuwci4midI-fFe6-QDTB_72B3s0y-bPSMgO-bevKq_IzbjqNXV5HaIvcj1flusUWD9h5ZxVXfdgu0YwoYwyhFfnHYlW4mwVDzXHObSTGuPiCgmlGAn8SzPP0mWd9ZXpB_8DWC3KB2SxrODVl9GwGeEdYXlsYls1Y8aJ_sf4adQn6Rd2uGWZbKRfxdO7hXfrZPatI4UvsJCYPyTeDG9Z3sh4w4U1dmNQV2lwW9FZxJy-010YvPAHq6xtoyCOrO5SCzCcYiZ16hrZatA", + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiIzMmIwY2VhNy1hN2NmLTRkNTEtYWNjOS04ZDAxYmZjN2Q0ODciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMTAsImlhdCI6MTcwMTE3OTMxMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzEwfQ.cHEYO0raxTWJJnymcm9KlLKJRQsoHd_NWcyYFO5iGE9H1mVtsVDTLuP-odwV6OpG2QkW1V9_neDjRuoxQFhlOrSKdt-iyVhfEns_yUOM69R0rf1SwMEE3O2J3Kwdjh6mC0Qr671QTsrbGySO0tmCpQsX-DZKYwr_zUbcRLlZ8Zz5Q8Z3rdSpTVM3w0K1Lfku_Zk9GuaDg5vd38hllIF-AKsmFvSxX-lfnDNf8SyN-zesLhCPmTaxuw9f3aRGaoRzbWyNjyg9L0lbZyzFJJxrGlKPnCUetmQ0lYbWQTEht4SVdJMX-GVq8j5sOk10Ez2r65POIVgMOMP_t7rtvYZFdA", + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI3YzQ5YjU5ZC01Y2NiLTRiMDktODQ2ZS04MjBkNTYxZDE3YzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMjMsImlhdCI6MTcwMTE3OTMyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzIzfQ.R1-wcS2pN0NUsYvF1V99HbBdvfnpKknWoBaKme_G3JjcP3mW4zKT91o7-0cuUtu3CcCmqLPhDw1w69J89zHifv3zmV_SVwjS3GzprsAdnCp7ELY2ngLbLRMvXK4T1z269SyFxYze6e3KYhNftk05DlyP13uRy_kY0PuDkrAoy4pWWZPRlHqhaG3RGf2HpRbKnCkC_7usAAZLYziLPDSYkkOPyk6hzOWAjYElRzrD5xFvc-V8QruYnkcvNLwHxGcrWVDFaaoMT0rYtH5nhPi2vHu3GrxxmRxvvdRL1NJX0yMJOpytvWMeFGI8wff2zY7-99tF_oysvPgF0rN4SgcxDQ", + }, + 21172: { + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjExNzIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNDQzNjJhZS01MjZiLTRmZGQtOWM0Zi1jNmQ5ZWU2NzI4NjciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMDksImlhdCI6MTcwMTE3ODQwOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDA5fQ.X4Vzz7JpjO38okLU7wroL80UwAJi4gR42WdBZ3jWx-4hRYk36NjkN14kbUb9z_JtPftvLI008FQd4kriFh5dAHMlIcXnE0NUKaOIdd07aiLysUZ2i7ojr3WitZFawiPcsGWo8tkCNCR3Qz-0CqLvCfI30e6eKRuWwrkq0alVWbTZtU1M6vYnkBasRFO7X0-x0Bo3Kt64owgGLzwTun2sKKHXsIFU9s04ATEKNYuvGG5j6HMcM05QEXJPPTw68Wu7hHvgDFM9y3mbCFgPd2m8YTE5bjreTo5ZtNLob79JJsynUkj8ZTju9jP5c6YUP7pcVmuLfUziwh6J3KXCqCmmvg", + }, + 13102: { + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MTMxMDIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1YmY2ODU4Yi01NjcyLTQ4YjUtOWQzZS0yYmQ4NDU2ZDRlMzkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMjMsImlhdCI6MTcwMTE3ODQyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDIzfQ.NSwcvYPZC9WLZm-g1fN31SPIeHeEAvO9WgoliRRMYSF85UZQNTJV41oDi5TmVbLGYAVoIlglCL5guUZII4nVfphvA2DG_PFZLwWxzlGitSYWWQGj1ecO-Yv8lHz2LGJwYkka5wYpLh0Y7a3iFiKD0orqMC2QWlTlLQV-Vlm3PfnzEehAPHydHBRr2wLTe2e1KisCsd1yX-oET13Oty0ENZio9CdkBB8JO4GheKeTXRGzCmfpSqF_f-GrQxCYDoNgnS981RwOWmcQRzBbi3Dm-cUCO0Pryc0gK5oyvisvtkfFZoNliZtYmbckzPHInXVhccw_87dbRquIB_znFZpUtQ", + }, + 9478: { + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6OTQ3OCwidHlwIjoiSldUIn0.eyJqdGkiOiI2ZDc3MGE5Yi00MTA3LTQ0YzktYmExNy01ZTE1MzBkNDI3OWIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMzcsImlhdCI6MTcwMTE3ODQzNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDM3fQ.Fsxnu5w-B4q-xeWAmLYBzUl5sgrSCXGmLMeq9-fexkO8aj5HEpcSTpnX3sKKNbS93Zxj2tnPxArpUqUh03QjFa3rmVFlK_DSY5pAr3UUSTq7wYTG9Qnvf_cA_Att1qYIhEbrnRn1mn4FGsaBA0n_92fvQ0m6cUykhyieK0Tk3p5vQ1kXtiaMWH2Y6oW3F0yoNGDshmMdpa3m66TDgmQCDEH95phB5dRKxMqvTfjzxvu7JeBmzTcVfwvuZOaB8WX8HhrkXr2D2UyYmGrXpcY7eJ_rnOJMQPgCp2sOQkIS7yMkJWW4MY9scmOXTTYbmRMbWJ0eEiuS_4G6rem7wt8twA", + }, + 20433: { + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjA0MzMsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MjQ5ZDA0Ni00NGUzLTRjNmMtOTVkZC03Y2YwMTc0ZGNlZjMiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyNDgsImlhdCI6MTcwMTE3ODQ0OCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDQ4fQ.tVSKEOGIkMwPVC2XNc1Re3u2MJ1Lg3n6mv4xJgpYBG1CdPWnDE_rxH_FG2gDDU4O-vK_NJIBlhJM3m4k_GwKNbkGmUuONUoTul65yM_SbVmGXI04OgRErTuXSAAg1r8jQq-eVxWxUX4M0gZcM7c5s7YvS72GO8yYU2LzHIlGBAyliE4v6MosXHqSZTeeHdrToyjmdpRHEFPkUcxb2Oi1YDNDklOnmCDKjcdYcdi9TYvinQJZJYLCHBDlZYbw1KnOu_W-lefh-9k_rxiY6SEaITqxP3uNy3q3j_4-6O_McOqEVd-rvfBjbpmbV3vjkNdlI_UJW_gpP3o8RBtfsNjs1g", + }, + } +) diff --git a/jwt/jwt_rsa.go b/jwt/jwt_rsa.go index 1830a0e0..f0f67ca1 100644 --- a/jwt/jwt_rsa.go +++ b/jwt/jwt_rsa.go @@ -15,38 +15,61 @@ package jwt import ( "crypto/rsa" + "strconv" "github.com/golang-jwt/jwt/v4" "github.com/pkg/errors" + + "github.com/mendersoftware/useradm/common" ) // JWTHandlerRS256 is an RS256-specific JWTHandler type JWTHandlerRS256 struct { - privKey *rsa.PrivateKey + privKey map[int]*rsa.PrivateKey + currentKeyId int } -func NewJWTHandlerRS256(privKey *rsa.PrivateKey) *JWTHandlerRS256 { +func NewJWTHandlerRS256(privKey *rsa.PrivateKey, keyId int) *JWTHandlerRS256 { return &JWTHandlerRS256{ - privKey: privKey, + privKey: map[int]*rsa.PrivateKey{keyId: privKey}, + currentKeyId: keyId, } } func (j *JWTHandlerRS256) ToJWT(token *Token) (string, error) { //generate jt := jwt.NewWithClaims(jwt.SigningMethodRS256, &token.Claims) - + jt.Header["kid"] = token.KeyId + if _, exists := j.privKey[token.KeyId]; !exists { + return "", common.ErrKeyIdNotFound + } //sign - data, err := jt.SignedString(j.privKey) + data, err := jt.SignedString(j.privKey[token.KeyId]) return data, err } func (j *JWTHandlerRS256) FromJWT(tokstr string) (*Token, error) { jwttoken, err := jwt.ParseWithClaims(tokstr, &Claims{}, func(token *jwt.Token) (interface{}, error) { + keyId := common.KeyIdZero + if _, ok := token.Header["kid"]; ok { + if _, isFloat := token.Header["kid"].(float64); isFloat { + keyId = int(token.Header["kid"].(float64)) + } + if _, isInt := token.Header["kid"].(int64); isInt { + keyId = int(token.Header["kid"].(int64)) + } + if _, isInt := token.Header["kid"].(int); isInt { + keyId = token.Header["kid"].(int) + } + } if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, errors.New("unexpected signing method: " + token.Method.Alg()) } - return &j.privKey.PublicKey, nil + if _, exists := j.privKey[keyId]; !exists { + return nil, errors.New("cannot find the key with id " + strconv.Itoa(keyId)) + } + return &j.privKey[keyId].PublicKey, nil }, ) diff --git a/jwt/jwt_rsa_test.go b/jwt/jwt_rsa_test.go index a6d4f16d..b057a90b 100644 --- a/jwt/jwt_rsa_test.go +++ b/jwt/jwt_rsa_test.go @@ -29,7 +29,7 @@ import ( func TestNewJWTHandlerRS256(t *testing.T) { privKey := loadRSAPrivKey("./testdata/rsa.pem", t) - jwtHandler := NewJWTHandlerRS256(privKey) + jwtHandler := NewJWTHandlerRS256(privKey, 0) assert.NotNil(t, jwtHandler) } @@ -67,7 +67,7 @@ func TestJWTHandlerRS256GenerateToken(t *testing.T) { for name, tc := range testCases { t.Logf("test case: %s", name) - jwtHandler := NewJWTHandlerRS256(tc.privKey) + jwtHandler := NewJWTHandlerRS256(tc.privKey, 0) raw, err := jwtHandler.ToJWT(&Token{ Claims: tc.claims, @@ -172,6 +172,31 @@ func TestJWTHandlerRS256FromJWT(t *testing.T) { }, }, }, + "ok (with kid 0)": { + privKey: key, + + inToken: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiJiMTAwZDVkYi0zYzFjLTQyOGUtOGI1Ni1lYzExOGVkZGE3YzYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3NTcwMTUsImlhdCI6MTcwMTE1MjIxNSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTUyMjE1fQ.hxamI4u9txdFwLN4ohzw_hOMqoJRqj3LdbRTUIowRp4vHBmsNzaT1MOB792NuIThJAo1LHEtUpRKteVmQpuhtyGSpVDYzV_dMSlClQOIP7n_lKBsqc6XvZyB-WtetJLYVEVy6bg5WU3agLOvhqyex9o7bdBABWNjpX9XdKmRyb6nVSbdUZSiwsA9N0A1Y6Ns8GXDj3i2Io2o24lHD9DTul696PlaRqWW7MoA1EjSoJXZ48RMWLkBRC6l6Ks4AJTlHTC_RhrKLLpy4hDq4IKcxtwq027WwJF6BrTM3DUwu4OKL9oqE9c2tPoPTRO0cwK0AApx6Bt_6t8IJWvosTxn1w", + + outToken: Token{ + Claims: Claims{ + ID: oid.FromString("b100d5db-3c1c-428e-8b56-ec118edda7c6"), + Subject: oid.FromString("78d27eb1-6cab-44dc-879b-ce7ee61385fe"), + ExpiresAt: &Time{ + Time: time.Unix(1701757015, 0), + }, + IssuedAt: Time{ + Time: time.Unix(1701152215, 0), + }, + NotBefore: Time{ + Time: time.Unix(1701152215, 0), + }, + Issuer: "mender.useradm", + Scope: "mender.*", + Tenant: "5abcb6de7a673a0001287c71", + User: true, + }, + }, + }, "error - bad claims": { privKey: key, @@ -223,7 +248,7 @@ func TestJWTHandlerRS256FromJWT(t *testing.T) { for name, tc := range testCases { t.Logf("test case: %s", name) - jwtHandler := NewJWTHandlerRS256(tc.privKey) + jwtHandler := NewJWTHandlerRS256(tc.privKey, 0) token, err := jwtHandler.FromJWT(tc.inToken) if tc.outErr == nil { diff --git a/jwt/jwt_test.go b/jwt/jwt_test.go index 8fd60c58..40497257 100644 --- a/jwt/jwt_test.go +++ b/jwt/jwt_test.go @@ -46,7 +46,7 @@ func TestNewJWTHandler(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { - _, err := NewJWTHandler(tc.privateKeyPath) + _, err := NewJWTHandler(tc.privateKeyPath, "pem") if tc.err != nil { assert.EqualError(t, err, tc.err.Error()) } else { @@ -55,3 +55,9 @@ func TestNewJWTHandler(t *testing.T) { }) } } + +func TestGetKeyId(t *testing.T) { + for id, token := range tokensByKid { + assert.Equal(t, id, GetKeyId(token)) + } +} diff --git a/jwt/testdata/private.id.1024.pem b/jwt/testdata/private.id.1024.pem new file mode 100644 index 00000000..12788f62 --- /dev/null +++ b/jwt/testdata/private.id.1024.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGPwOUb7T4/NP9 +Y0exY0muSskQu/XWUju3BPG+PMpwe/vLw6HVr2ZTiFw62xuSc1+AbT7emz2K2iif +P2U3MFKMRZMg8c/G2HqmM+6t0sVMwdEsLycoTF2eQ2IUn8eqF/hRV4xITw5YyO1J +gZsq23mY3cMNja5B3UJya7lqBbx0X3ixP0kCsoPUbjkKmjxqffrxneXNIdBsoS1d +OVb8LSKVbbsVPz9HHjoJn8Ol7EcOpJVkTWbhsuYiRi6Ed9nybad4dt2p56AnewrT +0dMaHyGeRKTUuEwqDEl/IcSHoUqQBSQ3zFAywTsSNE1XCbxbxL0YaTGixXCyW9qM +k469FwOZAgMBAAECggEACW5H+lC8wuIsJ4Vd4hIUwuYrMyZDPKMu1LcMf6ierMPe +fO6yjTAfOwwP7yYrOp1NxsCWWWVN2S7QTeW90pFJlq4HS22VcnfWkH+Ent+Zo9wj +L5SSqqJvYFmuRHLmvwW4Og5jIwbNFOuCC5l5yzZ4+LexUWdoXxkVJyJOxhITHqoH +y0Qcblk1WmJXeeyAe70fQq1q/Wkh9rN93zSNfms0Bo4wjiK0fQKUpJse0DI1eSEG +W375YG1p//4FNda5KSfhgBsrIZRTqp+dxv6fGYfp5zGUN1LivJKklnR6PPtXmB/n +yHOFsOXmZEE1CkJQCRJ6hkKZSWLTV2Jl+oKdqZXwbQKBgQDJ2cpKT31GRkcsqI85 +wRAiDbLDGwzPfGPp/oVN5Dgx/zRZfhHtwwAPLcxZ71Pz6XDQ5mK8iG+yx8GfyI0A +0FRSnsI9dEENLZnY+8ui2RbPtZWriH78DpyDvDLNaHZnyOxtLy2XhZHGr2Tkld3H +fyATJ34QLNDTWqE/N5e87aXaSwKBgQD7ba0hUFi11D9BdfGFo6GpD/wT+/IvPHwP +R9dQXqdAh5clEvtJth5qNP4Fuo/d8VkkxGybA+/uPG9+IakTcXGae6izMsQ5Vynw +5bRJejyQDzWh4hI46es6vJP7dp/nZHct9VNpkUX1BDjRU+7QJz3PS+OkE3A+dTqQ +MxO6QTprKwKBgBLm0xucT/XZtOaIrkjRPGqD7953VP7E/jI2RFNj8KM167gJpzIl +lYbWWhF96NWpYYgeWtaezB7ot8f3psSRvaOy/Ct3DHRodcSs54fLXmh675YNhR6j +W6K2Z+d2Rcg4N3ON/G348wqw9/iz0/3PeLhydUqcd72i+gCKeyoU8vf7AoGBAOHL +Ggrr4+7PAEuiRtc0Hw77yu6LeXBQSj1S0BteFaeO3P6vcDo/MktA8ctlQqQXhW3O +dJY1z/fMEHhedD+Axnvsh5D+i1fPXlv3fZPrEY5yCkEqDCFOeTBTgDX4zxt3jswY +H+OTUhb6xqe+T55hT1HlnjaQOmHs1z3+kL3YrETnAoGBAK9domz2Y2tVig0X7HWq +IM1+SCbXCqjuAlkN8DBnmX67ZHcu9csqusv/9q4KqqOiNVbPoHQQs1j7gZB5VWAa +G9/nFOuNUGdplrn8jQKxvza466CXK4seadzVFUND6UolOm9oRomObtY8+b6Sl6yf +zFAMaFO3V4ULwcSvaauUV1NJ +-----END PRIVATE KEY----- diff --git a/jwt/testdata/private.id.13102.pem b/jwt/testdata/private.id.13102.pem new file mode 100644 index 00000000..68d4a83d --- /dev/null +++ b/jwt/testdata/private.id.13102.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCp/TdXlby+krRs +pXfscolPB6UPOrz0nMzS+Gt2xw0sExxhpiFjuqD7qpozSuWf11a25KC/TNMQgs6b +c+NdhQIPhHBDcdaqjspIAInh0f129TzO+IShoAvzh3NTVdCYhFy0kJ4FYi4WeiIA +dKjnu4hZO8osyottJV5bCI/MBUVqgqt1k3saK/sOPMvskE8ly58QKM2wrfVJoPxU +JNUNlqRNDjPTiIWTNR6XqE2R4rj0QQSk0tbCroS7/wkwHgppWFBdRsgozz99J3DG +QHWg2hTqpdS1hURvthQ938WkXXZUuopkGPt7o8FHN5bf/rqKY9m7JxEu8DabEat0 +67o2qyr1AgMBAAECggEADnjX2tWNLZ5YzP0jYhZ4OSm59Dt4dczZpUZwA7s7lFqG +KowVwKNVbEegfChutzaCk+yQAOld+MRiKZrqqv4VaFPKqPtKntesQggFtRuHHDcR +t78xr37sNY39FAW0zh9KszaytVuf99rrBSXpcV0mZQ5xjnGqAB9T0qU+dMyoFcKp +lNyV+m85vaTI3dAKhGCuZLF//m8RgujhY2lnxlF9K/Sa0ljVa2lKskAI9McGhzmB +vUSPIe1B7ED6PfJrPUaIBqyIuHMHDn4hQacROvMV1fQNuXKW4f/D5/MPgKVcTdwL +3k3TNBV6o1mYzO7RfrAH7TI7leBkMZ3dQi3jNuIe+wKBgQDDeRiwBH/8Bz1ryUWx ++gmmn4RspCRBuZ2+wTQecpuWWq6/T27UipMNirQ0Qu3EfALVTsmWD3DV/2aiUHba +6NMFxn6seFQpp17V60J+VC4EibT8jmcrFlcHNzGz1YwkyiDPeFbNF5RMY0Bdvq5N +rLmwFeYrHWDSTU+zfkrpMUJrOwKBgQDeoArNVPrh94i5PJmKCrDAj6nUm0Bz8qQ6 +R64Pj5Ec4k0oiYfANaJEpKztEEhU+LYrK8+PdSrU9MJ/vstDbc+s83vnEC4Gmevy +HEpjZbWYKliTca9NGagxCKeIlmllw+/utLbBldWQ4vIN9B1ck9Dnrer+hNFApyI2 +XHrmUi1/jwKBgQCOlPpJCj9A2xcWVE4eMEBx5dF2XZSEzJwf1FXsD7UlyyfELHCv +YBU7v30K0fcVZ0+5wCqS5thrgEyhAQhxTXr/r3Ye3/akG421hFGQTF70uG3n+weO +ClGY1c3PW2lKPJYKP8ExAI4P2iXNO5Vw2xjnNFIcCOXjALmuN+T0jBXimQKBgQDW +OJyMXuSXghD/Zi+8pHLWqKdcXRRVPjwHlKgKczTI3X4vrr+BFlsRwIBiEdP4Z+cr +fZnZyn4vs2JheF+xf75iB13Hx0rysTRYoh52rQ1j4gOfxbpQ74pnvJMxnMLI2RVM +1noxp5N9OtR+1tNWHSrddP52canFk84ZDz532MXNQwKBgQCp4kSvkUyBoDboxKV8 +vPKF4YiDsVIk2TfALTicXF8RqnmRl9dM4f90ukNmRgixRhzCjTQAQ4OaeBObShJ0 +4+mFU9aqp3bRNs5gRbu8Xvjv79xCMhj8EJmgd7u1YtXDqmJJpGRCyefTw6l8hBgN +wv7RWaRl9qTEvUXPuO5ottjnQA== +-----END PRIVATE KEY----- diff --git a/jwt/testdata/private.id.14211.pem b/jwt/testdata/private.id.14211.pem new file mode 100644 index 00000000..70521dc6 --- /dev/null +++ b/jwt/testdata/private.id.14211.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEILA027NB6NmIVG9tLoRlQ+kw/bHNTgdW/41ayOCvluv5 +-----END PRIVATE KEY----- diff --git a/jwt/testdata/private.id.20433.pem b/jwt/testdata/private.id.20433.pem new file mode 100644 index 00000000..e7e2fcbc --- /dev/null +++ b/jwt/testdata/private.id.20433.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDMJw6WUQ1OtcLE +0rtzgQ7p4I/j6LqnvuFpDlf1lEWzjRRssqc99UDHCEceSbdPRs0JrDaV8xnYHJFO +sTB1PT1HtlbOfwygNmXPxfsuNdkS61sNdVMYDTbjl4at3qvdUc1hMIh8JSzaIaQC +fVvK/OC0NBSI7PaOnHslxMZCYxGI6ibFzXfg7sDZnZG12uKElxjXN0NKzoBLh9WO +j8WhuH7zn0o5G7Pvxwn0szjoYgTQi/tPWV5n3PpAOBZX85mbBgbZ8NbG+aatsgNJ +s9PswFP3SEDaeCG5AMRCkueKvqk8ZWV2cK33HQTJQ1IyDMqS6NV5gORMNqTszjXy +GBHaCyY1AgMBAAECggEALhDSFdEMM6qH3GnAqnuApf1p+n9VPuyvaEk4R83BPJak ++TIG0tA4YbDYe1ke5+CN+5TEbGmCd2Va1GrfCFVdMoVOaSH581zS9GM0HmVAKii5 +TvweV9sDe6+BUFJibXo74aZV5c1tpXrZy0YGJHOTMJZf29W8fMmKcCdKpUg9eKsV +xmm6Ln+W32UEsoncLdqUI1x4LSpLTiXTbuYvm16ukFGO68Zjo/9KLP7HgZ+OJPPX +h0W1JHCUtOQZJujCbSfMVn9Z2tuNXmOU/1D+Bd5dKBeeC0C6KcKJ/iFl1umNCu4i +uVQkDONL1Pxdlz/5/5d1STyGG8iIeFbxE+gQ3kM+AQKBgQD2cEv24NLCA5R8XQG1 +t2RAyQyqMKuYyeYlQPXG+PvejfxfCr/aIC8AAFDpO0037EtrEJ96Kal8HvH8poZw +dU3nNcBxI6j9vCNbrJ4LQk/clP2diaj5UKhjSMy1kt9jkQWpCNRVsll+Ebej+vTM +4GAfqn8HZjDkRWDcf/gC5GDiYQKBgQDUEsMRpmfvENOf2GxX8kQwp+Fp6vAAOCSI +9ZAMnqgYsYb06+4Jr/ehCwK+di+aMfr2rJjauBzCqEnN4vkVGi+dfVkJtg5jYz3b +JrXbGtFbgTsXC07kFrFdBGEY9x76h6hYhDRQfs18L9JEI1TnwQziY/3tX6QLAiYS +SBLRtNl8VQKBgHOK7SLgABC06v5wAOg5BskVOlnlHd8x8jTPQyy5+iU7fvL0sETN +yBuBCm5/vlGU+TtGfO8i73Lhc2WYkfnSFedsnUCujoIAnrdRtHvk6FqshEhDjbTu +6zQ5orkTFqexC+1X1W4kg68HEYnRlMsl/At8vbgzYoL0QFjBqv94IjChAoGAP7Pb +EKQxuCgPyYYTFV/ah9sF5PEDYq16nAFXYdMOn0xEQ7HS8OoAsklCa7/IkMLOpeY2 +jAyd5wLyGHxDYclZ4C7U3gvnyob2/6tjGQu3M4tgGo31BqKiaFR2bi9dCNhTAzPD +GyLbyp/6wyjxyiO9IQv0LKd7+SqOERPBQ2jVo3kCgYBeWVFvBRuKdwJENCump17o +sBgkNtpZGKqZ1Ub5r2G9ZzcY3PgttD5EkfKiUOj4Xu2sld7ygX3VON/OiDzN/i6+ +m+Q4yrtF8sGIda+TNMINJrEbPGllBv/9QbWctRVPgw+rDuxQ3tjBr0xWiBsZvBXP +V4D/ASd4Kn5j+rXxA0A+wQ== +-----END PRIVATE KEY----- diff --git a/jwt/testdata/private.id.2048.pem b/jwt/testdata/private.id.2048.pem new file mode 100644 index 00000000..98b23e9b --- /dev/null +++ b/jwt/testdata/private.id.2048.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIG1ZSPHHBCWnpD1hZAsEMxMemPK26E6EbxxXgsA5M/9H +-----END PRIVATE KEY----- diff --git a/jwt/testdata/private.id.21172.pem b/jwt/testdata/private.id.21172.pem new file mode 100644 index 00000000..75d6ff62 --- /dev/null +++ b/jwt/testdata/private.id.21172.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTq3vtVp7X1/UG +/k4UNtwz+bdSfuiipOCHfrYhF8VV0D2+gu1uxB4+1/yqVUVN/3nRNQgNEoHKGSLw +q0GbltTXhIczWMXR7j9mXe0qONOI41kdF2i6S0WAJDgdQ7wqOOAo0NbpQdeZqRHZ +6w4qtfkau/1kozI49xf3oA9EIBsc/Wvcf9ytBVwtOyz8fe6eJkWu+sjAjoci40PK +OUg+jekxCCvI1wYHvU0PzsmcwMptK8QixDQMIrb3gsXj8EFMpsnbojvwjsK9NOIE +fpYW+Whv3MhJmbXEZF+R/vSs6bSfdghdwMxi32E1CNWtkE42d+jv4+OvrDiVxRVB ++tJ3QbWjAgMBAAECggEARb2VimBk26cK03AMl7pvmSxoy2SHD7eVRu9n4MVtPlXr +Ug7fXgR+FqShWhnYSNeo8lYvvebgHo9jpLo4VqQ+onfG+BMCsqV1/CtsTeIFSG/n +157z4ZWVY6ALYREFOUAeEzSj6pzIBZfgCtXYiBgG6FkSJwtiK8S1/9+byA1wAX+g +mA7fFWeN/sNcKw/5P96wqzxvZO6hT5DrmDFjgvfZ/pRZRAGi1iJpHm22sFRtHgbG +Bu/D1qcAT3O1aa2eAgbFKZt4xAw6+SVGEaC8clHbsg39qE6vfzCz1HixLM7M9G2j +kL7gYZIbySXq90LcuOo1odxrnYhXEbUi9C+/oYynMQKBgQDmfUpXX70hP2EepLH4 +yH635wEg5p+eWUOKahieJQMAO1ZbOCblYQStof74CAZc70nYYMaGBAGB7u4j6/mF +VBg0jh9LXcioJ0U5ZyOzfv5d2D/bx/CiBiXlnGkahyTv96FVTtFA0wJhoX/97Zsd +rOs+dZebSHLUc7WFXq2gMymxOQKBgQDrGPVkPrQ1BW21KM+YppOF7v229oRb/K8E +HMmWf4JWufw5DpvF7ygTPL45MwAZKy2wphLAAwunaXD+/uMd5zb3pzRCzFtj981P +ZthILC9fSlRVuOReqSaBAuq1IM0nKW4l1ClsVCn/MaI8f1WctheVn2rjeevP7KkJ +vKTGymRJuwKBgQCYBs3xIaHi+yX5C1KkMMJ8VG8HH3brAB+vfVltfiYjkf/cvQXS +yBJI4JAHU2u3AtInWXW4UY4ZANqscOotChHJIHD9ygce+oALY79Flp76kTdVpORQ +SlPWxjsBF2DCswgD96/H3SY19Usx1vY4NaUYloGAF+I9xXBWXuOQxg5a6QKBgQC7 +KiiawSzYlVTNCAuV3O/Pjf/hPIsNbLKtFHoM3cCXaQP7LL0pLIf8ILzUbtziLLT4 +UI7W3NXXVyOFbsjDksL5EN2TXHxZrvt9/kOr2blxJIzVnhf5oLKtvPO8dbu+NMaj +STAdNTgmGY6umTVASNun273OEc+BvkipHs0UEicbZwKBgEXlEj0/v6M3+4xIW+qZ +DrrpUFgfbu18iYAMRbZ410yzIulH1zQ3JYIWfRavrWCC96YAFsD14MUaJRSL3C+U +im1hIgmZsAAVkWzdl6ny0BztNBUJhYKPN9xM9vxKws2y7oBl1HHEXg/RhhBk1NAx +rkYtGPhiDWcykTjAuE+gDCYr +-----END PRIVATE KEY----- diff --git a/jwt/testdata/private.id.22899.pem b/jwt/testdata/private.id.22899.pem new file mode 100644 index 00000000..5ed0a943 --- /dev/null +++ b/jwt/testdata/private.id.22899.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIAbM8Uf9K1GlitW7Di4N6TiFmUPLxsPoW3gGIFH5lWPN +-----END PRIVATE KEY----- diff --git a/jwt/testdata/private.id.5539.pem b/jwt/testdata/private.id.5539.pem new file mode 100644 index 00000000..5fcc1ed3 --- /dev/null +++ b/jwt/testdata/private.id.5539.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIG+/KSK/ejyLC108K+UmhEkm+dvIqwD/WmF2VYYgBfFf +-----END PRIVATE KEY----- diff --git a/jwt/testdata/private.id.826.pem b/jwt/testdata/private.id.826.pem new file mode 100644 index 00000000..c9f32c9f --- /dev/null +++ b/jwt/testdata/private.id.826.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEID9KFOURI+UvLvN/k2jeFkUwOb/ULvHlMuBtmfdXniqI +-----END PRIVATE KEY----- diff --git a/jwt/testdata/private.id.9478.pem b/jwt/testdata/private.id.9478.pem new file mode 100644 index 00000000..cebee304 --- /dev/null +++ b/jwt/testdata/private.id.9478.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCfxmk9rZ680lOF +UGmZfAYIwrGKSe84HLXgRnG2yqC5g78Z6GLfZv3m4jTyj0F6zUTeGyi6lRFgpPAs +W0AVnKNEEiLAJPEt4DUNHex+PgXza+cAbGRPsQJzAFIc8LX8GfJxY8Bct1zVqf4l +JJd7Vb9pWnc8tMF+IRY23UjmeDNA4UjQDraxwRBHkyj3zso3RToiAgAUWtk65gMQ +USX1Zrav592/0rdDmFtnTtGk/nK3oGEPBF83otEPxr3+rGQwno5lu3t+1OXkCuc0 +DEq3JClH3kq2FkjrVfBN9xLHO23pg99Y4XLJ0HKpSdvGMi/H6x/HPK+idqUXPB1F +KNEDvrQzAgMBAAECggEARSpcs60dywrXyh3h4OYeLJuvxLg13Rocp86G+oTkJ/r+ +9sWxS5mnr1PcbOaUB4el2Opz6rWOVMKCEAmeh9yLlvOs5HX5jA0Dmj6Keqn55IMg ++H9zu0Xa1BLe+RVBCrXmzKxMgTaFcx9IXCbnhqY7mu1lMbvkCWhkV/LaVJ7TPTG5 +vE16/3Re+eZ7b1qWxvP0oizWpoVOhFIjaCjeWnEvid18+mcBu1yqfsnIYRmySOd3 +YocIkx0R79dURdoVfwKtWWT29KHk2wwc/J6OXBWS5XWX7jJCUlMKbyLP927TUe9f +W/QLFl+JgdbNCZFIySiMzp03dCGsXf5fTGMNuckIkQKBgQC7jI/nB9PQu5hfJ9Tz +p0WofNqXvZuWiQaQ67VQ7o5zBCMYXcgwZOvkCw3kl/IeKM3rBGWRFhKasbh3Fkpp +C7uuWpUqUEp3dABykdflsOeWLeHWaxaWSIzwoJMYgwRDl+ewt6PKdo99VosdJgsU +amtpRgjA6wcMutaZErY/qUBKQwKBgQDaFtBSZpX7pbgslIQBDt7brUnxIZhGOxml +JmEco3uNkXAdRuYQ++pGEfTbUih8CgRheiIJdFosyQkVPIqTDZqpZZftpc4d0Efj +EdHnXe+q+QcvJMKdA9H06k0ZAv3GGhduT1lubUyAGJmD1HaeUGzlEZrjt+P1COsb +JeyfYJ0nUQKBgQCSZnj2aT/JEocw0FEFGa/ghAXGqXXInpIHkldpI68CzVnv7VUV +odIZox9nL80KCeQj7ryx8r73Dn/mRfoz4brRsVf7Pc3DTUQEKyCS5fMBYjOGQvXQ +vjV2qIHqgmtTsEJlyhQLOV1tA594U3gWqbPK66ZtoYlRQI+/0cF+FEphLQKBgQCQ +d25WN1iBE1z2QwdSgBxGnvsNPXjWhQ3O5aaMxwrwU3hWHIIInCerdPejVUA2MpiG +TdZANxAI/L8Ej08obGmYbxD+PshT3YXROBu4zPQXaNFGfG5ifoZVW+Pofp/G27/L +tcqDKOn1LNZ8ioBtDM+lyky1LjpWNisvl6awSNqxkQKBgBUV6F4xunE9+1JIYvSZ +EZvMHidCxrv5939mHQVT/19eY73TQaihnvNujIgLbTPIGVsP3bQUi/zYk+MDZ4ZW +6OlT1Mn4+BNTBGbQbbHCgciE5zw83COsx3hi3s7v9g/epsL9PAqsXzZ+mva3uPVj +7fDCAkQrKaueUPi82/jWnL9+ +-----END PRIVATE KEY----- diff --git a/jwt/token.go b/jwt/token.go index a0d1cbe5..4e92aeed 100644 --- a/jwt/token.go +++ b/jwt/token.go @@ -32,6 +32,11 @@ type Token struct { LastUsed *time.Time `json:"last_used,omitempty" bson:"last_used,omitempty"` // TokenName holds the name of the token TokenName *string `json:"name,omitempty" bson:"name,omitempty"` + // KeyId is the field that corresponds to "kid" in the JWT Header, we use it + // to identify the key which was used to sign the token. It allows the private key rotation + // as long as you keep the private keys we can use the new ones (the highest id) + // to issue new tokens, while verify the old tokens with the keys used to issue them + KeyId int `json:"key_id,omitempty" bson:"key_id,omitempty"` } // MarshalJWT marshals Token into JWT comaptible format. `sign` provides means diff --git a/middleware.go b/middleware.go index 54547903..0ed205e0 100644 --- a/middleware.go +++ b/middleware.go @@ -79,7 +79,7 @@ func SetupMiddleware( api *rest.Api, mwtype string, authorizer authz.Authorizer, - jwth jwt.Handler, + jwth map[int]jwt.Handler, jwthFallback jwt.Handler, ) error { @@ -101,7 +101,7 @@ func SetupMiddleware( authzmw := &authz.AuthzMiddleware{ Authz: authorizer, ResFunc: api_http.ExtractResourceAction, - JWTHandler: jwth, + JWTHandlers: jwth, JWTFallbackHandler: jwthFallback, } diff --git a/server.go b/server.go index fd148777..32983b9f 100644 --- a/server.go +++ b/server.go @@ -15,6 +15,10 @@ package main import ( "net/http" + "os" + "path" + "path/filepath" + "regexp" "github.com/ant0ine/go-json-rest/rest" "github.com/mendersoftware/go-lib-micro/config" @@ -24,13 +28,14 @@ import ( api_http "github.com/mendersoftware/useradm/api/http" "github.com/mendersoftware/useradm/authz" "github.com/mendersoftware/useradm/client/tenant" + "github.com/mendersoftware/useradm/common" . "github.com/mendersoftware/useradm/config" "github.com/mendersoftware/useradm/jwt" "github.com/mendersoftware/useradm/store/mongo" useradm "github.com/mendersoftware/useradm/user" ) -func SetupAPI(stacktype string, authz authz.Authorizer, jwth jwt.Handler, +func SetupAPI(stacktype string, authz authz.Authorizer, jwth map[int]jwt.Handler, jwthFallback jwt.Handler) (*rest.Api, error) { api := rest.NewApi() if err := SetupMiddleware(api, stacktype, authz, jwth, jwthFallback); err != nil { @@ -50,15 +55,79 @@ func RunServer(c config.Reader) error { l := log.New(log.Ctx{}) - authz := &SimpleAuthz{} - jwtHandler, err := jwt.NewJWTHandler( - c.GetString(SettingServerPrivKeyPath), + authorizer := &SimpleAuthz{} + + // let's now go through all the existing keys and load them + jwtHandlers, err := addPrivateKeys( + l, + filepath.Dir(c.GetString(SettingServerPrivKeyPath)), + c.GetString(SettingServerPrivKeyFileNamePattern), + ) + if err != nil { + return err + } + + // the handler for keyId equal 0 is the one associated with the + // SettingServerPrivKeyPathDefault key. it is the one serving all the previously + // issued tokens (before the kid introduction in the JWTs) + defaultHandler, err := jwt.NewJWTHandler( + SettingServerPrivKeyPathDefault, + c.GetString(SettingServerPrivKeyFileNamePattern), ) + if err == nil && defaultHandler != nil { + // the key with id 0 is by default the default one. this allows + // to support tokens without "kid" in the header + // it is possible, that you rotated the default key, in which case you have to + // set USERADM_SERVER_PRIV_KEY_PATH=/etc/useradm/rsa/private.id.2048.pem + // where private.id.2048.pem is the new key, with new id. the new one will by default + // be used to issue new tokens, while any other token which has id that we have + // will be authorized against its matching key (by id from "kid" in JWT header) + // or which does not have "kid" will be authorized against the key with id 0. + // in other words: the key with id 0 (if not present as private.id.0.pem) + // is the default one, and all the JWT with no "kid" in headers are being + // checked against it. + jwtHandlers[common.KeyIdZero] = defaultHandler + } + + // if the default path is different from the currently set key path + // we still have not loaded this key. this happens when the key rotation took place, + // someone exported USERADM_SERVER_PRIV_KEY_PATH=path-to-a-new-key and this key + // now will serve all. if we do not have this, the Login will fall back to the keyId + // from the filename and either use the KeyIdZero key or fail to find the key to issue a + // token if the one set in USERADM_SERVER_PRIV_KEY_PATH does have id in the filename + // (but does not exist because we have not loaded it) + // this also means that careless setting of USERADM_SERVER_PRIV_KEY_PATH to a key that does + // not match the SettingServerPrivKeyFileNamePattern will result in + // KeyIdZero handler overwrite and lack of back support for tokens signed by it. + if c.GetString(SettingServerPrivKeyPath) != SettingServerPrivKeyPathDefault { + defaultHandler, err = jwt.NewJWTHandler( + c.GetString(SettingServerPrivKeyPath), + c.GetString(SettingServerPrivKeyFileNamePattern), + ) + if err == nil && defaultHandler != nil { + keyId := common.KeyIdFromPath( + c.GetString(SettingServerPrivKeyPath), + c.GetString(SettingServerPrivKeyFileNamePattern), + ) + if keyId == common.KeyIdZero { + l.Warnf( + "currently set private key %s either does not match %s pattern"+ + " or has explicitly set id=0. we are overridding the default"+ + " private key handler with id=0", + c.GetString(SettingServerPrivKeyPath), + c.GetString(SettingServerPrivKeyFileNamePattern), + ) + } + jwtHandlers[keyId] = defaultHandler + } + } + var jwtFallbackHandler jwt.Handler fallback := c.GetString(SettingServerFallbackPrivKeyPath) if err == nil && fallback != "" { jwtFallbackHandler, err = jwt.NewJWTHandler( fallback, + c.GetString(SettingServerPrivKeyFileNamePattern), ) } if err != nil { @@ -70,13 +139,15 @@ func RunServer(c config.Reader) error { return errors.Wrap(err, "database connection failed") } - ua := useradm.NewUserAdm(jwtHandler, db, + ua := useradm.NewUserAdm(jwtHandlers, db, useradm.Config{ Issuer: c.GetString(SettingJWTIssuer), ExpirationTimeSeconds: int64(c.GetInt(SettingJWTExpirationTimeout)), LimitSessionsPerUser: c.GetInt(SettingLimitSessionsPerUser), LimitTokensPerUser: c.GetInt(SettingLimitTokensPerUser), TokenLastUsedUpdateFreqMinutes: c.GetInt(SettingTokenLastUsedUpdateFreqMinutes), + PrivateKeyPath: c.GetString(SettingServerPrivKeyPath), + PrivateKeyFileNamePattern: c.GetString(SettingServerPrivKeyFileNamePattern), }) if tadmAddr := c.GetString(SettingTenantAdmAddr); tadmAddr != "" { @@ -89,12 +160,17 @@ func RunServer(c config.Reader) error { ua = ua.WithTenantVerification(tc) } - useradmapi := api_http.NewUserAdmApiHandlers(ua, db, jwtHandler, + useradmapi := api_http.NewUserAdmApiHandlers(ua, db, jwtHandlers, api_http.Config{ TokenMaxExpSeconds: c.GetInt(SettingTokenMaxExpirationSeconds), }) - api, err := SetupAPI(c.GetString(SettingMiddleware), authz, jwtHandler, jwtFallbackHandler) + api, err := SetupAPI( + c.GetString(SettingMiddleware), + authorizer, + jwtHandlers, + jwtFallbackHandler, + ) if err != nil { return errors.Wrap(err, "API setup failed") } @@ -110,3 +186,34 @@ func RunServer(c config.Reader) error { return http.ListenAndServe(addr, api.MakeHandler()) } + +func addPrivateKeys( + l *log.Logger, + privateKeysDirectory string, + privateKeyPattern string, +) (handlers map[int]jwt.Handler, err error) { + files, err := os.ReadDir(privateKeysDirectory) + if err != nil { + return + } + + r, err := regexp.Compile(privateKeyPattern) + if err != nil { + return + } + + handlers = make(map[int]jwt.Handler, len(files)) + for _, fileEntry := range files { + if r.MatchString(fileEntry.Name()) { + keyPath := path.Join(privateKeysDirectory, fileEntry.Name()) + handler, err := jwt.NewJWTHandler(keyPath, privateKeyPattern) + if err != nil { + continue + } + keyId := common.KeyIdFromPath(keyPath, privateKeyPattern) + l.Infof("loaded private key id=%d from %s", keyId, keyPath) + handlers[keyId] = handler + } + } + return handlers, nil +} diff --git a/server_test.go b/server_test.go index 0673f6b7..f3ea5e36 100644 --- a/server_test.go +++ b/server_test.go @@ -14,6 +14,7 @@ package main import ( + "github.com/mendersoftware/go-lib-micro/log" "testing" "github.com/stretchr/testify/assert" @@ -29,3 +30,20 @@ func TestSetupApi(t *testing.T) { assert.NotNil(t, api) assert.Nil(t, err) } + +func TestAddPrivateKeys(t *testing.T) { + l := log.New(log.Ctx{}) + handlers, err := addPrivateKeys(l, "user/testdata", "private\\.id\\.([0-9]*)\\.pem") + assert.NoError(t, err) + assert.Equal(t, 10, len(handlers)) // there are 10 keys matching the pattern + assert.Contains(t, handlers, 1024) + assert.Contains(t, handlers, 13102) + assert.Contains(t, handlers, 14211) + assert.Contains(t, handlers, 20433) + assert.Contains(t, handlers, 2048) + assert.Contains(t, handlers, 21172) + assert.Contains(t, handlers, 22899) + assert.Contains(t, handlers, 5539) + assert.Contains(t, handlers, 826) + assert.Contains(t, handlers, 9478) +} diff --git a/user/testdata/private-13102.pem b/user/testdata/private-13102.pem new file mode 100644 index 00000000..68d4a83d --- /dev/null +++ b/user/testdata/private-13102.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCp/TdXlby+krRs +pXfscolPB6UPOrz0nMzS+Gt2xw0sExxhpiFjuqD7qpozSuWf11a25KC/TNMQgs6b +c+NdhQIPhHBDcdaqjspIAInh0f129TzO+IShoAvzh3NTVdCYhFy0kJ4FYi4WeiIA +dKjnu4hZO8osyottJV5bCI/MBUVqgqt1k3saK/sOPMvskE8ly58QKM2wrfVJoPxU +JNUNlqRNDjPTiIWTNR6XqE2R4rj0QQSk0tbCroS7/wkwHgppWFBdRsgozz99J3DG +QHWg2hTqpdS1hURvthQ938WkXXZUuopkGPt7o8FHN5bf/rqKY9m7JxEu8DabEat0 +67o2qyr1AgMBAAECggEADnjX2tWNLZ5YzP0jYhZ4OSm59Dt4dczZpUZwA7s7lFqG +KowVwKNVbEegfChutzaCk+yQAOld+MRiKZrqqv4VaFPKqPtKntesQggFtRuHHDcR +t78xr37sNY39FAW0zh9KszaytVuf99rrBSXpcV0mZQ5xjnGqAB9T0qU+dMyoFcKp +lNyV+m85vaTI3dAKhGCuZLF//m8RgujhY2lnxlF9K/Sa0ljVa2lKskAI9McGhzmB +vUSPIe1B7ED6PfJrPUaIBqyIuHMHDn4hQacROvMV1fQNuXKW4f/D5/MPgKVcTdwL +3k3TNBV6o1mYzO7RfrAH7TI7leBkMZ3dQi3jNuIe+wKBgQDDeRiwBH/8Bz1ryUWx ++gmmn4RspCRBuZ2+wTQecpuWWq6/T27UipMNirQ0Qu3EfALVTsmWD3DV/2aiUHba +6NMFxn6seFQpp17V60J+VC4EibT8jmcrFlcHNzGz1YwkyiDPeFbNF5RMY0Bdvq5N +rLmwFeYrHWDSTU+zfkrpMUJrOwKBgQDeoArNVPrh94i5PJmKCrDAj6nUm0Bz8qQ6 +R64Pj5Ec4k0oiYfANaJEpKztEEhU+LYrK8+PdSrU9MJ/vstDbc+s83vnEC4Gmevy +HEpjZbWYKliTca9NGagxCKeIlmllw+/utLbBldWQ4vIN9B1ck9Dnrer+hNFApyI2 +XHrmUi1/jwKBgQCOlPpJCj9A2xcWVE4eMEBx5dF2XZSEzJwf1FXsD7UlyyfELHCv +YBU7v30K0fcVZ0+5wCqS5thrgEyhAQhxTXr/r3Ye3/akG421hFGQTF70uG3n+weO +ClGY1c3PW2lKPJYKP8ExAI4P2iXNO5Vw2xjnNFIcCOXjALmuN+T0jBXimQKBgQDW +OJyMXuSXghD/Zi+8pHLWqKdcXRRVPjwHlKgKczTI3X4vrr+BFlsRwIBiEdP4Z+cr +fZnZyn4vs2JheF+xf75iB13Hx0rysTRYoh52rQ1j4gOfxbpQ74pnvJMxnMLI2RVM +1noxp5N9OtR+1tNWHSrddP52canFk84ZDz532MXNQwKBgQCp4kSvkUyBoDboxKV8 +vPKF4YiDsVIk2TfALTicXF8RqnmRl9dM4f90ukNmRgixRhzCjTQAQ4OaeBObShJ0 +4+mFU9aqp3bRNs5gRbu8Xvjv79xCMhj8EJmgd7u1YtXDqmJJpGRCyefTw6l8hBgN +wv7RWaRl9qTEvUXPuO5ottjnQA== +-----END PRIVATE KEY----- diff --git a/user/testdata/private-826.pem b/user/testdata/private-826.pem new file mode 100644 index 00000000..c9f32c9f --- /dev/null +++ b/user/testdata/private-826.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEID9KFOURI+UvLvN/k2jeFkUwOb/ULvHlMuBtmfdXniqI +-----END PRIVATE KEY----- diff --git a/user/testdata/private-9478.pem b/user/testdata/private-9478.pem new file mode 100644 index 00000000..cebee304 --- /dev/null +++ b/user/testdata/private-9478.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCfxmk9rZ680lOF +UGmZfAYIwrGKSe84HLXgRnG2yqC5g78Z6GLfZv3m4jTyj0F6zUTeGyi6lRFgpPAs +W0AVnKNEEiLAJPEt4DUNHex+PgXza+cAbGRPsQJzAFIc8LX8GfJxY8Bct1zVqf4l +JJd7Vb9pWnc8tMF+IRY23UjmeDNA4UjQDraxwRBHkyj3zso3RToiAgAUWtk65gMQ +USX1Zrav592/0rdDmFtnTtGk/nK3oGEPBF83otEPxr3+rGQwno5lu3t+1OXkCuc0 +DEq3JClH3kq2FkjrVfBN9xLHO23pg99Y4XLJ0HKpSdvGMi/H6x/HPK+idqUXPB1F +KNEDvrQzAgMBAAECggEARSpcs60dywrXyh3h4OYeLJuvxLg13Rocp86G+oTkJ/r+ +9sWxS5mnr1PcbOaUB4el2Opz6rWOVMKCEAmeh9yLlvOs5HX5jA0Dmj6Keqn55IMg ++H9zu0Xa1BLe+RVBCrXmzKxMgTaFcx9IXCbnhqY7mu1lMbvkCWhkV/LaVJ7TPTG5 +vE16/3Re+eZ7b1qWxvP0oizWpoVOhFIjaCjeWnEvid18+mcBu1yqfsnIYRmySOd3 +YocIkx0R79dURdoVfwKtWWT29KHk2wwc/J6OXBWS5XWX7jJCUlMKbyLP927TUe9f +W/QLFl+JgdbNCZFIySiMzp03dCGsXf5fTGMNuckIkQKBgQC7jI/nB9PQu5hfJ9Tz +p0WofNqXvZuWiQaQ67VQ7o5zBCMYXcgwZOvkCw3kl/IeKM3rBGWRFhKasbh3Fkpp +C7uuWpUqUEp3dABykdflsOeWLeHWaxaWSIzwoJMYgwRDl+ewt6PKdo99VosdJgsU +amtpRgjA6wcMutaZErY/qUBKQwKBgQDaFtBSZpX7pbgslIQBDt7brUnxIZhGOxml +JmEco3uNkXAdRuYQ++pGEfTbUih8CgRheiIJdFosyQkVPIqTDZqpZZftpc4d0Efj +EdHnXe+q+QcvJMKdA9H06k0ZAv3GGhduT1lubUyAGJmD1HaeUGzlEZrjt+P1COsb +JeyfYJ0nUQKBgQCSZnj2aT/JEocw0FEFGa/ghAXGqXXInpIHkldpI68CzVnv7VUV +odIZox9nL80KCeQj7ryx8r73Dn/mRfoz4brRsVf7Pc3DTUQEKyCS5fMBYjOGQvXQ +vjV2qIHqgmtTsEJlyhQLOV1tA594U3gWqbPK66ZtoYlRQI+/0cF+FEphLQKBgQCQ +d25WN1iBE1z2QwdSgBxGnvsNPXjWhQ3O5aaMxwrwU3hWHIIInCerdPejVUA2MpiG +TdZANxAI/L8Ej08obGmYbxD+PshT3YXROBu4zPQXaNFGfG5ifoZVW+Pofp/G27/L +tcqDKOn1LNZ8ioBtDM+lyky1LjpWNisvl6awSNqxkQKBgBUV6F4xunE9+1JIYvSZ +EZvMHidCxrv5939mHQVT/19eY73TQaihnvNujIgLbTPIGVsP3bQUi/zYk+MDZ4ZW +6OlT1Mn4+BNTBGbQbbHCgciE5zw83COsx3hi3s7v9g/epsL9PAqsXzZ+mva3uPVj +7fDCAkQrKaueUPi82/jWnL9+ +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.1024.pem b/user/testdata/private.id.1024.pem new file mode 100644 index 00000000..12788f62 --- /dev/null +++ b/user/testdata/private.id.1024.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGPwOUb7T4/NP9 +Y0exY0muSskQu/XWUju3BPG+PMpwe/vLw6HVr2ZTiFw62xuSc1+AbT7emz2K2iif +P2U3MFKMRZMg8c/G2HqmM+6t0sVMwdEsLycoTF2eQ2IUn8eqF/hRV4xITw5YyO1J +gZsq23mY3cMNja5B3UJya7lqBbx0X3ixP0kCsoPUbjkKmjxqffrxneXNIdBsoS1d +OVb8LSKVbbsVPz9HHjoJn8Ol7EcOpJVkTWbhsuYiRi6Ed9nybad4dt2p56AnewrT +0dMaHyGeRKTUuEwqDEl/IcSHoUqQBSQ3zFAywTsSNE1XCbxbxL0YaTGixXCyW9qM +k469FwOZAgMBAAECggEACW5H+lC8wuIsJ4Vd4hIUwuYrMyZDPKMu1LcMf6ierMPe +fO6yjTAfOwwP7yYrOp1NxsCWWWVN2S7QTeW90pFJlq4HS22VcnfWkH+Ent+Zo9wj +L5SSqqJvYFmuRHLmvwW4Og5jIwbNFOuCC5l5yzZ4+LexUWdoXxkVJyJOxhITHqoH +y0Qcblk1WmJXeeyAe70fQq1q/Wkh9rN93zSNfms0Bo4wjiK0fQKUpJse0DI1eSEG +W375YG1p//4FNda5KSfhgBsrIZRTqp+dxv6fGYfp5zGUN1LivJKklnR6PPtXmB/n +yHOFsOXmZEE1CkJQCRJ6hkKZSWLTV2Jl+oKdqZXwbQKBgQDJ2cpKT31GRkcsqI85 +wRAiDbLDGwzPfGPp/oVN5Dgx/zRZfhHtwwAPLcxZ71Pz6XDQ5mK8iG+yx8GfyI0A +0FRSnsI9dEENLZnY+8ui2RbPtZWriH78DpyDvDLNaHZnyOxtLy2XhZHGr2Tkld3H +fyATJ34QLNDTWqE/N5e87aXaSwKBgQD7ba0hUFi11D9BdfGFo6GpD/wT+/IvPHwP +R9dQXqdAh5clEvtJth5qNP4Fuo/d8VkkxGybA+/uPG9+IakTcXGae6izMsQ5Vynw +5bRJejyQDzWh4hI46es6vJP7dp/nZHct9VNpkUX1BDjRU+7QJz3PS+OkE3A+dTqQ +MxO6QTprKwKBgBLm0xucT/XZtOaIrkjRPGqD7953VP7E/jI2RFNj8KM167gJpzIl +lYbWWhF96NWpYYgeWtaezB7ot8f3psSRvaOy/Ct3DHRodcSs54fLXmh675YNhR6j +W6K2Z+d2Rcg4N3ON/G348wqw9/iz0/3PeLhydUqcd72i+gCKeyoU8vf7AoGBAOHL +Ggrr4+7PAEuiRtc0Hw77yu6LeXBQSj1S0BteFaeO3P6vcDo/MktA8ctlQqQXhW3O +dJY1z/fMEHhedD+Axnvsh5D+i1fPXlv3fZPrEY5yCkEqDCFOeTBTgDX4zxt3jswY +H+OTUhb6xqe+T55hT1HlnjaQOmHs1z3+kL3YrETnAoGBAK9domz2Y2tVig0X7HWq +IM1+SCbXCqjuAlkN8DBnmX67ZHcu9csqusv/9q4KqqOiNVbPoHQQs1j7gZB5VWAa +G9/nFOuNUGdplrn8jQKxvza466CXK4seadzVFUND6UolOm9oRomObtY8+b6Sl6yf +zFAMaFO3V4ULwcSvaauUV1NJ +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.13102.pem b/user/testdata/private.id.13102.pem new file mode 100644 index 00000000..68d4a83d --- /dev/null +++ b/user/testdata/private.id.13102.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCp/TdXlby+krRs +pXfscolPB6UPOrz0nMzS+Gt2xw0sExxhpiFjuqD7qpozSuWf11a25KC/TNMQgs6b +c+NdhQIPhHBDcdaqjspIAInh0f129TzO+IShoAvzh3NTVdCYhFy0kJ4FYi4WeiIA +dKjnu4hZO8osyottJV5bCI/MBUVqgqt1k3saK/sOPMvskE8ly58QKM2wrfVJoPxU +JNUNlqRNDjPTiIWTNR6XqE2R4rj0QQSk0tbCroS7/wkwHgppWFBdRsgozz99J3DG +QHWg2hTqpdS1hURvthQ938WkXXZUuopkGPt7o8FHN5bf/rqKY9m7JxEu8DabEat0 +67o2qyr1AgMBAAECggEADnjX2tWNLZ5YzP0jYhZ4OSm59Dt4dczZpUZwA7s7lFqG +KowVwKNVbEegfChutzaCk+yQAOld+MRiKZrqqv4VaFPKqPtKntesQggFtRuHHDcR +t78xr37sNY39FAW0zh9KszaytVuf99rrBSXpcV0mZQ5xjnGqAB9T0qU+dMyoFcKp +lNyV+m85vaTI3dAKhGCuZLF//m8RgujhY2lnxlF9K/Sa0ljVa2lKskAI9McGhzmB +vUSPIe1B7ED6PfJrPUaIBqyIuHMHDn4hQacROvMV1fQNuXKW4f/D5/MPgKVcTdwL +3k3TNBV6o1mYzO7RfrAH7TI7leBkMZ3dQi3jNuIe+wKBgQDDeRiwBH/8Bz1ryUWx ++gmmn4RspCRBuZ2+wTQecpuWWq6/T27UipMNirQ0Qu3EfALVTsmWD3DV/2aiUHba +6NMFxn6seFQpp17V60J+VC4EibT8jmcrFlcHNzGz1YwkyiDPeFbNF5RMY0Bdvq5N +rLmwFeYrHWDSTU+zfkrpMUJrOwKBgQDeoArNVPrh94i5PJmKCrDAj6nUm0Bz8qQ6 +R64Pj5Ec4k0oiYfANaJEpKztEEhU+LYrK8+PdSrU9MJ/vstDbc+s83vnEC4Gmevy +HEpjZbWYKliTca9NGagxCKeIlmllw+/utLbBldWQ4vIN9B1ck9Dnrer+hNFApyI2 +XHrmUi1/jwKBgQCOlPpJCj9A2xcWVE4eMEBx5dF2XZSEzJwf1FXsD7UlyyfELHCv +YBU7v30K0fcVZ0+5wCqS5thrgEyhAQhxTXr/r3Ye3/akG421hFGQTF70uG3n+weO +ClGY1c3PW2lKPJYKP8ExAI4P2iXNO5Vw2xjnNFIcCOXjALmuN+T0jBXimQKBgQDW +OJyMXuSXghD/Zi+8pHLWqKdcXRRVPjwHlKgKczTI3X4vrr+BFlsRwIBiEdP4Z+cr +fZnZyn4vs2JheF+xf75iB13Hx0rysTRYoh52rQ1j4gOfxbpQ74pnvJMxnMLI2RVM +1noxp5N9OtR+1tNWHSrddP52canFk84ZDz532MXNQwKBgQCp4kSvkUyBoDboxKV8 +vPKF4YiDsVIk2TfALTicXF8RqnmRl9dM4f90ukNmRgixRhzCjTQAQ4OaeBObShJ0 +4+mFU9aqp3bRNs5gRbu8Xvjv79xCMhj8EJmgd7u1YtXDqmJJpGRCyefTw6l8hBgN +wv7RWaRl9qTEvUXPuO5ottjnQA== +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.14211.pem b/user/testdata/private.id.14211.pem new file mode 100644 index 00000000..70521dc6 --- /dev/null +++ b/user/testdata/private.id.14211.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEILA027NB6NmIVG9tLoRlQ+kw/bHNTgdW/41ayOCvluv5 +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.20433.pem b/user/testdata/private.id.20433.pem new file mode 100644 index 00000000..e7e2fcbc --- /dev/null +++ b/user/testdata/private.id.20433.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDMJw6WUQ1OtcLE +0rtzgQ7p4I/j6LqnvuFpDlf1lEWzjRRssqc99UDHCEceSbdPRs0JrDaV8xnYHJFO +sTB1PT1HtlbOfwygNmXPxfsuNdkS61sNdVMYDTbjl4at3qvdUc1hMIh8JSzaIaQC +fVvK/OC0NBSI7PaOnHslxMZCYxGI6ibFzXfg7sDZnZG12uKElxjXN0NKzoBLh9WO +j8WhuH7zn0o5G7Pvxwn0szjoYgTQi/tPWV5n3PpAOBZX85mbBgbZ8NbG+aatsgNJ +s9PswFP3SEDaeCG5AMRCkueKvqk8ZWV2cK33HQTJQ1IyDMqS6NV5gORMNqTszjXy +GBHaCyY1AgMBAAECggEALhDSFdEMM6qH3GnAqnuApf1p+n9VPuyvaEk4R83BPJak ++TIG0tA4YbDYe1ke5+CN+5TEbGmCd2Va1GrfCFVdMoVOaSH581zS9GM0HmVAKii5 +TvweV9sDe6+BUFJibXo74aZV5c1tpXrZy0YGJHOTMJZf29W8fMmKcCdKpUg9eKsV +xmm6Ln+W32UEsoncLdqUI1x4LSpLTiXTbuYvm16ukFGO68Zjo/9KLP7HgZ+OJPPX +h0W1JHCUtOQZJujCbSfMVn9Z2tuNXmOU/1D+Bd5dKBeeC0C6KcKJ/iFl1umNCu4i +uVQkDONL1Pxdlz/5/5d1STyGG8iIeFbxE+gQ3kM+AQKBgQD2cEv24NLCA5R8XQG1 +t2RAyQyqMKuYyeYlQPXG+PvejfxfCr/aIC8AAFDpO0037EtrEJ96Kal8HvH8poZw +dU3nNcBxI6j9vCNbrJ4LQk/clP2diaj5UKhjSMy1kt9jkQWpCNRVsll+Ebej+vTM +4GAfqn8HZjDkRWDcf/gC5GDiYQKBgQDUEsMRpmfvENOf2GxX8kQwp+Fp6vAAOCSI +9ZAMnqgYsYb06+4Jr/ehCwK+di+aMfr2rJjauBzCqEnN4vkVGi+dfVkJtg5jYz3b +JrXbGtFbgTsXC07kFrFdBGEY9x76h6hYhDRQfs18L9JEI1TnwQziY/3tX6QLAiYS +SBLRtNl8VQKBgHOK7SLgABC06v5wAOg5BskVOlnlHd8x8jTPQyy5+iU7fvL0sETN +yBuBCm5/vlGU+TtGfO8i73Lhc2WYkfnSFedsnUCujoIAnrdRtHvk6FqshEhDjbTu +6zQ5orkTFqexC+1X1W4kg68HEYnRlMsl/At8vbgzYoL0QFjBqv94IjChAoGAP7Pb +EKQxuCgPyYYTFV/ah9sF5PEDYq16nAFXYdMOn0xEQ7HS8OoAsklCa7/IkMLOpeY2 +jAyd5wLyGHxDYclZ4C7U3gvnyob2/6tjGQu3M4tgGo31BqKiaFR2bi9dCNhTAzPD +GyLbyp/6wyjxyiO9IQv0LKd7+SqOERPBQ2jVo3kCgYBeWVFvBRuKdwJENCump17o +sBgkNtpZGKqZ1Ub5r2G9ZzcY3PgttD5EkfKiUOj4Xu2sld7ygX3VON/OiDzN/i6+ +m+Q4yrtF8sGIda+TNMINJrEbPGllBv/9QbWctRVPgw+rDuxQ3tjBr0xWiBsZvBXP +V4D/ASd4Kn5j+rXxA0A+wQ== +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.2048.pem b/user/testdata/private.id.2048.pem new file mode 100644 index 00000000..98b23e9b --- /dev/null +++ b/user/testdata/private.id.2048.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIG1ZSPHHBCWnpD1hZAsEMxMemPK26E6EbxxXgsA5M/9H +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.21172.pem b/user/testdata/private.id.21172.pem new file mode 100644 index 00000000..75d6ff62 --- /dev/null +++ b/user/testdata/private.id.21172.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDTq3vtVp7X1/UG +/k4UNtwz+bdSfuiipOCHfrYhF8VV0D2+gu1uxB4+1/yqVUVN/3nRNQgNEoHKGSLw +q0GbltTXhIczWMXR7j9mXe0qONOI41kdF2i6S0WAJDgdQ7wqOOAo0NbpQdeZqRHZ +6w4qtfkau/1kozI49xf3oA9EIBsc/Wvcf9ytBVwtOyz8fe6eJkWu+sjAjoci40PK +OUg+jekxCCvI1wYHvU0PzsmcwMptK8QixDQMIrb3gsXj8EFMpsnbojvwjsK9NOIE +fpYW+Whv3MhJmbXEZF+R/vSs6bSfdghdwMxi32E1CNWtkE42d+jv4+OvrDiVxRVB ++tJ3QbWjAgMBAAECggEARb2VimBk26cK03AMl7pvmSxoy2SHD7eVRu9n4MVtPlXr +Ug7fXgR+FqShWhnYSNeo8lYvvebgHo9jpLo4VqQ+onfG+BMCsqV1/CtsTeIFSG/n +157z4ZWVY6ALYREFOUAeEzSj6pzIBZfgCtXYiBgG6FkSJwtiK8S1/9+byA1wAX+g +mA7fFWeN/sNcKw/5P96wqzxvZO6hT5DrmDFjgvfZ/pRZRAGi1iJpHm22sFRtHgbG +Bu/D1qcAT3O1aa2eAgbFKZt4xAw6+SVGEaC8clHbsg39qE6vfzCz1HixLM7M9G2j +kL7gYZIbySXq90LcuOo1odxrnYhXEbUi9C+/oYynMQKBgQDmfUpXX70hP2EepLH4 +yH635wEg5p+eWUOKahieJQMAO1ZbOCblYQStof74CAZc70nYYMaGBAGB7u4j6/mF +VBg0jh9LXcioJ0U5ZyOzfv5d2D/bx/CiBiXlnGkahyTv96FVTtFA0wJhoX/97Zsd +rOs+dZebSHLUc7WFXq2gMymxOQKBgQDrGPVkPrQ1BW21KM+YppOF7v229oRb/K8E +HMmWf4JWufw5DpvF7ygTPL45MwAZKy2wphLAAwunaXD+/uMd5zb3pzRCzFtj981P +ZthILC9fSlRVuOReqSaBAuq1IM0nKW4l1ClsVCn/MaI8f1WctheVn2rjeevP7KkJ +vKTGymRJuwKBgQCYBs3xIaHi+yX5C1KkMMJ8VG8HH3brAB+vfVltfiYjkf/cvQXS +yBJI4JAHU2u3AtInWXW4UY4ZANqscOotChHJIHD9ygce+oALY79Flp76kTdVpORQ +SlPWxjsBF2DCswgD96/H3SY19Usx1vY4NaUYloGAF+I9xXBWXuOQxg5a6QKBgQC7 +KiiawSzYlVTNCAuV3O/Pjf/hPIsNbLKtFHoM3cCXaQP7LL0pLIf8ILzUbtziLLT4 +UI7W3NXXVyOFbsjDksL5EN2TXHxZrvt9/kOr2blxJIzVnhf5oLKtvPO8dbu+NMaj +STAdNTgmGY6umTVASNun273OEc+BvkipHs0UEicbZwKBgEXlEj0/v6M3+4xIW+qZ +DrrpUFgfbu18iYAMRbZ410yzIulH1zQ3JYIWfRavrWCC96YAFsD14MUaJRSL3C+U +im1hIgmZsAAVkWzdl6ny0BztNBUJhYKPN9xM9vxKws2y7oBl1HHEXg/RhhBk1NAx +rkYtGPhiDWcykTjAuE+gDCYr +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.22899.pem b/user/testdata/private.id.22899.pem new file mode 100644 index 00000000..5ed0a943 --- /dev/null +++ b/user/testdata/private.id.22899.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIAbM8Uf9K1GlitW7Di4N6TiFmUPLxsPoW3gGIFH5lWPN +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.5539.pem b/user/testdata/private.id.5539.pem new file mode 100644 index 00000000..5fcc1ed3 --- /dev/null +++ b/user/testdata/private.id.5539.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEIG+/KSK/ejyLC108K+UmhEkm+dvIqwD/WmF2VYYgBfFf +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.826.pem b/user/testdata/private.id.826.pem new file mode 100644 index 00000000..c9f32c9f --- /dev/null +++ b/user/testdata/private.id.826.pem @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEID9KFOURI+UvLvN/k2jeFkUwOb/ULvHlMuBtmfdXniqI +-----END PRIVATE KEY----- diff --git a/user/testdata/private.id.9478.pem b/user/testdata/private.id.9478.pem new file mode 100644 index 00000000..cebee304 --- /dev/null +++ b/user/testdata/private.id.9478.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCfxmk9rZ680lOF +UGmZfAYIwrGKSe84HLXgRnG2yqC5g78Z6GLfZv3m4jTyj0F6zUTeGyi6lRFgpPAs +W0AVnKNEEiLAJPEt4DUNHex+PgXza+cAbGRPsQJzAFIc8LX8GfJxY8Bct1zVqf4l +JJd7Vb9pWnc8tMF+IRY23UjmeDNA4UjQDraxwRBHkyj3zso3RToiAgAUWtk65gMQ +USX1Zrav592/0rdDmFtnTtGk/nK3oGEPBF83otEPxr3+rGQwno5lu3t+1OXkCuc0 +DEq3JClH3kq2FkjrVfBN9xLHO23pg99Y4XLJ0HKpSdvGMi/H6x/HPK+idqUXPB1F +KNEDvrQzAgMBAAECggEARSpcs60dywrXyh3h4OYeLJuvxLg13Rocp86G+oTkJ/r+ +9sWxS5mnr1PcbOaUB4el2Opz6rWOVMKCEAmeh9yLlvOs5HX5jA0Dmj6Keqn55IMg ++H9zu0Xa1BLe+RVBCrXmzKxMgTaFcx9IXCbnhqY7mu1lMbvkCWhkV/LaVJ7TPTG5 +vE16/3Re+eZ7b1qWxvP0oizWpoVOhFIjaCjeWnEvid18+mcBu1yqfsnIYRmySOd3 +YocIkx0R79dURdoVfwKtWWT29KHk2wwc/J6OXBWS5XWX7jJCUlMKbyLP927TUe9f +W/QLFl+JgdbNCZFIySiMzp03dCGsXf5fTGMNuckIkQKBgQC7jI/nB9PQu5hfJ9Tz +p0WofNqXvZuWiQaQ67VQ7o5zBCMYXcgwZOvkCw3kl/IeKM3rBGWRFhKasbh3Fkpp +C7uuWpUqUEp3dABykdflsOeWLeHWaxaWSIzwoJMYgwRDl+ewt6PKdo99VosdJgsU +amtpRgjA6wcMutaZErY/qUBKQwKBgQDaFtBSZpX7pbgslIQBDt7brUnxIZhGOxml +JmEco3uNkXAdRuYQ++pGEfTbUih8CgRheiIJdFosyQkVPIqTDZqpZZftpc4d0Efj +EdHnXe+q+QcvJMKdA9H06k0ZAv3GGhduT1lubUyAGJmD1HaeUGzlEZrjt+P1COsb +JeyfYJ0nUQKBgQCSZnj2aT/JEocw0FEFGa/ghAXGqXXInpIHkldpI68CzVnv7VUV +odIZox9nL80KCeQj7ryx8r73Dn/mRfoz4brRsVf7Pc3DTUQEKyCS5fMBYjOGQvXQ +vjV2qIHqgmtTsEJlyhQLOV1tA594U3gWqbPK66ZtoYlRQI+/0cF+FEphLQKBgQCQ +d25WN1iBE1z2QwdSgBxGnvsNPXjWhQ3O5aaMxwrwU3hWHIIInCerdPejVUA2MpiG +TdZANxAI/L8Ej08obGmYbxD+PshT3YXROBu4zPQXaNFGfG5ifoZVW+Pofp/G27/L +tcqDKOn1LNZ8ioBtDM+lyky1LjpWNisvl6awSNqxkQKBgBUV6F4xunE9+1JIYvSZ +EZvMHidCxrv5939mHQVT/19eY73TQaihnvNujIgLbTPIGVsP3bQUi/zYk+MDZ4ZW +6OlT1Mn4+BNTBGbQbbHCgciE5zw83COsx3hi3s7v9g/epsL9PAqsXzZ+mva3uPVj +7fDCAkQrKaueUPi82/jWnL9+ +-----END PRIVATE KEY----- diff --git a/user/useradm.go b/user/useradm.go index 8231cd9e..03fa947b 100644 --- a/user/useradm.go +++ b/user/useradm.go @@ -26,6 +26,7 @@ import ( "golang.org/x/crypto/bcrypt" "github.com/mendersoftware/useradm/client/tenant" + "github.com/mendersoftware/useradm/common" "github.com/mendersoftware/useradm/jwt" "github.com/mendersoftware/useradm/model" "github.com/mendersoftware/useradm/scope" @@ -103,6 +104,11 @@ type Config struct { // how often we should update personal access token // with last used timestamp TokenLastUsedUpdateFreqMinutes int + // path to the private key, used to generate kid header in JWT and to get all the keys + PrivateKeyPath string + // PrivateKeyFileNamePattern holds the regular expression used + // to get the key id from a filename + PrivateKeyFileNamePattern string } type ApiClientGetter func() apiclient.HttpRunner @@ -113,7 +119,7 @@ func simpleApiClientGetter() apiclient.HttpRunner { type UserAdm struct { // JWT serialized/deserializer - jwtHandler jwt.Handler + jwtHandlers map[int]jwt.Handler db store.DataStore config Config verifyTenant bool @@ -121,10 +127,10 @@ type UserAdm struct { clientGetter ApiClientGetter } -func NewUserAdm(jwtHandler jwt.Handler, db store.DataStore, config Config) *UserAdm { +func NewUserAdm(jwtHandlers map[int]jwt.Handler, db store.DataStore, config Config) *UserAdm { return &UserAdm{ - jwtHandler: jwtHandler, + jwtHandlers: jwtHandlers, db: db, config: config, clientGetter: simpleApiClientGetter, @@ -194,7 +200,13 @@ func (u *UserAdm) Login(ctx context.Context, email model.Email, pass string, } //generate and save token - t, err := u.generateToken(user.ID, scope.All, ident.Tenant, options.NoExpiry) + t, err := u.generateToken( + user.ID, + scope.All, + ident.Tenant, + options.NoExpiry, + common.KeyIdFromPath(u.config.PrivateKeyPath, u.config.PrivateKeyFileNamePattern), + ) if err != nil { return nil, errors.Wrap(err, "useradm: failed to generate token") } @@ -218,20 +230,22 @@ func (u *UserAdm) Login(ctx context.Context, email model.Email, pass string, } func (u *UserAdm) generateToken(subject, scope, tenant string, - noExpiry bool) (*jwt.Token, error) { + noExpiry bool, keyId int) (*jwt.Token, error) { id := oid.NewUUIDv4() subjectID := oid.FromString(subject) now := jwt.Time{Time: time.Now()} - ret := &jwt.Token{Claims: jwt.Claims{ - ID: id, - Subject: subjectID, - Issuer: u.config.Issuer, - IssuedAt: now, - NotBefore: now, - Tenant: tenant, - Scope: scope, - User: true, - }} + ret := &jwt.Token{ + KeyId: keyId, + Claims: jwt.Claims{ + ID: id, + Subject: subjectID, + Issuer: u.config.Issuer, + IssuedAt: now, + NotBefore: now, + Tenant: tenant, + Scope: scope, + User: true, + }} if !noExpiry { ret.Claims.ExpiresAt = &jwt.Time{ Time: now.Add(time.Second * time.Duration(u.config.ExpirationTimeSeconds)), @@ -241,7 +255,10 @@ func (u *UserAdm) generateToken(subject, scope, tenant string, } func (u *UserAdm) SignToken(ctx context.Context, t *jwt.Token) (string, error) { - return u.jwtHandler.ToJWT(t) + if _, ok := u.jwtHandlers[t.KeyId]; !ok { + return "", common.ErrKeyIdNotFound + } + return u.jwtHandlers[t.KeyId].ToJWT(t) } func (u *UserAdm) Logout(ctx context.Context, token *jwt.Token) error { @@ -648,7 +665,14 @@ func (u *UserAdm) IssuePersonalAccessToken( } } //generate and save token - t, err := u.generateToken(id.Subject, scope.All, id.Tenant, tr.ExpiresIn == 0) + keyId := common.KeyIdFromPath(u.config.PrivateKeyPath, u.config.PrivateKeyFileNamePattern) + t, err := u.generateToken( + id.Subject, + scope.All, + id.Tenant, + tr.ExpiresIn == 0, + keyId, + ) if err != nil { return "", errors.Wrap(err, "useradm: failed to generate token") } @@ -668,8 +692,11 @@ func (u *UserAdm) IssuePersonalAccessToken( return "", errors.Wrap(err, "useradm: failed to save token") } + if _, ok := u.jwtHandlers[keyId]; !ok { + return "", common.ErrKeyIdNotFound + } // sign token - return u.jwtHandler.ToJWT(t) + return u.jwtHandlers[keyId].ToJWT(t) } func (ua *UserAdm) GetPersonalAccessTokens( diff --git a/user/useradm_test.go b/user/useradm_test.go index 6f44f2cb..906e6069 100644 --- a/user/useradm_test.go +++ b/user/useradm_test.go @@ -15,7 +15,9 @@ package useradm import ( "context" + "github.com/mendersoftware/useradm/common" "net/http" + "strconv" "testing" "time" @@ -120,7 +122,7 @@ func TestUserAdmSignToken(t *testing.T) { mock.AnythingOfType("*jwt.Token"), ).Return(tc.signed, tc.signErr) - useradm := NewUserAdm(&mockJWTHandler, nil, tc.config) + useradm := NewUserAdm(map[int]jwt.Handler{0: &mockJWTHandler}, nil, tc.config) signed, err := useradm.SignToken(ctx, &jwt.Token{}) if tc.signErr != nil { @@ -1779,7 +1781,7 @@ func TestUserAdmIssuePersonalAccessToken(t *testing.T) { mock.AnythingOfType("*jwt.Token"), ).Return("signed", nil) - useradm := NewUserAdm(&mockJWTHandler, db, tc.config) + useradm := NewUserAdm(map[int]jwt.Handler{0: &mockJWTHandler}, db, tc.config) id := &identity.Identity{ Subject: "foo", @@ -1926,3 +1928,269 @@ func TestUserAdmGetPlanBinding(t *testing.T) { }) } } + +var ( + // tokens* maps hold tokens gined by keys of given id and carrying kid, KeyIdZero, or no kid respectively + tokensEdKeyNoKid = map[int]string{ + 22899: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzYmM3NGQwZC02YjhhLTQ3OWMtOTViOS1iMGY2YTFiOWY0YWYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1MTksImlhdCI6MTcwMTE3MzcxOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzE5fQ.3VNlSrIAhvFImp8rQ-fS8R22pMeOwbGPmGYiuw7Qir_oQl9klzIVUVu07wa4zUu72sUDNfKkRbbiJtJhbZx2AQ", + 14211: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5YjIyOWMxMy0wNmFiLTRmOTctYTQ3OC1mMjUzOTNmNzdlODkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1MzMsImlhdCI6MTcwMTE3MzczMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzMzfQ.1bQJP5YuoriJc94ZwW5ssbx_BJ5SWtBVsJ6s_OzkuK8UJfiQiW1-oLgwW2bDF1HdNTN9KV3E_Xai9bC8qYTkDQ", + 5539: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0NTUzNTkzMi0wZTI5LTQ0ZGYtOGQzYi1lY2M1OTc0YmNiNWQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1NDUsImlhdCI6MTcwMTE3Mzc0NSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzQ1fQ.CefYAqWLZ1W97ImXdSRStm26eB9Zq6Kq9awl98fc23oqguJ_I6rUi8ebG65K49XJeNq-S273gM_JVOk-nrsjBA", + 826: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJmZjJjZDBmMy00ODA3LTQ2ZjYtYmQ4Yi1kY2Y5NzY5MDcyZTUiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1NTMsImlhdCI6MTcwMTE3Mzc1MywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzUzfQ.GPiEB_u71NNaiu8bh5x5QSXrk7kIAJ6e_RSQYxS6JSl_XUdy0VVbhEND5EzTJDrOSedV-uVPqRWcCT18dWx_DQ", + } + tokensRSAKeyNoKid = map[int]string{ + 21172: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxY2Q2OTI3NS1jMGFlLTQzMjEtYjZlYy04NGQxNzk2Y2Y4MzciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc4MTgsImlhdCI6MTcwMTE3MzAxOCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMDE4fQ.irr7e5MUIffqOS2oVRnBi0yT96qisEgngdwNdfIW7Z_9ldsmaqUEidHraBIfDwNP-U3LTVl1scqt4aKCgko6iuGqKxhyrRSdk0k_LaxdH0GgoUMbnmO80x2JMOwGQGm9BQAhzyqAMWBk70GL9GkAtblBUjFy2-9FY6V48O1UVLisQumLqs1PKKZU1KIvvWdzTFDPRj7Luhe0h9fqHi7Z7JXjH0q4c_s1QHMkvjOsIPHsz1qmoS8PFLGALq39Z2iqsF0ZSAbInmYLGB6tjqc-gmxZOdkdiDXb3NDC5Qx0shkqacY3vTi0CfcJqaChwlWVnLwoX-BmqRcGoTWmOzaMeg", + 13102: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjYzFjYTc0ZC05YmUwLTQ3ZjctYTg2MC1iYTI0ZTU5NDIzNDAiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc4ODEsImlhdCI6MTcwMTE3MzA4MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMDgxfQ.bbHZepFGjHJqBSSLdcGWQ9cqBAlUm_WUXyi_T3lWjXZWadP6fCMDRUn23I-fp0ZxjAiFJ8vpC9LugA75tn6sHLejiRztjZ6RLG6v7olu7V1oFLZPRfM9wn2X4MnYzaA1DI9Shy-Eo3-Sr1a3lV8Vv1i3Ts4m8oe6kFu1ehtZN8HKE0Qn2m6x8qcDtx4K-GMHoT6Q0DTmyg8d-56VgKPL5xyHq7AEDR4oG9LwCeOmCxH-WrrlZzaGnRRY28ew__VQ22eDtq3aFkBT3zsBasXW6bQmq406u86QeXxQogcXwvn8EboAcJTwMl0axvw2bZiRHx4EqG0SWUzxk15mwLNN0g", + 9478: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMWIyYWZmNC02N2UyLTQzNGEtYmI0ZS1iMjYyNWVlYWNiYjAiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc5MDcsImlhdCI6MTcwMTE3MzEwNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMTA3fQ.f1Litghr0k2CAUYVpe7ukuilCxQUgubqJoY2ovcFsMqGf2rA-nED3jXzx7cp9sjKghFmEq4ThTflcV2_ZK6IDNxGtNU_BO18e_dnPYZYkxkFJfz9fTvp9kkIcUYloDI8Z_LMKZvQhgsdphmQEiJd8v5BkAYc61J7Rj9Rr8bdZHyl1IDK3pqhNn8F7jQ7fNMhSgHq-5k_RZsQdG8OVZy9q_Ne5DLEs4E09Vo1xsFbd1471h5XjNVRMEOQysPdx_hs6pA81Z1YNN7NcWim7clvlO3xo9xEzQ_cCSneQ2cZxfLhDbMWgE0lnCKzWuq9pt8eDIMSguonjv7Yf3XmP9BMAg", + 20433: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ODUzNDY3OS01OGZiLTQzN2QtOTMwYS1mOGM0Zjg1M2Y5YWEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc5MTUsImlhdCI6MTcwMTE3MzExNSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMTE1fQ.j7gFMkIreU1x8MLVYBor1Q-zwAI81YlNnQMknAmjyB_4IrGI-_agcmcRG7xeOS9LiM9Eqvlt-HqY4NGnbJENRwqx-KejV29z0R0OP53kAgpsIACSYZKLMAw58yXmNAhddkpHltKColYAYj_TL67JHwSSb8wDZkKOTbwBJAbA4bbMkkodMyKs_udcSgsKph-yCf96LDKcW3R76lKhSL8hPPInQGoiU4VuOn_TNVs6wY5fO4Bhie4VOaOL4kAYy9ULwT_lyDfyOf-nyzChz7M_4qbOjpXKOd6QMIw_1h1yFf_5fCu9mPMfRmQ2tPY1oxRbeNdf8IB-ZyUvMYNuvZZLrw", + } + tokensByKeyIdNoKid = map[int]string{ + 22899: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzYmM3NGQwZC02YjhhLTQ3OWMtOTViOS1iMGY2YTFiOWY0YWYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1MTksImlhdCI6MTcwMTE3MzcxOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzE5fQ.3VNlSrIAhvFImp8rQ-fS8R22pMeOwbGPmGYiuw7Qir_oQl9klzIVUVu07wa4zUu72sUDNfKkRbbiJtJhbZx2AQ", + 14211: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5YjIyOWMxMy0wNmFiLTRmOTctYTQ3OC1mMjUzOTNmNzdlODkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1MzMsImlhdCI6MTcwMTE3MzczMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzMzfQ.1bQJP5YuoriJc94ZwW5ssbx_BJ5SWtBVsJ6s_OzkuK8UJfiQiW1-oLgwW2bDF1HdNTN9KV3E_Xai9bC8qYTkDQ", + 5539: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI0NTUzNTkzMi0wZTI5LTQ0ZGYtOGQzYi1lY2M1OTc0YmNiNWQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1NDUsImlhdCI6MTcwMTE3Mzc0NSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzQ1fQ.CefYAqWLZ1W97ImXdSRStm26eB9Zq6Kq9awl98fc23oqguJ_I6rUi8ebG65K49XJeNq-S273gM_JVOk-nrsjBA", + 826: "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJmZjJjZDBmMy00ODA3LTQ2ZjYtYmQ4Yi1kY2Y5NzY5MDcyZTUiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzg1NTMsImlhdCI6MTcwMTE3Mzc1MywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczNzUzfQ.GPiEB_u71NNaiu8bh5x5QSXrk7kIAJ6e_RSQYxS6JSl_XUdy0VVbhEND5EzTJDrOSedV-uVPqRWcCT18dWx_DQ", + 21172: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxY2Q2OTI3NS1jMGFlLTQzMjEtYjZlYy04NGQxNzk2Y2Y4MzciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc4MTgsImlhdCI6MTcwMTE3MzAxOCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMDE4fQ.irr7e5MUIffqOS2oVRnBi0yT96qisEgngdwNdfIW7Z_9ldsmaqUEidHraBIfDwNP-U3LTVl1scqt4aKCgko6iuGqKxhyrRSdk0k_LaxdH0GgoUMbnmO80x2JMOwGQGm9BQAhzyqAMWBk70GL9GkAtblBUjFy2-9FY6V48O1UVLisQumLqs1PKKZU1KIvvWdzTFDPRj7Luhe0h9fqHi7Z7JXjH0q4c_s1QHMkvjOsIPHsz1qmoS8PFLGALq39Z2iqsF0ZSAbInmYLGB6tjqc-gmxZOdkdiDXb3NDC5Qx0shkqacY3vTi0CfcJqaChwlWVnLwoX-BmqRcGoTWmOzaMeg", + 13102: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjYzFjYTc0ZC05YmUwLTQ3ZjctYTg2MC1iYTI0ZTU5NDIzNDAiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc4ODEsImlhdCI6MTcwMTE3MzA4MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMDgxfQ.bbHZepFGjHJqBSSLdcGWQ9cqBAlUm_WUXyi_T3lWjXZWadP6fCMDRUn23I-fp0ZxjAiFJ8vpC9LugA75tn6sHLejiRztjZ6RLG6v7olu7V1oFLZPRfM9wn2X4MnYzaA1DI9Shy-Eo3-Sr1a3lV8Vv1i3Ts4m8oe6kFu1ehtZN8HKE0Qn2m6x8qcDtx4K-GMHoT6Q0DTmyg8d-56VgKPL5xyHq7AEDR4oG9LwCeOmCxH-WrrlZzaGnRRY28ew__VQ22eDtq3aFkBT3zsBasXW6bQmq406u86QeXxQogcXwvn8EboAcJTwMl0axvw2bZiRHx4EqG0SWUzxk15mwLNN0g", + 9478: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMWIyYWZmNC02N2UyLTQzNGEtYmI0ZS1iMjYyNWVlYWNiYjAiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc5MDcsImlhdCI6MTcwMTE3MzEwNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMTA3fQ.f1Litghr0k2CAUYVpe7ukuilCxQUgubqJoY2ovcFsMqGf2rA-nED3jXzx7cp9sjKghFmEq4ThTflcV2_ZK6IDNxGtNU_BO18e_dnPYZYkxkFJfz9fTvp9kkIcUYloDI8Z_LMKZvQhgsdphmQEiJd8v5BkAYc61J7Rj9Rr8bdZHyl1IDK3pqhNn8F7jQ7fNMhSgHq-5k_RZsQdG8OVZy9q_Ne5DLEs4E09Vo1xsFbd1471h5XjNVRMEOQysPdx_hs6pA81Z1YNN7NcWim7clvlO3xo9xEzQ_cCSneQ2cZxfLhDbMWgE0lnCKzWuq9pt8eDIMSguonjv7Yf3XmP9BMAg", + 20433: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ODUzNDY3OS01OGZiLTQzN2QtOTMwYS1mOGM0Zjg1M2Y5YWEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3Nzc5MTUsImlhdCI6MTcwMTE3MzExNSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTczMTE1fQ.j7gFMkIreU1x8MLVYBor1Q-zwAI81YlNnQMknAmjyB_4IrGI-_agcmcRG7xeOS9LiM9Eqvlt-HqY4NGnbJENRwqx-KejV29z0R0OP53kAgpsIACSYZKLMAw58yXmNAhddkpHltKColYAYj_TL67JHwSSb8wDZkKOTbwBJAbA4bbMkkodMyKs_udcSgsKph-yCf96LDKcW3R76lKhSL8hPPInQGoiU4VuOn_TNVs6wY5fO4Bhie4VOaOL4kAYy9ULwT_lyDfyOf-nyzChz7M_4qbOjpXKOd6QMIw_1h1yFf_5fCu9mPMfRmQ2tPY1oxRbeNdf8IB-ZyUvMYNuvZZLrw", + } + tokensByKid = []map[int]string{ + 22899: { + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MjI4OTksInR5cCI6IkpXVCJ9.eyJqdGkiOiJiZWI5MDI5Mi1jMDdmLTRkY2QtYjU4MS1jYWViYWI3Mzk3MzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODI5NzQsImlhdCI6MTcwMTE3ODE3NCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MTc0fQ.WJ6J97ITaWdE5XBmw530eIGc6KOaBjAuPJwf-XnuwlqFKfDRZ01GOapF4PaUojdgFHwGVpcnVp8-cSMujQARDw", + }, + 14211: { + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MTQyMTEsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ZTlkNWJmZC1lNjFiLTQzZmEtOTVlMC1jYmE5MWYwMDlkYTYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMjQsImlhdCI6MTcwMTE3ODIyNCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjI0fQ.g0ksruHmBp-74rk9Dm8pU39qw1Vfxou-xsVeIg-FSLm7gDWRWN-XovmkwAamrEYqbjSR7ANQK0ykNOSgSk-1Aw", + }, + 5539: { + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6NTUzOSwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZDg4OGNmZi1jMjBkLTRhZTktYjdlZi00OWQxNjUwOTA4MTQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMzYsImlhdCI6MTcwMTE3ODIzNiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjM2fQ.3kkEuku2iGyWcn2FS3evHcCIGHSm_2QHjS-u_bSSiSbmc7pvqJi1Wb7NIpwfivPxDH10GCfDjSsihEavJ7y8AA", + }, + 826: { + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6ODI2LCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIxNjU4ZGRhNi0xOGE3LTRlNWUtOWE2OC1lMWM1YzRjOGVhNzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwNTEsImlhdCI6MTcwMTE3ODI1MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjUxfQ.oe0_DlGIPJ0AjdzCB_u58VAb912YltxiBwgeu1JIEhHSycpKeS2DXKo_6spa7DL1z7hyLBS6LoRDV53B4EdzDg", + }, + 21172: { + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjExNzIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNDQzNjJhZS01MjZiLTRmZGQtOWM0Zi1jNmQ5ZWU2NzI4NjciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMDksImlhdCI6MTcwMTE3ODQwOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDA5fQ.X4Vzz7JpjO38okLU7wroL80UwAJi4gR42WdBZ3jWx-4hRYk36NjkN14kbUb9z_JtPftvLI008FQd4kriFh5dAHMlIcXnE0NUKaOIdd07aiLysUZ2i7ojr3WitZFawiPcsGWo8tkCNCR3Qz-0CqLvCfI30e6eKRuWwrkq0alVWbTZtU1M6vYnkBasRFO7X0-x0Bo3Kt64owgGLzwTun2sKKHXsIFU9s04ATEKNYuvGG5j6HMcM05QEXJPPTw68Wu7hHvgDFM9y3mbCFgPd2m8YTE5bjreTo5ZtNLob79JJsynUkj8ZTju9jP5c6YUP7pcVmuLfUziwh6J3KXCqCmmvg", + }, + 13102: { + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MTMxMDIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1YmY2ODU4Yi01NjcyLTQ4YjUtOWQzZS0yYmQ4NDU2ZDRlMzkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMjMsImlhdCI6MTcwMTE3ODQyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDIzfQ.NSwcvYPZC9WLZm-g1fN31SPIeHeEAvO9WgoliRRMYSF85UZQNTJV41oDi5TmVbLGYAVoIlglCL5guUZII4nVfphvA2DG_PFZLwWxzlGitSYWWQGj1ecO-Yv8lHz2LGJwYkka5wYpLh0Y7a3iFiKD0orqMC2QWlTlLQV-Vlm3PfnzEehAPHydHBRr2wLTe2e1KisCsd1yX-oET13Oty0ENZio9CdkBB8JO4GheKeTXRGzCmfpSqF_f-GrQxCYDoNgnS981RwOWmcQRzBbi3Dm-cUCO0Pryc0gK5oyvisvtkfFZoNliZtYmbckzPHInXVhccw_87dbRquIB_znFZpUtQ", + }, + 9478: { + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6OTQ3OCwidHlwIjoiSldUIn0.eyJqdGkiOiI2ZDc3MGE5Yi00MTA3LTQ0YzktYmExNy01ZTE1MzBkNDI3OWIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMzcsImlhdCI6MTcwMTE3ODQzNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDM3fQ.Fsxnu5w-B4q-xeWAmLYBzUl5sgrSCXGmLMeq9-fexkO8aj5HEpcSTpnX3sKKNbS93Zxj2tnPxArpUqUh03QjFa3rmVFlK_DSY5pAr3UUSTq7wYTG9Qnvf_cA_Att1qYIhEbrnRn1mn4FGsaBA0n_92fvQ0m6cUykhyieK0Tk3p5vQ1kXtiaMWH2Y6oW3F0yoNGDshmMdpa3m66TDgmQCDEH95phB5dRKxMqvTfjzxvu7JeBmzTcVfwvuZOaB8WX8HhrkXr2D2UyYmGrXpcY7eJ_rnOJMQPgCp2sOQkIS7yMkJWW4MY9scmOXTTYbmRMbWJ0eEiuS_4G6rem7wt8twA", + }, + 20433: { + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjA0MzMsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MjQ5ZDA0Ni00NGUzLTRjNmMtOTVkZC03Y2YwMTc0ZGNlZjMiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyNDgsImlhdCI6MTcwMTE3ODQ0OCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDQ4fQ.tVSKEOGIkMwPVC2XNc1Re3u2MJ1Lg3n6mv4xJgpYBG1CdPWnDE_rxH_FG2gDDU4O-vK_NJIBlhJM3m4k_GwKNbkGmUuONUoTul65yM_SbVmGXI04OgRErTuXSAAg1r8jQq-eVxWxUX4M0gZcM7c5s7YvS72GO8yYU2LzHIlGBAyliE4v6MosXHqSZTeeHdrToyjmdpRHEFPkUcxb2Oi1YDNDklOnmCDKjcdYcdi9TYvinQJZJYLCHBDlZYbw1KnOu_W-lefh-9k_rxiY6SEaITqxP3uNy3q3j_4-6O_McOqEVd-rvfBjbpmbV3vjkNdlI_UJW_gpP3o8RBtfsNjs1g", + }, + } + tokensByKidAll = []map[int]string{ + 0: { + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZjNiZTIwZS1jN2JmLTQwMjUtOWRlYS01M2I5ZWE3ZTFkNDEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMTAsImlhdCI6MTcwMTE3OTQxMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDEwfQ.o2Vc0FYGJoy01cy2LTINoyCvlca8zs3S_M7fop6D9TtpRdxIltRH3EGMc4GsHWq4p2AwKivACGMRr9zl6GOBCQ", + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiJjNGViNDhhOS01OTI3LTQ5NGQtYTE0MC0yZGE2MTU1ZTJjYzEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMjIsImlhdCI6MTcwMTE3OTQyMiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDIyfQ.mEsojatYxdke2Nvn9pncQ834mKT-m2B96mkuJ57RlojQr1Gh2Ewar_nyGlfMgxg1XpmJVR_w5MD_W_xdEdn-CQ", + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI1YzhmNzRhMy00Yzg1LTQzMTYtYTIwYi0zZGMzZGVmMmI5YWQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMzIsImlhdCI6MTcwMTE3OTQzMiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDMyfQ.z3EhNJTyptDvmpa6G55QtV0doag2UBheZayRtbRuC-7vQ6HpL1TRqyQlNGDjhA4v1EtULrkNYBsZyoSaHgFpBA", + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI0Y2YyMzcwMy04N2I2LTRlNDktODY2YS02YmE2ZTdhZTE4YzYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyNDMsImlhdCI6MTcwMTE3OTQ0MywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDQzfQ.pyNXs9gR73V4Dz5jf5nm2AUnYtpeXL_RTjaW3qO7A-9RebxUBShDJei_P9oSOchDGuJ4hDHtqS24vIlycqiLBw", + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiJiYTU0MDk3Yi02MDNmLTQ2YjMtYTY4Yi1iOWUxZWMzOTJiMDIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQwNzcsImlhdCI6MTcwMTE3OTI3NywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5Mjc3fQ.hiHmxckoBGFMj8rtU7TBSHeuuVjVYJn3T3ZSurFqCRCvcKNU-2CfnwBZEGIpXClObFKyHKIAMIM_at1HNG4rKSmV9-_go1nLT7r2pAAowRHUFieuhsVmZlEUaXvVYsBhqYkxXW-FatwbkTIrjYsbIxXqEpQCEbo1z35qGpsT-N3NiF9nTdfCSLLyy7lWDxHvcAN78vBNOhIbvh6ULsSkpXNugYithGVZ81iD9mo4Swi8cJbzgB-UjdZc_0DPqQjS2bkKALA3oA_FxMd3dk-gMqiKgUArL_WSr_A8gBSPOSi7o7Jgps_8AnQu4zIqjtG-IZSS_P9qy5XLOPdKSh0w9A", + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI1OGIzYmI5Ny02Y2I2LTQyM2YtYmViMC0xYWI2YzFkNjMzNzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMDAsImlhdCI6MTcwMTE3OTMwMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzAwfQ.MexEDN-eHYMcEFKik4cswbB1cT8K4s_CVBYw8LjLx9ZYizNG6217r5i52vYNjnw297GshGpWUuwci4midI-fFe6-QDTB_72B3s0y-bPSMgO-bevKq_IzbjqNXV5HaIvcj1flusUWD9h5ZxVXfdgu0YwoYwyhFfnHYlW4mwVDzXHObSTGuPiCgmlGAn8SzPP0mWd9ZXpB_8DWC3KB2SxrODVl9GwGeEdYXlsYls1Y8aJ_sf4adQn6Rd2uGWZbKRfxdO7hXfrZPatI4UvsJCYPyTeDG9Z3sh4w4U1dmNQV2lwW9FZxJy-010YvPAHq6xtoyCOrO5SCzCcYiZ16hrZatA", + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiIzMmIwY2VhNy1hN2NmLTRkNTEtYWNjOS04ZDAxYmZjN2Q0ODciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMTAsImlhdCI6MTcwMTE3OTMxMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzEwfQ.cHEYO0raxTWJJnymcm9KlLKJRQsoHd_NWcyYFO5iGE9H1mVtsVDTLuP-odwV6OpG2QkW1V9_neDjRuoxQFhlOrSKdt-iyVhfEns_yUOM69R0rf1SwMEE3O2J3Kwdjh6mC0Qr671QTsrbGySO0tmCpQsX-DZKYwr_zUbcRLlZ8Zz5Q8Z3rdSpTVM3w0K1Lfku_Zk9GuaDg5vd38hllIF-AKsmFvSxX-lfnDNf8SyN-zesLhCPmTaxuw9f3aRGaoRzbWyNjyg9L0lbZyzFJJxrGlKPnCUetmQ0lYbWQTEht4SVdJMX-GVq8j5sOk10Ez2r65POIVgMOMP_t7rtvYZFdA", + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI3YzQ5YjU5ZC01Y2NiLTRiMDktODQ2ZS04MjBkNTYxZDE3YzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMjMsImlhdCI6MTcwMTE3OTMyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzIzfQ.R1-wcS2pN0NUsYvF1V99HbBdvfnpKknWoBaKme_G3JjcP3mW4zKT91o7-0cuUtu3CcCmqLPhDw1w69J89zHifv3zmV_SVwjS3GzprsAdnCp7ELY2ngLbLRMvXK4T1z269SyFxYze6e3KYhNftk05DlyP13uRy_kY0PuDkrAoy4pWWZPRlHqhaG3RGf2HpRbKnCkC_7usAAZLYziLPDSYkkOPyk6hzOWAjYElRzrD5xFvc-V8QruYnkcvNLwHxGcrWVDFaaoMT0rYtH5nhPi2vHu3GrxxmRxvvdRL1NJX0yMJOpytvWMeFGI8wff2zY7-99tF_oysvPgF0rN4SgcxDQ", + }, + 22899: { + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MjI4OTksInR5cCI6IkpXVCJ9.eyJqdGkiOiJiZWI5MDI5Mi1jMDdmLTRkY2QtYjU4MS1jYWViYWI3Mzk3MzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODI5NzQsImlhdCI6MTcwMTE3ODE3NCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MTc0fQ.WJ6J97ITaWdE5XBmw530eIGc6KOaBjAuPJwf-XnuwlqFKfDRZ01GOapF4PaUojdgFHwGVpcnVp8-cSMujQARDw", + }, + 14211: { + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MTQyMTEsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ZTlkNWJmZC1lNjFiLTQzZmEtOTVlMC1jYmE5MWYwMDlkYTYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMjQsImlhdCI6MTcwMTE3ODIyNCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjI0fQ.g0ksruHmBp-74rk9Dm8pU39qw1Vfxou-xsVeIg-FSLm7gDWRWN-XovmkwAamrEYqbjSR7ANQK0ykNOSgSk-1Aw", + }, + 5539: { + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6NTUzOSwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZDg4OGNmZi1jMjBkLTRhZTktYjdlZi00OWQxNjUwOTA4MTQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMzYsImlhdCI6MTcwMTE3ODIzNiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjM2fQ.3kkEuku2iGyWcn2FS3evHcCIGHSm_2QHjS-u_bSSiSbmc7pvqJi1Wb7NIpwfivPxDH10GCfDjSsihEavJ7y8AA", + }, + 826: { + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6ODI2LCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIxNjU4ZGRhNi0xOGE3LTRlNWUtOWE2OC1lMWM1YzRjOGVhNzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwNTEsImlhdCI6MTcwMTE3ODI1MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjUxfQ.oe0_DlGIPJ0AjdzCB_u58VAb912YltxiBwgeu1JIEhHSycpKeS2DXKo_6spa7DL1z7hyLBS6LoRDV53B4EdzDg", + }, + 21172: { + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjExNzIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNDQzNjJhZS01MjZiLTRmZGQtOWM0Zi1jNmQ5ZWU2NzI4NjciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMDksImlhdCI6MTcwMTE3ODQwOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDA5fQ.X4Vzz7JpjO38okLU7wroL80UwAJi4gR42WdBZ3jWx-4hRYk36NjkN14kbUb9z_JtPftvLI008FQd4kriFh5dAHMlIcXnE0NUKaOIdd07aiLysUZ2i7ojr3WitZFawiPcsGWo8tkCNCR3Qz-0CqLvCfI30e6eKRuWwrkq0alVWbTZtU1M6vYnkBasRFO7X0-x0Bo3Kt64owgGLzwTun2sKKHXsIFU9s04ATEKNYuvGG5j6HMcM05QEXJPPTw68Wu7hHvgDFM9y3mbCFgPd2m8YTE5bjreTo5ZtNLob79JJsynUkj8ZTju9jP5c6YUP7pcVmuLfUziwh6J3KXCqCmmvg", + }, + 13102: { + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MTMxMDIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1YmY2ODU4Yi01NjcyLTQ4YjUtOWQzZS0yYmQ4NDU2ZDRlMzkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMjMsImlhdCI6MTcwMTE3ODQyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDIzfQ.NSwcvYPZC9WLZm-g1fN31SPIeHeEAvO9WgoliRRMYSF85UZQNTJV41oDi5TmVbLGYAVoIlglCL5guUZII4nVfphvA2DG_PFZLwWxzlGitSYWWQGj1ecO-Yv8lHz2LGJwYkka5wYpLh0Y7a3iFiKD0orqMC2QWlTlLQV-Vlm3PfnzEehAPHydHBRr2wLTe2e1KisCsd1yX-oET13Oty0ENZio9CdkBB8JO4GheKeTXRGzCmfpSqF_f-GrQxCYDoNgnS981RwOWmcQRzBbi3Dm-cUCO0Pryc0gK5oyvisvtkfFZoNliZtYmbckzPHInXVhccw_87dbRquIB_znFZpUtQ", + }, + 9478: { + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6OTQ3OCwidHlwIjoiSldUIn0.eyJqdGkiOiI2ZDc3MGE5Yi00MTA3LTQ0YzktYmExNy01ZTE1MzBkNDI3OWIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMzcsImlhdCI6MTcwMTE3ODQzNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDM3fQ.Fsxnu5w-B4q-xeWAmLYBzUl5sgrSCXGmLMeq9-fexkO8aj5HEpcSTpnX3sKKNbS93Zxj2tnPxArpUqUh03QjFa3rmVFlK_DSY5pAr3UUSTq7wYTG9Qnvf_cA_Att1qYIhEbrnRn1mn4FGsaBA0n_92fvQ0m6cUykhyieK0Tk3p5vQ1kXtiaMWH2Y6oW3F0yoNGDshmMdpa3m66TDgmQCDEH95phB5dRKxMqvTfjzxvu7JeBmzTcVfwvuZOaB8WX8HhrkXr2D2UyYmGrXpcY7eJ_rnOJMQPgCp2sOQkIS7yMkJWW4MY9scmOXTTYbmRMbWJ0eEiuS_4G6rem7wt8twA", + }, + 20433: { + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjA0MzMsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MjQ5ZDA0Ni00NGUzLTRjNmMtOTVkZC03Y2YwMTc0ZGNlZjMiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyNDgsImlhdCI6MTcwMTE3ODQ0OCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDQ4fQ.tVSKEOGIkMwPVC2XNc1Re3u2MJ1Lg3n6mv4xJgpYBG1CdPWnDE_rxH_FG2gDDU4O-vK_NJIBlhJM3m4k_GwKNbkGmUuONUoTul65yM_SbVmGXI04OgRErTuXSAAg1r8jQq-eVxWxUX4M0gZcM7c5s7YvS72GO8yYU2LzHIlGBAyliE4v6MosXHqSZTeeHdrToyjmdpRHEFPkUcxb2Oi1YDNDklOnmCDKjcdYcdi9TYvinQJZJYLCHBDlZYbw1KnOu_W-lefh-9k_rxiY6SEaITqxP3uNy3q3j_4-6O_McOqEVd-rvfBjbpmbV3vjkNdlI_UJW_gpP3o8RBtfsNjs1g", + }, + } + tokensEdByKid = []map[int]string{ + 0: { + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZjNiZTIwZS1jN2JmLTQwMjUtOWRlYS01M2I5ZWE3ZTFkNDEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMTAsImlhdCI6MTcwMTE3OTQxMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDEwfQ.o2Vc0FYGJoy01cy2LTINoyCvlca8zs3S_M7fop6D9TtpRdxIltRH3EGMc4GsHWq4p2AwKivACGMRr9zl6GOBCQ", + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiJjNGViNDhhOS01OTI3LTQ5NGQtYTE0MC0yZGE2MTU1ZTJjYzEiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMjIsImlhdCI6MTcwMTE3OTQyMiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDIyfQ.mEsojatYxdke2Nvn9pncQ834mKT-m2B96mkuJ57RlojQr1Gh2Ewar_nyGlfMgxg1XpmJVR_w5MD_W_xdEdn-CQ", + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI1YzhmNzRhMy00Yzg1LTQzMTYtYTIwYi0zZGMzZGVmMmI5YWQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyMzIsImlhdCI6MTcwMTE3OTQzMiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDMyfQ.z3EhNJTyptDvmpa6G55QtV0doag2UBheZayRtbRuC-7vQ6HpL1TRqyQlNGDjhA4v1EtULrkNYBsZyoSaHgFpBA", + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI0Y2YyMzcwMy04N2I2LTRlNDktODY2YS02YmE2ZTdhZTE4YzYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQyNDMsImlhdCI6MTcwMTE3OTQ0MywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5NDQzfQ.pyNXs9gR73V4Dz5jf5nm2AUnYtpeXL_RTjaW3qO7A-9RebxUBShDJei_P9oSOchDGuJ4hDHtqS24vIlycqiLBw", + }, + 22899: { + 22899: "eyJhbGciOiJFZERTQSIsImtpZCI6MjI4OTksInR5cCI6IkpXVCJ9.eyJqdGkiOiJiZWI5MDI5Mi1jMDdmLTRkY2QtYjU4MS1jYWViYWI3Mzk3MzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODI5NzQsImlhdCI6MTcwMTE3ODE3NCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MTc0fQ.WJ6J97ITaWdE5XBmw530eIGc6KOaBjAuPJwf-XnuwlqFKfDRZ01GOapF4PaUojdgFHwGVpcnVp8-cSMujQARDw", + }, + 14211: { + 14211: "eyJhbGciOiJFZERTQSIsImtpZCI6MTQyMTEsInR5cCI6IkpXVCJ9.eyJqdGkiOiI5ZTlkNWJmZC1lNjFiLTQzZmEtOTVlMC1jYmE5MWYwMDlkYTYiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMjQsImlhdCI6MTcwMTE3ODIyNCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjI0fQ.g0ksruHmBp-74rk9Dm8pU39qw1Vfxou-xsVeIg-FSLm7gDWRWN-XovmkwAamrEYqbjSR7ANQK0ykNOSgSk-1Aw", + }, + 5539: { + 5539: "eyJhbGciOiJFZERTQSIsImtpZCI6NTUzOSwidHlwIjoiSldUIn0.eyJqdGkiOiI0ZDg4OGNmZi1jMjBkLTRhZTktYjdlZi00OWQxNjUwOTA4MTQiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwMzYsImlhdCI6MTcwMTE3ODIzNiwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjM2fQ.3kkEuku2iGyWcn2FS3evHcCIGHSm_2QHjS-u_bSSiSbmc7pvqJi1Wb7NIpwfivPxDH10GCfDjSsihEavJ7y8AA", + }, + 826: { + 826: "eyJhbGciOiJFZERTQSIsImtpZCI6ODI2LCJ0eXAiOiJKV1QifQ.eyJqdGkiOiIxNjU4ZGRhNi0xOGE3LTRlNWUtOWE2OC1lMWM1YzRjOGVhNzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMwNTEsImlhdCI6MTcwMTE3ODI1MSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4MjUxfQ.oe0_DlGIPJ0AjdzCB_u58VAb912YltxiBwgeu1JIEhHSycpKeS2DXKo_6spa7DL1z7hyLBS6LoRDV53B4EdzDg", + }, + } + tokensRSAByKid = []map[int]string{ + 0: { + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiJiYTU0MDk3Yi02MDNmLTQ2YjMtYTY4Yi1iOWUxZWMzOTJiMDIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQwNzcsImlhdCI6MTcwMTE3OTI3NywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5Mjc3fQ.hiHmxckoBGFMj8rtU7TBSHeuuVjVYJn3T3ZSurFqCRCvcKNU-2CfnwBZEGIpXClObFKyHKIAMIM_at1HNG4rKSmV9-_go1nLT7r2pAAowRHUFieuhsVmZlEUaXvVYsBhqYkxXW-FatwbkTIrjYsbIxXqEpQCEbo1z35qGpsT-N3NiF9nTdfCSLLyy7lWDxHvcAN78vBNOhIbvh6ULsSkpXNugYithGVZ81iD9mo4Swi8cJbzgB-UjdZc_0DPqQjS2bkKALA3oA_FxMd3dk-gMqiKgUArL_WSr_A8gBSPOSi7o7Jgps_8AnQu4zIqjtG-IZSS_P9qy5XLOPdKSh0w9A", + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI1OGIzYmI5Ny02Y2I2LTQyM2YtYmViMC0xYWI2YzFkNjMzNzgiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMDAsImlhdCI6MTcwMTE3OTMwMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzAwfQ.MexEDN-eHYMcEFKik4cswbB1cT8K4s_CVBYw8LjLx9ZYizNG6217r5i52vYNjnw297GshGpWUuwci4midI-fFe6-QDTB_72B3s0y-bPSMgO-bevKq_IzbjqNXV5HaIvcj1flusUWD9h5ZxVXfdgu0YwoYwyhFfnHYlW4mwVDzXHObSTGuPiCgmlGAn8SzPP0mWd9ZXpB_8DWC3KB2SxrODVl9GwGeEdYXlsYls1Y8aJ_sf4adQn6Rd2uGWZbKRfxdO7hXfrZPatI4UvsJCYPyTeDG9Z3sh4w4U1dmNQV2lwW9FZxJy-010YvPAHq6xtoyCOrO5SCzCcYiZ16hrZatA", + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiIzMmIwY2VhNy1hN2NmLTRkNTEtYWNjOS04ZDAxYmZjN2Q0ODciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMTAsImlhdCI6MTcwMTE3OTMxMCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzEwfQ.cHEYO0raxTWJJnymcm9KlLKJRQsoHd_NWcyYFO5iGE9H1mVtsVDTLuP-odwV6OpG2QkW1V9_neDjRuoxQFhlOrSKdt-iyVhfEns_yUOM69R0rf1SwMEE3O2J3Kwdjh6mC0Qr671QTsrbGySO0tmCpQsX-DZKYwr_zUbcRLlZ8Zz5Q8Z3rdSpTVM3w0K1Lfku_Zk9GuaDg5vd38hllIF-AKsmFvSxX-lfnDNf8SyN-zesLhCPmTaxuw9f3aRGaoRzbWyNjyg9L0lbZyzFJJxrGlKPnCUetmQ0lYbWQTEht4SVdJMX-GVq8j5sOk10Ez2r65POIVgMOMP_t7rtvYZFdA", + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MCwidHlwIjoiSldUIn0.eyJqdGkiOiI3YzQ5YjU5ZC01Y2NiLTRiMDktODQ2ZS04MjBkNTYxZDE3YzIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODQxMjMsImlhdCI6MTcwMTE3OTMyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc5MzIzfQ.R1-wcS2pN0NUsYvF1V99HbBdvfnpKknWoBaKme_G3JjcP3mW4zKT91o7-0cuUtu3CcCmqLPhDw1w69J89zHifv3zmV_SVwjS3GzprsAdnCp7ELY2ngLbLRMvXK4T1z269SyFxYze6e3KYhNftk05DlyP13uRy_kY0PuDkrAoy4pWWZPRlHqhaG3RGf2HpRbKnCkC_7usAAZLYziLPDSYkkOPyk6hzOWAjYElRzrD5xFvc-V8QruYnkcvNLwHxGcrWVDFaaoMT0rYtH5nhPi2vHu3GrxxmRxvvdRL1NJX0yMJOpytvWMeFGI8wff2zY7-99tF_oysvPgF0rN4SgcxDQ", + }, + 21172: { + 21172: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjExNzIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNDQzNjJhZS01MjZiLTRmZGQtOWM0Zi1jNmQ5ZWU2NzI4NjciLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMDksImlhdCI6MTcwMTE3ODQwOSwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDA5fQ.X4Vzz7JpjO38okLU7wroL80UwAJi4gR42WdBZ3jWx-4hRYk36NjkN14kbUb9z_JtPftvLI008FQd4kriFh5dAHMlIcXnE0NUKaOIdd07aiLysUZ2i7ojr3WitZFawiPcsGWo8tkCNCR3Qz-0CqLvCfI30e6eKRuWwrkq0alVWbTZtU1M6vYnkBasRFO7X0-x0Bo3Kt64owgGLzwTun2sKKHXsIFU9s04ATEKNYuvGG5j6HMcM05QEXJPPTw68Wu7hHvgDFM9y3mbCFgPd2m8YTE5bjreTo5ZtNLob79JJsynUkj8ZTju9jP5c6YUP7pcVmuLfUziwh6J3KXCqCmmvg", + }, + 13102: { + 13102: "eyJhbGciOiJSUzI1NiIsImtpZCI6MTMxMDIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1YmY2ODU4Yi01NjcyLTQ4YjUtOWQzZS0yYmQ4NDU2ZDRlMzkiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMjMsImlhdCI6MTcwMTE3ODQyMywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDIzfQ.NSwcvYPZC9WLZm-g1fN31SPIeHeEAvO9WgoliRRMYSF85UZQNTJV41oDi5TmVbLGYAVoIlglCL5guUZII4nVfphvA2DG_PFZLwWxzlGitSYWWQGj1ecO-Yv8lHz2LGJwYkka5wYpLh0Y7a3iFiKD0orqMC2QWlTlLQV-Vlm3PfnzEehAPHydHBRr2wLTe2e1KisCsd1yX-oET13Oty0ENZio9CdkBB8JO4GheKeTXRGzCmfpSqF_f-GrQxCYDoNgnS981RwOWmcQRzBbi3Dm-cUCO0Pryc0gK5oyvisvtkfFZoNliZtYmbckzPHInXVhccw_87dbRquIB_znFZpUtQ", + }, + 9478: { + 9478: "eyJhbGciOiJSUzI1NiIsImtpZCI6OTQ3OCwidHlwIjoiSldUIn0.eyJqdGkiOiI2ZDc3MGE5Yi00MTA3LTQ0YzktYmExNy01ZTE1MzBkNDI3OWIiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyMzcsImlhdCI6MTcwMTE3ODQzNywibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDM3fQ.Fsxnu5w-B4q-xeWAmLYBzUl5sgrSCXGmLMeq9-fexkO8aj5HEpcSTpnX3sKKNbS93Zxj2tnPxArpUqUh03QjFa3rmVFlK_DSY5pAr3UUSTq7wYTG9Qnvf_cA_Att1qYIhEbrnRn1mn4FGsaBA0n_92fvQ0m6cUykhyieK0Tk3p5vQ1kXtiaMWH2Y6oW3F0yoNGDshmMdpa3m66TDgmQCDEH95phB5dRKxMqvTfjzxvu7JeBmzTcVfwvuZOaB8WX8HhrkXr2D2UyYmGrXpcY7eJ_rnOJMQPgCp2sOQkIS7yMkJWW4MY9scmOXTTYbmRMbWJ0eEiuS_4G6rem7wt8twA", + }, + 20433: { + 20433: "eyJhbGciOiJSUzI1NiIsImtpZCI6MjA0MzMsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MjQ5ZDA0Ni00NGUzLTRjNmMtOTVkZC03Y2YwMTc0ZGNlZjMiLCJzdWIiOiI3OGQyN2ViMS02Y2FiLTQ0ZGMtODc5Yi1jZTdlZTYxMzg1ZmUiLCJleHAiOjE3MDE3ODMyNDgsImlhdCI6MTcwMTE3ODQ0OCwibWVuZGVyLnRlbmFudCI6IjVhYmNiNmRlN2E2NzNhMDAwMTI4N2M3MSIsIm1lbmRlci51c2VyIjp0cnVlLCJpc3MiOiJtZW5kZXIudXNlcmFkbSIsInNjcCI6Im1lbmRlci4qIiwibmJmIjoxNzAxMTc4NDQ4fQ.tVSKEOGIkMwPVC2XNc1Re3u2MJ1Lg3n6mv4xJgpYBG1CdPWnDE_rxH_FG2gDDU4O-vK_NJIBlhJM3m4k_GwKNbkGmUuONUoTul65yM_SbVmGXI04OgRErTuXSAAg1r8jQq-eVxWxUX4M0gZcM7c5s7YvS72GO8yYU2LzHIlGBAyliE4v6MosXHqSZTeeHdrToyjmdpRHEFPkUcxb2Oi1YDNDklOnmCDKjcdYcdi9TYvinQJZJYLCHBDlZYbw1KnOu_W-lefh-9k_rxiY6SEaITqxP3uNy3q3j_4-6O_McOqEVd-rvfBjbpmbV3vjkNdlI_UJW_gpP3o8RBtfsNjs1g", + }, + } +) + +func TestUserAdmMultipleKeys(t *testing.T) { + testCases := map[string]struct { + inEmail model.Email + inPassword string + dbUser *model.User + keyIds []int + defaultKeyId int + }{ + "ok one key": { + inEmail: "foo@bar.com", + inPassword: "correcthorsebatterystaple", + + dbUser: &model.User{ + ID: oid.NewUUIDv5("1234").String(), + Email: "foo@bar.com", + Password: `$2a$10$wMW4kC6o1fY87DokgO.lDektJO7hBXydf4B.yIWmE8hR9jOiO8way`, + }, + keyIds: []int{21172}, + defaultKeyId: 13102, + }, + "ok all keys": { + inEmail: "foo@bar.com", + inPassword: "correcthorsebatterystaple", + + dbUser: &model.User{ + ID: oid.NewUUIDv5("1234").String(), + Email: "foo@bar.com", + Password: `$2a$10$wMW4kC6o1fY87DokgO.lDektJO7hBXydf4B.yIWmE8hR9jOiO8way`, + }, + keyIds: []int{ + 22899, + 14211, + 5539, + 826, + 21172, + 13102, + 9478, + 20433, + }, + defaultKeyId: 826, + }, + } + + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + ctx := context.Background() + + config := Config{ + Issuer: "mender-units", + ExpirationTimeSeconds: 32, + LimitSessionsPerUser: 10, + LimitTokensPerUser: 10, + TokenLastUsedUpdateFreqMinutes: 10, + PrivateKeyPath: "testdata/private.id." + strconv.Itoa(tc.keyIds[0]) + ".pem", + PrivateKeyFileNamePattern: "private\\.id\\.([0-9]*)\\.pem", + } + handlersByKeyId := make(map[int]jwt.Handler, len(tc.keyIds)+1) + for i := 0; i < len(tc.keyIds); i++ { + handlersByKeyId[tc.keyIds[i]], _ = jwt.NewJWTHandler( + "testdata/private.id."+strconv.Itoa(tc.keyIds[i])+".pem", + "private\\.id\\.([0-9]*)\\.pem", + ) + } + handlersByKeyId[common.KeyIdZero], _ = jwt.NewJWTHandler( + "testdata/private-"+strconv.Itoa(tc.defaultKeyId)+".pem", + "private\\.id\\.([0-9]*)\\.pem", + ) + + db := &mstore.DataStore{} + db.On("GetUserByEmail", ContextMatcher(), tc.inEmail).Return(tc.dbUser, nil) + db.On("GetUserById", ContextMatcher(), mock.AnythingOfType("string")).Return(tc.dbUser, nil) + + db.On("SaveToken", ContextMatcher(), mock.AnythingOfType("*jwt.Token")).Return(nil) + db.On("EnsureSessionTokensLimit", ContextMatcher(), mock.AnythingOfType("oid.ObjectID"), + mock.AnythingOfType("int")).Return(nil) + db.On("UpdateLoginTs", ContextMatcher(), tc.dbUser.ID). + Return(nil) + + useradm := NewUserAdm(handlersByKeyId, db, config) + cTenant := &mct.ClientRunner{} + cTenant.On("GetTenant", ContextMatcher(), string(tc.inEmail), &apiclient.HttpApi{}). + Return(&ct.Tenant{ + ID: "5abcb6de7a673a0001287c71", + Name: "tenant1", + Status: "active", + }, nil) + useradm = useradm.WithTenantVerification(cTenant) + loginToken, err := useradm.Login(ctx, tc.inEmail, tc.inPassword, &LoginOptions{}) + db.On("GetTokenById", ContextMatcher(), mock.AnythingOfType("oid.ObjectID")). + Return(loginToken, nil) + + signed, err := useradm.SignToken(ctx, loginToken) + + assert.NoError(t, err) + token, err := handlersByKeyId[tc.keyIds[0]].FromJWT(signed) + err = useradm.Verify(ctx, token) + assert.NoError(t, err) + + // the default key (as loaded from testdata/private-tc.defaultKeyId.pem above + // and assigned to KeyIdZero) is the one used to verify + // the tokens with no kid or with kid equal 0 + token, err = handlersByKeyId[0].FromJWT(tokensByKeyIdNoKid[tc.defaultKeyId]) + err = useradm.Verify(ctx, token) + assert.NoError(t, err) + + token, err = handlersByKeyId[0].FromJWT(tokensByKidAll[0][tc.defaultKeyId]) + err = useradm.Verify(ctx, token) + assert.NoError(t, err) + + for i := range tc.keyIds { + // all the tokens signed by given key and carrying the kid should be valid + token, err = handlersByKeyId[tc.keyIds[i]].FromJWT(tokensByKid[tc.keyIds[i]][tc.keyIds[i]]) + err = useradm.Verify(ctx, token) + assert.NoError(t, err) + } + + for i := range tc.keyIds { + for j := range tc.keyIds { + if i == j { + continue + } + // tokens signed by different keys cant verify + token, err = handlersByKeyId[tc.keyIds[i]].FromJWT(tokensByKid[tc.keyIds[j]][tc.keyIds[j]]) + err = useradm.Verify(ctx, token) + assert.Error(t, err) + } + } + + for i := range tc.keyIds { + // tokens with no kid are assumed to be signed by the default key hence cant verify + token, err = handlersByKeyId[tc.keyIds[i]].FromJWT(tokensByKeyIdNoKid[tc.keyIds[i]]) + err = useradm.Verify(ctx, token) + assert.Error(t, err) + } + }) + } +}