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 (
"context"
"crypto/ecdsa"
"go.pinniped.dev/internal/constable"
"github.com/ory/fosite"
"github.com/ory/fosite/compose"
@ -15,7 +18,6 @@ import (
// TODO: doc me.
type dynamicOpenIDConnectECDSAStrategy struct {
issuer string
fositeConfig *compose.Config
jwksProvider jwks.DynamicJWKSProvider
}
@ -23,12 +25,10 @@ type dynamicOpenIDConnectECDSAStrategy struct {
var _ openid.OpenIDConnectTokenStrategy = &dynamicOpenIDConnectECDSAStrategy{}
func newDynamicOpenIDConnectECDSAStrategy(
issuer string,
fositeConfig *compose.Config,
jwksProvider jwks.DynamicJWKSProvider,
) *dynamicOpenIDConnectECDSAStrategy {
return &dynamicOpenIDConnectECDSAStrategy{
issuer: issuer,
fositeConfig: fositeConfig,
jwksProvider: jwksProvider,
}
@ -38,5 +38,15 @@ func (s *dynamicOpenIDConnectECDSAStrategy) GenerateIDToken(
ctx context.Context,
requester fosite.Requester,
) (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"
"github.com/ory/fosite"
"github.com/ory/fosite/compose"
"github.com/ory/fosite/handler/openid"
"github.com/ory/fosite/token/jwt"
"github.com/stretchr/testify/require"
"go.pinniped.dev/internal/oidc/jwks"
"gopkg.in/square/go-jose.v2"
"go.pinniped.dev/internal/oidc/jwks"
)
func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
const (
goodIssuer = "https://some-good-issuer.com"
clientID = "some-client-id"
goodIssuer = "https://some-good-issuer.com"
clientID = "some-client-id"
goodSubject = "some-subject"
goodUsername = "some-username"
)
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",
issuer: goodIssuer,
wantError: "some unkonwn key error",
wantError: "No JWK found 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 {
@ -85,13 +90,22 @@ func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
if test.jwksProvider != nil {
test.jwksProvider(jwksProvider)
}
s := newDynamicOpenIDConnectECDSAStrategy(test.issuer, &compose.Config{}, jwksProvider)
s := newDynamicOpenIDConnectECDSAStrategy(
&compose.Config{IDTokenIssuer: test.issuer},
jwksProvider,
)
requester := &fosite.Request{
Client: &fosite.DefaultClient{
ID: clientID,
},
// Session: fositeopenid.DefaultSession{},
Session: &openid.DefaultSession{
Claims: &jwt.IDTokenClaims{
Subject: goodSubject,
},
Subject: goodSubject,
Username: goodUsername,
},
}
idToken, err := s.GenerateIDToken(context.Background(), requester)
if test.wantError != "" {

View File

@ -122,7 +122,7 @@ func FositeOauth2Helper(
&compose.CommonStrategy{
// Note that Fosite requires the HMAC secret to be at least 32 bytes.
CoreStrategy: compose.NewOAuth2HMACStrategy(oauthConfig, hmacSecretOfLengthAtLeast32, nil),
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(issuer, oauthConfig, jwksProvider),
OpenIDConnectTokenStrategy: newDynamicOpenIDConnectECDSAStrategy(oauthConfig, jwksProvider),
// OpenIDConnectTokenStrategy: compose.NewOpenIDConnectECDSAStrategy(oauthConfig, jwtSigningKey),
},
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)
// 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:
// 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")
}
requireTokenRequestToBeHandled := func(requestIssuer, authCode string, jwks *jose.JSONWebKeySet) {
requireTokenRequestToBeHandled := func(requestIssuer, authCode string, jwks *jose.JSONWebKeySet, jwkIssuer string) {
recorder := httptest.NewRecorder()
numberOfKubeActionsBeforeThisRequest := len(kubeClient.Actions())
@ -194,7 +194,7 @@ func TestManager(t *testing.T) {
keySet := newStaticKeySet(privateKey.Public())
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)
r.NoError(err)
@ -326,16 +326,16 @@ func TestManager(t *testing.T) {
downstreamAuthCode1 := requireCallbackRequestToBeHandled(issuer1, 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)
downstreamAuthCode4 := requireCallbackRequestToBeHandled(issuer2DifferentCaseHostname, callbackRequestParams, csrfCookieValue)
requireTokenRequestToBeHandled(issuer1, downstreamAuthCode1, issuer1JWKS)
requireTokenRequestToBeHandled(issuer2, downstreamAuthCode2, issuer2JWKS)
requireTokenRequestToBeHandled(issuer1, downstreamAuthCode1, issuer1JWKS, issuer1)
requireTokenRequestToBeHandled(issuer2, downstreamAuthCode2, issuer2JWKS, issuer2)
// Hostnames are case-insensitive, so test that we can handle that.
requireTokenRequestToBeHandled(issuer1DifferentCaseHostname, downstreamAuthCode3, issuer1JWKS)
requireTokenRequestToBeHandled(issuer2DifferentCaseHostname, downstreamAuthCode4, issuer2JWKS)
requireTokenRequestToBeHandled(issuer1DifferentCaseHostname, downstreamAuthCode3, issuer1JWKS, issuer1)
requireTokenRequestToBeHandled(issuer2DifferentCaseHostname, downstreamAuthCode4, issuer2JWKS, issuer2)
}
when("given some valid providers via SetProviders()", func() {