diff --git a/internal/oidc/dynamic_open_id_connect_ecdsa_strategy.go b/internal/oidc/dynamic_open_id_connect_ecdsa_strategy.go index 6d7d837f..00261379 100644 --- a/internal/oidc/dynamic_open_id_connect_ecdsa_strategy.go +++ b/internal/oidc/dynamic_open_id_connect_ecdsa_strategy.go @@ -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) } diff --git a/internal/oidc/dynamic_open_id_connect_ecdsa_strategy_test.go b/internal/oidc/dynamic_open_id_connect_ecdsa_strategy_test.go index 93ba46cc..a35bfa13 100644 --- a/internal/oidc/dynamic_open_id_connect_ecdsa_strategy_test.go +++ b/internal/oidc/dynamic_open_id_connect_ecdsa_strategy_test.go @@ -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 != "" { diff --git a/internal/oidc/oidc.go b/internal/oidc/oidc.go index c5e6abf0..d38a6495 100644 --- a/internal/oidc/oidc.go +++ b/internal/oidc/oidc.go @@ -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. diff --git a/internal/oidc/provider/manager/manager.go b/internal/oidc/provider/manager/manager.go index b1d9d6b5..7e8cd6d7 100644 --- a/internal/oidc/provider/manager/manager.go +++ b/internal/oidc/provider/manager/manager.go @@ -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 diff --git a/internal/oidc/provider/manager/manager_test.go b/internal/oidc/provider/manager/manager_test.go index d3cf1cc9..465b3c10 100644 --- a/internal/oidc/provider/manager/manager_test.go +++ b/internal/oidc/provider/manager/manager_test.go @@ -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() {