Skip to content

Commit 1e76606

Browse files
authored
Key rotation with VerificationKeySet (#344)
1 parent 1691aa9 commit 1e76606

File tree

3 files changed

+129
-10
lines changed

3 files changed

+129
-10
lines changed

parser.go

+26-10
Original file line numberDiff line numberDiff line change
@@ -74,24 +74,40 @@ func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyf
7474
}
7575
}
7676

77-
// Lookup key
78-
var key interface{}
77+
// Decode signature
78+
token.Signature, err = p.DecodeSegment(parts[2])
79+
if err != nil {
80+
return token, newError("could not base64 decode signature", ErrTokenMalformed, err)
81+
}
82+
text := strings.Join(parts[0:2], ".")
83+
84+
// Lookup key(s)
7985
if keyFunc == nil {
8086
// keyFunc was not provided. short circuiting validation
8187
return token, newError("no keyfunc was provided", ErrTokenUnverifiable)
8288
}
83-
if key, err = keyFunc(token); err != nil {
84-
return token, newError("error while executing keyfunc", ErrTokenUnverifiable, err)
85-
}
8689

87-
// Decode signature
88-
token.Signature, err = p.DecodeSegment(parts[2])
90+
got, err := keyFunc(token)
8991
if err != nil {
90-
return token, newError("could not base64 decode signature", ErrTokenMalformed, err)
92+
return token, newError("error while executing keyfunc", ErrTokenUnverifiable, err)
9193
}
9294

93-
// Perform signature validation
94-
if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil {
95+
switch have := got.(type) {
96+
case VerificationKeySet:
97+
if len(have.Keys) == 0 {
98+
return token, newError("keyfunc returned empty verification key set", ErrTokenUnverifiable)
99+
}
100+
// Iterate through keys and verify signature, skipping the rest when a match is found.
101+
// Return the last error if no match is found.
102+
for _, key := range have.Keys {
103+
if err = token.Method.Verify(text, token.Signature, key); err == nil {
104+
break
105+
}
106+
}
107+
default:
108+
err = token.Method.Verify(text, token.Signature, have)
109+
}
110+
if err != nil {
95111
return token, newError("", ErrTokenSignatureInvalid, err)
96112
}
97113

parser_test.go

+89
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ var (
2828
emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil }
2929
errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, errKeyFuncError }
3030
nilKeyFunc jwt.Keyfunc = nil
31+
multipleZeroKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return []interface{}{}, nil }
32+
multipleEmptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) {
33+
return jwt.VerificationKeySet{Keys: []jwt.VerificationKey{nil, nil}}, nil
34+
}
35+
multipleVerificationKeysFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) {
36+
return []jwt.VerificationKey{jwtTestDefaultKey, jwtTestEC256PublicKey}, nil
37+
}
38+
multipleLastKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) {
39+
return jwt.VerificationKeySet{Keys: []jwt.VerificationKey{jwtTestEC256PublicKey, jwtTestDefaultKey}}, nil
40+
}
41+
multipleFirstKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) {
42+
return jwt.VerificationKeySet{Keys: []jwt.VerificationKey{jwtTestDefaultKey, jwtTestEC256PublicKey}}, nil
43+
}
44+
multipleAltTypedKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) {
45+
return jwt.VerificationKeySet{Keys: []jwt.VerificationKey{jwtTestDefaultKey, jwtTestDefaultKey}}, nil
46+
}
47+
emptyVerificationKeySetFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) {
48+
return jwt.VerificationKeySet{}, nil
49+
}
3150
)
3251

3352
func init() {
@@ -94,6 +113,46 @@ var jwtTestData = []struct {
94113
nil,
95114
jwt.SigningMethodRS256,
96115
},
116+
{
117+
"multiple keys, last matches",
118+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
119+
multipleLastKeyFunc,
120+
jwt.MapClaims{"foo": "bar"},
121+
true,
122+
nil,
123+
nil,
124+
jwt.SigningMethodRS256,
125+
},
126+
{
127+
"multiple keys not []interface{} type, all match",
128+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
129+
multipleAltTypedKeyFunc,
130+
jwt.MapClaims{"foo": "bar"},
131+
true,
132+
nil,
133+
nil,
134+
jwt.SigningMethodRS256,
135+
},
136+
{
137+
"multiple keys, first matches",
138+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
139+
multipleFirstKeyFunc,
140+
jwt.MapClaims{"foo": "bar"},
141+
true,
142+
nil,
143+
nil,
144+
jwt.SigningMethodRS256,
145+
},
146+
{
147+
"public keys slice, not allowed",
148+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
149+
multipleVerificationKeysFunc,
150+
jwt.MapClaims{"foo": "bar"},
151+
false,
152+
[]error{jwt.ErrTokenSignatureInvalid},
153+
nil,
154+
jwt.SigningMethodRS256,
155+
},
97156
{
98157
"basic expired",
99158
"", // autogen
@@ -154,6 +213,36 @@ var jwtTestData = []struct {
154213
nil,
155214
jwt.SigningMethodRS256,
156215
},
216+
{
217+
"multiple nokey",
218+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
219+
multipleEmptyKeyFunc,
220+
jwt.MapClaims{"foo": "bar"},
221+
false,
222+
[]error{jwt.ErrTokenSignatureInvalid},
223+
nil,
224+
jwt.SigningMethodRS256,
225+
},
226+
{
227+
"empty verification key set",
228+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
229+
emptyVerificationKeySetFunc,
230+
jwt.MapClaims{"foo": "bar"},
231+
false,
232+
[]error{jwt.ErrTokenUnverifiable},
233+
nil,
234+
jwt.SigningMethodRS256,
235+
},
236+
{
237+
"zero length key list",
238+
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",
239+
multipleZeroKeyFunc,
240+
jwt.MapClaims{"foo": "bar"},
241+
false,
242+
[]error{jwt.ErrTokenSignatureInvalid},
243+
nil,
244+
jwt.SigningMethodRS256,
245+
},
157246
{
158247
"basic errorkey",
159248
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg",

token.go

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package jwt
22

33
import (
4+
"crypto"
45
"encoding/base64"
56
"encoding/json"
67
)
@@ -9,8 +10,21 @@ import (
910
// the key for verification. The function receives the parsed, but unverified
1011
// Token. This allows you to use properties in the Header of the token (such as
1112
// `kid`) to identify which key to use.
13+
//
14+
// The returned interface{} may be a single key or a PublicKeyset containing
15+
// multiple keys.
1216
type Keyfunc func(*Token) (interface{}, error)
1317

18+
// VerificationKey represents a public or secret key for verifying a token's signature.
19+
type VerificationKey interface {
20+
crypto.PublicKey | []uint8
21+
}
22+
23+
// VerificationKeySet is a set of public or secret keys. It is used by the parser to verify a token.
24+
type VerificationKeySet struct {
25+
Keys []VerificationKey
26+
}
27+
1428
// Token represents a JWT Token. Different fields will be used depending on
1529
// whether you're creating or parsing/verifying a token.
1630
type Token struct {

0 commit comments

Comments
 (0)