Passing signing key through to the token endpoint

This commit is contained in:
Margo Crawford 2020-12-03 17:16:08 -08:00
parent 58237d0e7d
commit 0bb2b10b3b
5 changed files with 44 additions and 20 deletions

View File

@ -5,6 +5,9 @@ package oidc
import ( import (
"context" "context"
"crypto/ecdsa"
"go.pinniped.dev/internal/constable"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/compose" "github.com/ory/fosite/compose"
@ -15,7 +18,6 @@ import (
// TODO: doc me. // TODO: doc me.
type dynamicOpenIDConnectECDSAStrategy struct { type dynamicOpenIDConnectECDSAStrategy struct {
issuer string
fositeConfig *compose.Config fositeConfig *compose.Config
jwksProvider jwks.DynamicJWKSProvider jwksProvider jwks.DynamicJWKSProvider
} }
@ -23,12 +25,10 @@ type dynamicOpenIDConnectECDSAStrategy struct {
var _ openid.OpenIDConnectTokenStrategy = &dynamicOpenIDConnectECDSAStrategy{} var _ openid.OpenIDConnectTokenStrategy = &dynamicOpenIDConnectECDSAStrategy{}
func newDynamicOpenIDConnectECDSAStrategy( func newDynamicOpenIDConnectECDSAStrategy(
issuer string,
fositeConfig *compose.Config, fositeConfig *compose.Config,
jwksProvider jwks.DynamicJWKSProvider, jwksProvider jwks.DynamicJWKSProvider,
) *dynamicOpenIDConnectECDSAStrategy { ) *dynamicOpenIDConnectECDSAStrategy {
return &dynamicOpenIDConnectECDSAStrategy{ return &dynamicOpenIDConnectECDSAStrategy{
issuer: issuer,
fositeConfig: fositeConfig, fositeConfig: fositeConfig,
jwksProvider: jwksProvider, jwksProvider: jwksProvider,
} }
@ -38,5 +38,15 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
ctx context.Context, ctx context.Context,
requester fosite.Requester, requester fosite.Requester,
) (string, error) { ) (string, error) {
return "", nil _, activeJwk := s.jwksProvider.GetJWKS(s.fositeConfig.IDTokenIssuer)
if activeJwk == nil {
return "", constable.Error("No JWK found for issuer")
}
key, ok := activeJwk.Key.(*ecdsa.PrivateKey)
if !ok {
return "", constable.Error("JWK must be of type ecdsa")
}
// todo write story/issue about caching this strategy
return compose.NewOpenIDConnectECDSAStrategy(s.fositeConfig, key).GenerateIDToken(ctx, requester)
} }

View File

@ -16,15 +16,20 @@ import (
coreosoidc "github.com/coreos/go-oidc" coreosoidc "github.com/coreos/go-oidc"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/compose" "github.com/ory/fosite/compose"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.pinniped.dev/internal/oidc/jwks"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
"go.pinniped.dev/internal/oidc/jwks"
) )
func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) { func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
const ( const (
goodIssuer = "https://some-good-issuer.com" goodIssuer = "https://some-good-issuer.com"
clientID = "some-client-id" clientID = "some-client-id"
goodSubject = "some-subject"
goodUsername = "some-username"
) )
ecPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) ecPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
@ -60,7 +65,7 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
{ {
name: "jwks provider does not contain signing key for issuer", name: "jwks provider does not contain signing key for issuer",
issuer: goodIssuer, issuer: goodIssuer,
wantError: "some unkonwn key error", wantError: "No JWK found for issuer",
}, },
{ {
name: "jwks provider contains signing key of wrong type for issuer", name: "jwks provider contains signing key of wrong type for issuer",
@ -75,7 +80,7 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
}, },
) )
}, },
wantError: "some invalid key type error", wantError: "JWK must be of type ecdsa",
}, },
} }
for _, test := range tests { for _, test := range tests {
@ -85,13 +90,22 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
if test.jwksProvider != nil { if test.jwksProvider != nil {
test.jwksProvider(jwksProvider) test.jwksProvider(jwksProvider)
} }
s := newDynamicOpenIDConnectECDSAStrategy(test.issuer, &compose.Config{}, jwksProvider) s := newDynamicOpenIDConnectECDSAStrategy(
&compose.Config{IDTokenIssuer: test.issuer},
jwksProvider,
)
requester := &fosite.Request{ requester := &fosite.Request{
Client: &fosite.DefaultClient{ Client: &fosite.DefaultClient{
ID: clientID, ID: clientID,
}, },
// Session: fositeopenid.DefaultSession{}, Session: &openid.DefaultSession{
Claims: &jwt.IDTokenClaims{
Subject: goodSubject,
},
Subject: goodSubject,
Username: goodUsername,
},
} }
idToken, err := s.GenerateIDToken(context.Background(), requester) idToken, err := s.GenerateIDToken(context.Background(), requester)
if test.wantError != "" { if test.wantError != "" {

View File

@ -122,7 +122,7 @@ func FositeOauth2Helper(
&compose.CommonStrategy{ &compose.CommonStrategy{
// Note that Fosite requires the HMAC secret to be at least 32 bytes. // Note that Fosite requires the HMAC secret to be at least 32 bytes.
CoreStrategy: compose.NewOAuth2HMACStrategy(oauthConfig, hmacSecretOfLengthAtLeast32, nil), CoreStrategy: compose.NewOAuth2HMACStrategy(oauthConfig, hmacSecretOfLengthAtLeast32, nil),
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(issuer, oauthConfig, jwksProvider), OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(oauthConfig, jwksProvider),
// OpenIDConnectTokenStrategy: compose.NewOpenIDConnectECDSAStrategy(oauthConfig, jwtSigningKey), // OpenIDConnectTokenStrategy: compose.NewOpenIDConnectECDSAStrategy(oauthConfig, jwtSigningKey),
}, },
nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets. nil, // hasher, defaults to using BCrypt when nil. Used for hashing client secrets.

View File

@ -82,7 +82,7 @@ func (m *Manager) SetProviders(oidcProviders ...*provider.OIDCProvider) {
oauthHelperWithNullStorage := oidc.FositeOauth2Helper(oidc.NullStorage{}, issuer, fositeHMACSecretForThisProvider, nil) oauthHelperWithNullStorage := oidc.FositeOauth2Helper(oidc.NullStorage{}, issuer, fositeHMACSecretForThisProvider, nil)
// For all the other endpoints, make another oauth helper with exactly the same settings except use real storage. // For all the other endpoints, make another oauth helper with exactly the same settings except use real storage.
oauthHelperWithKubeStorage := oidc.FositeOauth2Helper(oidc.NewKubeStorage(m.secretsClient), issuer, fositeHMACSecretForThisProvider, nil) oauthHelperWithKubeStorage := oidc.FositeOauth2Helper(oidc.NewKubeStorage(m.secretsClient), issuer, fositeHMACSecretForThisProvider, m.dynamicJWKSProvider)
// TODO use different codecs for the state and the cookie, because: // TODO use different codecs for the state and the cookie, because:
// 1. we would like to state to have an embedded expiration date while the cookie does not need that // 1. we would like to state to have an embedded expiration date while the cookie does not need that

View File

@ -159,7 +159,7 @@ func TestManager(t *testing.T) {
return actualLocationQueryParams.Get("code") return actualLocationQueryParams.Get("code")
} }
requireTokenRequestToBeHandled := func(requestIssuer, authCode string, jwks *jose.JSONWebKeySet) { requireTokenRequestToBeHandled := func(requestIssuer, authCode string, jwks *jose.JSONWebKeySet, jwkIssuer string) {
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
numberOfKubeActionsBeforeThisRequest := len(kubeClient.Actions()) numberOfKubeActionsBeforeThisRequest := len(kubeClient.Actions())
@ -194,7 +194,7 @@ func TestManager(t *testing.T) {
keySet := newStaticKeySet(privateKey.Public()) keySet := newStaticKeySet(privateKey.Public())
verifyConfig := coreosoidc.Config{ClientID: downstreamClientID, SupportedSigningAlgs: []string{coreosoidc.ES256}} verifyConfig := coreosoidc.Config{ClientID: downstreamClientID, SupportedSigningAlgs: []string{coreosoidc.ES256}}
verifier := coreosoidc.NewVerifier(requestIssuer, keySet, &verifyConfig) verifier := coreosoidc.NewVerifier(jwkIssuer, keySet, &verifyConfig)
_, err := verifier.Verify(context.Background(), idToken) _, err := verifier.Verify(context.Background(), idToken)
r.NoError(err) r.NoError(err)
@ -326,16 +326,16 @@ func TestManager(t *testing.T) {
downstreamAuthCode1 := requireCallbackRequestToBeHandled(issuer1, callbackRequestParams, csrfCookieValue) downstreamAuthCode1 := requireCallbackRequestToBeHandled(issuer1, callbackRequestParams, csrfCookieValue)
downstreamAuthCode2 := requireCallbackRequestToBeHandled(issuer2, callbackRequestParams, csrfCookieValue) downstreamAuthCode2 := requireCallbackRequestToBeHandled(issuer2, callbackRequestParams, csrfCookieValue)
// // Hostnames are case-insensitive, so test that we can handle that. // Hostnames are case-insensitive, so test that we can handle that.
downstreamAuthCode3 := requireCallbackRequestToBeHandled(issuer1DifferentCaseHostname, callbackRequestParams, csrfCookieValue) downstreamAuthCode3 := requireCallbackRequestToBeHandled(issuer1DifferentCaseHostname, callbackRequestParams, csrfCookieValue)
downstreamAuthCode4 := requireCallbackRequestToBeHandled(issuer2DifferentCaseHostname, callbackRequestParams, csrfCookieValue) downstreamAuthCode4 := requireCallbackRequestToBeHandled(issuer2DifferentCaseHostname, callbackRequestParams, csrfCookieValue)
requireTokenRequestToBeHandled(issuer1, downstreamAuthCode1, issuer1JWKS) requireTokenRequestToBeHandled(issuer1, downstreamAuthCode1, issuer1JWKS, issuer1)
requireTokenRequestToBeHandled(issuer2, downstreamAuthCode2, issuer2JWKS) requireTokenRequestToBeHandled(issuer2, downstreamAuthCode2, issuer2JWKS, issuer2)
// Hostnames are case-insensitive, so test that we can handle that. // Hostnames are case-insensitive, so test that we can handle that.
requireTokenRequestToBeHandled(issuer1DifferentCaseHostname, downstreamAuthCode3, issuer1JWKS) requireTokenRequestToBeHandled(issuer1DifferentCaseHostname, downstreamAuthCode3, issuer1JWKS, issuer1)
requireTokenRequestToBeHandled(issuer2DifferentCaseHostname, downstreamAuthCode4, issuer2JWKS) requireTokenRequestToBeHandled(issuer2DifferentCaseHostname, downstreamAuthCode4, issuer2JWKS, issuer2)
} }
when("given some valid providers via SetProviders()", func() { when("given some valid providers via SetProviders()", func() {