fix: improve JWS and key verification (#128)
* fix: improve JWS and key verification * fix: get remote keys if no cached key matches * fix: get remote keys if no cached key matches * fix exactMatch * fix exactMatch * chore: change default branch name in .releaserc.js
This commit is contained in:
parent
2b5b436c41
commit
a63fbee93d
7 changed files with 453 additions and 32 deletions
|
@ -5,6 +5,7 @@ import (
|
|||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
@ -13,6 +14,11 @@ const (
|
|||
KeyUseSignature = "sig"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyMultiple = errors.New("multiple possible keys match")
|
||||
ErrKeyNone = errors.New("no possible keys matches")
|
||||
)
|
||||
|
||||
//KeySet represents a set of JSON Web Keys
|
||||
// - remotely fetch via discovery and jwks_uri -> `remoteKeySet`
|
||||
// - held by the OP itself in storage -> `openIDKeySet`
|
||||
|
@ -39,20 +45,38 @@ func GetKeyIDAndAlg(jws *jose.JSONWebSignature) (string, string) {
|
|||
//will return the key immediately if matches exact (id, usage, type)
|
||||
//
|
||||
//will return false none or multiple match
|
||||
//
|
||||
//deprecated: use FindMatchingKey which will return an error (more specific) instead of just a bool
|
||||
//moved implementation already to FindMatchingKey
|
||||
func FindKey(keyID, use, expectedAlg string, keys ...jose.JSONWebKey) (jose.JSONWebKey, bool) {
|
||||
key, err := FindMatchingKey(keyID, use, expectedAlg, keys...)
|
||||
return key, err == nil
|
||||
}
|
||||
|
||||
//FindMatchingKey searches the given JSON Web Keys for the requested key ID, usage and key type
|
||||
//
|
||||
//will return the key immediately if matches exact (id, usage, type)
|
||||
//
|
||||
//will return a specific error if none (ErrKeyNone) or multiple (ErrKeyMultiple) match
|
||||
func FindMatchingKey(keyID, use, expectedAlg string, keys ...jose.JSONWebKey) (key jose.JSONWebKey, err error) {
|
||||
var validKeys []jose.JSONWebKey
|
||||
for _, key := range keys {
|
||||
if key.KeyID == keyID && key.Use == use && algToKeyType(key.Key, expectedAlg) {
|
||||
if keyID != "" {
|
||||
return key, true
|
||||
for _, k := range keys {
|
||||
if k.Use == use && algToKeyType(k.Key, expectedAlg) {
|
||||
if k.KeyID == keyID && keyID != "" {
|
||||
return k, nil
|
||||
}
|
||||
if k.KeyID == "" || keyID == "" {
|
||||
validKeys = append(validKeys, k)
|
||||
}
|
||||
validKeys = append(validKeys, key)
|
||||
}
|
||||
}
|
||||
if len(validKeys) == 1 {
|
||||
return validKeys[0], true
|
||||
return validKeys[0], nil
|
||||
}
|
||||
return jose.JSONWebKey{}, false
|
||||
if len(validKeys) > 1 {
|
||||
return key, ErrKeyMultiple
|
||||
}
|
||||
return key, ErrKeyNone
|
||||
}
|
||||
|
||||
func algToKeyType(key interface{}, alg string) bool {
|
||||
|
|
319
pkg/oidc/keyset_test.go
Normal file
319
pkg/oidc/keyset_test.go
Normal file
|
@ -0,0 +1,319 @@
|
|||
package oidc
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
func TestFindKey(t *testing.T) {
|
||||
type args struct {
|
||||
keyID string
|
||||
use string
|
||||
expectedAlg string
|
||||
keys []jose.JSONWebKey
|
||||
}
|
||||
type res struct {
|
||||
key jose.JSONWebKey
|
||||
err error
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"no keys, ErrKeyNone",
|
||||
args{
|
||||
keyID: "",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: nil,
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{},
|
||||
err: ErrKeyNone,
|
||||
},
|
||||
},
|
||||
{
|
||||
"single key enc, ErrKeyNone",
|
||||
args{
|
||||
keyID: "",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "enc",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{},
|
||||
err: ErrKeyNone,
|
||||
},
|
||||
},
|
||||
{
|
||||
"single key wrong algorithm, ErrKeyNone",
|
||||
args{
|
||||
keyID: "",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
Key: &rsa.PrivateKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{},
|
||||
err: ErrKeyNone,
|
||||
},
|
||||
},
|
||||
{
|
||||
"single key no kid, no jwt kid, match",
|
||||
args{
|
||||
keyID: "",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
"single key kid, jwt no kid, match",
|
||||
args{
|
||||
keyID: "",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
KeyID: "id",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{
|
||||
Use: "sig",
|
||||
KeyID: "id",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
"single key no kid, jwt with kid, match",
|
||||
args{
|
||||
keyID: "id",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
"single key wrong kid, ErrKeyNone",
|
||||
args{
|
||||
keyID: "id",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
KeyID: "id2",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{},
|
||||
err: ErrKeyNone,
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple keys no kid, jwt no kid, ErrKeyMultiple",
|
||||
args{
|
||||
keyID: "",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{},
|
||||
err: ErrKeyMultiple,
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple keys with kid, jwt no kid, ErrKeyMultiple",
|
||||
args{
|
||||
keyID: "",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
KeyID: "id1",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
{
|
||||
Use: "sig",
|
||||
KeyID: "id2",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{},
|
||||
err: ErrKeyMultiple,
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple keys, single sig key, jwt no kid, match",
|
||||
args{
|
||||
keyID: "",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
{
|
||||
Use: "enc",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple keys no kid, jwt with kid, ErrKeyMultiple",
|
||||
args{
|
||||
keyID: "id",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{},
|
||||
err: ErrKeyMultiple,
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple keys with kid, jwt with kid, match",
|
||||
args{
|
||||
keyID: "id1",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
KeyID: "id1",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
{
|
||||
Use: "sig",
|
||||
KeyID: "id2",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{
|
||||
Use: "sig",
|
||||
KeyID: "id1",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
"multiple keys, single sig key, jwt with kid, match",
|
||||
args{
|
||||
keyID: "id1",
|
||||
use: KeyUseSignature,
|
||||
expectedAlg: "RS256",
|
||||
keys: []jose.JSONWebKey{
|
||||
{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
{
|
||||
Use: "enc",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
},
|
||||
},
|
||||
res{
|
||||
key: jose.JSONWebKey{
|
||||
Use: "sig",
|
||||
Key: &rsa.PublicKey{},
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := FindMatchingKey(tt.args.keyID, tt.args.use, tt.args.expectedAlg, tt.args.keys...)
|
||||
if (tt.res.err != nil && !errors.Is(err, tt.res.err)) || (tt.res.err == nil && err != nil) {
|
||||
t.Errorf("FindKey() error, got = %v, want = %v", err, tt.res.err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.res.key) {
|
||||
t.Errorf("FindKey() got = %v, want %v", got, tt.res.key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -39,6 +39,7 @@ var (
|
|||
ErrSignatureMultiple = errors.New("id_token contains multiple signatures")
|
||||
ErrSignatureUnsupportedAlg = errors.New("signature algorithm not supported")
|
||||
ErrSignatureInvalidPayload = errors.New("signature does not match Payload")
|
||||
ErrSignatureInvalid = errors.New("invalid signature")
|
||||
ErrExpired = errors.New("token has expired")
|
||||
ErrIatMissing = errors.New("issuedAt of token is missing")
|
||||
ErrIatInFuture = errors.New("issuedAt of token is in the future")
|
||||
|
@ -143,7 +144,7 @@ func CheckSignature(ctx context.Context, token string, payload []byte, claims Cl
|
|||
|
||||
signedPayload, err := set.VerifySignature(ctx, jws)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%w (%v)", ErrSignatureInvalid, err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(signedPayload, payload) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue