Skip to content

Commit

Permalink
oidc: add StaticKeySet
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ericchiang committed May 11, 2022
1 parent 3c48294 commit 5011cdf
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 28 deletions.
32 changes: 32 additions & 0 deletions oidc/jwks.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package oidc

import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"errors"
"fmt"
"io/ioutil"
Expand All @@ -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
Expand Down
1 change: 0 additions & 1 deletion oidc/jwks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
}
Expand Down
11 changes: 3 additions & 8 deletions oidc/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
24 changes: 5 additions & 19 deletions oidc/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,16 @@ package oidc

import (
"context"
"fmt"
"crypto"
"io"
"net/http"
"net/http/httptest"
"reflect"
"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{
{
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 5011cdf

Please sign in to comment.