Repository: coreos/go-oidc
Pull request #337 (2022-05-11 17:09:34)

oidc: add StaticKeySet
This has been extremely useful for testing, but I've been hesitant to
add any API surface that references our underlying JOSE library.

Add a StaticKeySet that only accepts keys of type *rsa.PublicKey and
*ecdsa.PublicKey.


Patch:
diff --git a/oidc/jwks.go b/oidc/jwks.go
index a272b7a..6d2ffee 100644
--- a/oidc/jwks.go
+++ b/oidc/jwks.go
@@ -2,6 +2,9 @@ package oidc
 
 import (
 	"context"
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/rsa"
 	"errors"
 	"fmt"
 	"io/ioutil"
@@ -12,6 +15,35 @@ import (
 	jose "gopkg.in/square/go-jose.v2"
 )
 
+// StaticKeySet is a verifier that validates JWT against a static set of public keys.
+type StaticKeySet struct {
+	// PublicKeys used to verify the JWT. Supported types are *rsa.PublicKey and
+	// *ecdsa.PublicKey.
+	PublicKeys []crypto.PublicKey
+}
+
+// VerifySignature compares the signature against a static set of public keys.
+func (s *StaticKeySet) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
+	jws, err := jose.ParseSigned(jwt)
+	if err != nil {
+		return nil, fmt.Errorf("parsing jwt: %v", err)
+	}
+	for _, pub := range s.PublicKeys {
+		switch pub.(type) {
+		case *rsa.PublicKey:
+		case *ecdsa.PublicKey:
+		default:
+			return nil, fmt.Errorf("invalid public key type provided: %T", pub)
+		}
+		payload, err := jws.Verify(pub)
+		if err != nil {
+			continue
+		}
+		return payload, nil
+	}
+	return nil, fmt.Errorf("no public keys able to verify jwt")
+}
+
 // NewRemoteKeySet returns a KeySet that can validate JSON web tokens by using HTTP
 // GETs to fetch JSON web token sets hosted at a remote URL. This is automatically
 // used by NewProvider using the URLs returned by OpenID Connect discovery, but is
diff --git a/oidc/verify.go b/oidc/verify.go
index c2a9f48..3672058 100644
--- a/oidc/verify.go
+++ b/oidc/verify.go
@@ -55,15 +55,10 @@ type IDTokenVerifier struct {
 //		keySet := oidc.NewRemoteKeySet(ctx, "https://www.googleapis.com/oauth2/v3/certs")
 //		verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
 //
-// Since KeySet is an interface, this constructor can also be used to supply custom
-// public key sources. For example, if a user wanted to supply public keys out-of-band
-// and hold them statically in-memory:
+// Or a static key set (e.g. for testing):
 //
-//		// Custom KeySet implementation.
-//		keySet := newStatisKeySet(publicKeys...)
-//
-//		// Verifier uses the custom KeySet implementation.
-//		verifier := oidc.NewVerifier("https://auth.example.com", keySet, config)
+//		keySet := &oidc.StaticKeySet{PublicKeys: []crypto.PublicKey{pub1, pub2}}
+//		verifier := oidc.NewVerifier("https://accounts.google.com", keySet, config)
 //
 func NewVerifier(issuerURL string, keySet KeySet, config *Config) *IDTokenVerifier {
 	return &IDTokenVerifier{keySet: keySet, config: config, issuer: issuerURL}


Test patch:
diff --git a/oidc/jwks_test.go b/oidc/jwks_test.go
index 4103e4b..69b5560 100644
--- a/oidc/jwks_test.go
+++ b/oidc/jwks_test.go
@@ -58,7 +58,6 @@ func (s *signingKey) sign(t *testing.T, payload []byte) string {
 	return data
 }
 
-// jwk returns the public part of the signing key.
 func (s *signingKey) jwk() jose.JSONWebKey {
 	return jose.JSONWebKey{Key: s.pub, Use: "sig", Algorithm: string(s.alg), KeyID: s.keyID}
 }
diff --git a/oidc/verify_test.go b/oidc/verify_test.go
index d2fffa9..b8a13ea 100644
--- a/oidc/verify_test.go
+++ b/oidc/verify_test.go
@@ -2,7 +2,7 @@ package oidc
 
 import (
 	"context"
-	"fmt"
+	"crypto"
 	"io"
 	"net/http"
 	"net/http/httptest"
@@ -10,22 +10,8 @@ import (
 	"strconv"
 	"testing"
 	"time"
-
-	jose "gopkg.in/square/go-jose.v2"
 )
 
-type testVerifier struct {
-	jwk jose.JSONWebKey
-}
-
-func (t *testVerifier) VerifySignature(ctx context.Context, jwt string) ([]byte, error) {
-	jws, err := jose.ParseSigned(jwt)
-	if err != nil {
-		return nil, fmt.Errorf("oidc: malformed jwt: %v", err)
-	}
-	return jws.Verify(&t.jwk)
-}
-
 func TestVerify(t *testing.T) {
 	tests := []verificationTest{
 		{
@@ -513,9 +499,9 @@ func (v resolverTest) testEndpoint(t *testing.T) ([]byte, error) {
 	issuer := v.issuer
 	var ks KeySet
 	if v.verificationKey == nil {
-		ks = &testVerifier{v.signKey.jwk()}
+		ks = &StaticKeySet{PublicKeys: []crypto.PublicKey{v.signKey.pub}}
 	} else {
-		ks = &testVerifier{v.verificationKey.jwk()}
+		ks = &StaticKeySet{PublicKeys: []crypto.PublicKey{v.verificationKey.pub}}
 	}
 	verifier := NewVerifier(issuer, ks, &v.config)
 
@@ -560,9 +546,9 @@ func (v verificationTest) runGetToken(t *testing.T) (*IDToken, error) {
 	}
 	var ks KeySet
 	if v.verificationKey == nil {
-		ks = &testVerifier{v.signKey.jwk()}
+		ks = &StaticKeySet{PublicKeys: []crypto.PublicKey{v.signKey.pub}}
 	} else {
-		ks = &testVerifier{v.verificationKey.jwk()}
+		ks = &StaticKeySet{PublicKeys: []crypto.PublicKey{v.verificationKey.pub}}
 	}
 	verifier := NewVerifier(issuer, ks, &v.config)