1
Fork 0

Attempt all keys when validating stateless tokens.

This commit is contained in:
Juliusz Chroboczek 2024-05-09 18:40:14 +02:00
parent f3ceb05033
commit 3a6d924374
3 changed files with 19 additions and 17 deletions

View File

@ -19,6 +19,8 @@ Galene 0.9 (unreleased)
* Added a new command "/stopshare". * Added a new command "/stopshare".
* Added a new permission "message" and new commands "shutup" and "unshutup". * Added a new permission "message" and new commands "shutup" and "unshutup".
* Fixed a bug that could allow an ordinary user to clear the chat. * Fixed a bug that could allow an ordinary user to clear the chat.
* Changed stateless token validation to attempt all keys rather than
just the first matching one.
14 April 2024: Galene 0.8.2 14 April 2024: Galene 0.8.2

7
README
View File

@ -309,21 +309,18 @@ specify either an authorisation server or an authorisation portal.
"kty": "oct", "kty": "oct",
"alg": "HS256", "alg": "HS256",
"k": "MYz3IfCq4Yq-UmPdNqWEOdPl4C_m9imHHs9uveDUJGQ", "k": "MYz3IfCq4Yq-UmPdNqWEOdPl4C_m9imHHs9uveDUJGQ",
"kid": "20211030"
}, { }, {
"kty": "EC", "kty": "EC",
"alg": "ES256", "alg": "ES256",
"crv": "P-256", "crv": "P-256",
"x": "dElK9qBNyCpRXdvJsn4GdjrFzScSzpkz_I0JhKbYC88", "x": "dElK9qBNyCpRXdvJsn4GdjrFzScSzpkz_I0JhKbYC88",
"y": "pBhVb37haKvwEoleoW3qxnT4y5bK35_RTP7_RmFKR6Q", "y": "pBhVb37haKvwEoleoW3qxnT4y5bK35_RTP7_RmFKR6Q",
"kid": "20211101"
}] }]
"authServer": "https://auth.example.org", "authServer": "https://auth.example.org",
} }
The `kid` field serves to distinguish among multiple keys, and must match If multiple keys are provided, then they will all be tried in turn (the
the value provided by the authorisation server. If the server doesn't kid field, if provided, is ignored).
provide a `kid`, the first key with a matching `alg` field will be used.
If an authorisation server is specified, then the default client, after it If an authorisation server is specified, then the default client, after it
prompts for a password, will request a token from the authorisation server prompts for a password, will request a token from the authorisation server

View File

@ -28,7 +28,7 @@ func parseBase64(k string, d map[string]interface{}) ([]byte, error) {
return vv, nil return vv, nil
} }
func ParseKey(key map[string]interface{}) (interface{}, error) { func ParseKey(key map[string]any) (any, error) {
kty, ok := key["kty"].(string) kty, ok := key["kty"].(string)
if !ok { if !ok {
return nil, errors.New("kty not found") return nil, errors.New("kty not found")
@ -96,17 +96,16 @@ func ParseKey(key map[string]interface{}) (interface{}, error) {
} }
} }
func getKey(header map[string]interface{}, keys []map[string]interface{}) (interface{}, error) { func ParseKeys(keys []map[string]any) ([]jwt.VerificationKey, error) {
alg, _ := header["alg"].(string) ks := make([]jwt.VerificationKey, len(keys))
kid, _ := header["kid"].(string) for i, ky := range keys {
for _, k := range keys { k, err := ParseKey(ky)
kid2, _ := k["kid"].(string) if err != nil {
alg2, _ := k["alg"].(string) return nil, err
if (kid == "" || kid == kid2) && alg == alg2 {
return ParseKey(k)
} }
ks[i] = k
} }
return nil, errors.New("key not found") return ks, nil
} }
func toStringArray(a interface{}) ([]string, bool) { func toStringArray(a interface{}) ([]string, bool) {
@ -128,11 +127,15 @@ func toStringArray(a interface{}) ([]string, bool) {
// parseJWT tries to parse a string as a JWT. // parseJWT tries to parse a string as a JWT.
// It returns (nil, nil) if the string does not look like a JWT. // It returns (nil, nil) if the string does not look like a JWT.
func parseJWT(token string, keys []map[string]interface{}) (*JWT, error) { func parseJWT(token string, keys []map[string]any) (*JWT, error) {
t, err := jwt.Parse( t, err := jwt.Parse(
token, token,
func(t *jwt.Token) (interface{}, error) { func(t *jwt.Token) (interface{}, error) {
return getKey(t.Header, keys) ks, err := ParseKeys(keys)
if err != nil {
return nil, err
}
return jwt.VerificationKeySet{Keys: ks}, nil
}, },
jwt.WithExpirationRequired(), jwt.WithExpirationRequired(),
jwt.WithIssuedAt(), jwt.WithIssuedAt(),