2021-04-09 00:28:01 +00:00
|
|
|
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
2020-12-03 20:34:58 +00:00
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package oidc
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rsa"
|
2020-12-07 19:53:24 +00:00
|
|
|
"errors"
|
2020-12-04 15:06:55 +00:00
|
|
|
"net/url"
|
2020-12-03 20:34:58 +00:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/ory/fosite"
|
|
|
|
"github.com/ory/fosite/compose"
|
2020-12-04 01:16:08 +00:00
|
|
|
"github.com/ory/fosite/handler/openid"
|
|
|
|
"github.com/ory/fosite/token/jwt"
|
2020-12-03 20:34:58 +00:00
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"gopkg.in/square/go-jose.v2"
|
2020-12-04 01:16:08 +00:00
|
|
|
|
|
|
|
"go.pinniped.dev/internal/oidc/jwks"
|
2021-04-09 00:28:01 +00:00
|
|
|
"go.pinniped.dev/internal/testutil/oidctestutil"
|
2020-12-03 20:34:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestDynamicOpenIDConnectECDSAStrategy(t *testing.T) {
|
|
|
|
const (
|
2020-12-04 01:16:08 +00:00
|
|
|
goodIssuer = "https://some-good-issuer.com"
|
|
|
|
clientID = "some-client-id"
|
|
|
|
goodSubject = "some-subject"
|
|
|
|
goodUsername = "some-username"
|
2020-12-12 01:39:58 +00:00
|
|
|
goodNonce = "some-nonce-value-with-enough-bytes-to-exceed-min-allowed"
|
2020-12-03 20:34:58 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
ecPrivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
rsaPrivateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
issuer string
|
|
|
|
jwksProvider func(jwks.DynamicJWKSProvider)
|
2020-12-07 19:53:24 +00:00
|
|
|
wantErrorType *fosite.RFC6749Error
|
|
|
|
wantErrorCause string
|
2020-12-03 20:34:58 +00:00
|
|
|
wantSigningJWK *jose.JSONWebKey
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "jwks provider does contain signing key for issuer",
|
|
|
|
issuer: goodIssuer,
|
|
|
|
jwksProvider: func(provider jwks.DynamicJWKSProvider) {
|
|
|
|
provider.SetIssuerToJWKSMap(
|
|
|
|
nil,
|
|
|
|
map[string]*jose.JSONWebKey{
|
|
|
|
goodIssuer: {
|
|
|
|
Key: ecPrivateKey,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
},
|
|
|
|
wantSigningJWK: &jose.JSONWebKey{
|
|
|
|
Key: ecPrivateKey,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2020-12-07 19:53:24 +00:00
|
|
|
name: "jwks provider does not contain signing key for issuer",
|
|
|
|
issuer: goodIssuer,
|
|
|
|
wantErrorType: fosite.ErrTemporarilyUnavailable,
|
|
|
|
wantErrorCause: "no JWK found for issuer",
|
2020-12-03 20:34:58 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "jwks provider contains signing key of wrong type for issuer",
|
|
|
|
issuer: goodIssuer,
|
|
|
|
jwksProvider: func(provider jwks.DynamicJWKSProvider) {
|
|
|
|
provider.SetIssuerToJWKSMap(
|
|
|
|
nil,
|
|
|
|
map[string]*jose.JSONWebKey{
|
|
|
|
goodIssuer: {
|
|
|
|
Key: rsaPrivateKey,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
},
|
2020-12-07 19:53:24 +00:00
|
|
|
wantErrorType: fosite.ErrServerError,
|
|
|
|
wantErrorCause: "JWK must be of type ecdsa",
|
2020-12-03 20:34:58 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, test := range tests {
|
|
|
|
test := test
|
|
|
|
t.Run(test.name, func(t *testing.T) {
|
|
|
|
jwksProvider := jwks.NewDynamicJWKSProvider()
|
|
|
|
if test.jwksProvider != nil {
|
|
|
|
test.jwksProvider(jwksProvider)
|
|
|
|
}
|
2020-12-04 01:16:08 +00:00
|
|
|
s := newDynamicOpenIDConnectECDSAStrategy(
|
|
|
|
&compose.Config{IDTokenIssuer: test.issuer},
|
|
|
|
jwksProvider,
|
|
|
|
)
|
2020-12-03 20:34:58 +00:00
|
|
|
|
|
|
|
requester := &fosite.Request{
|
|
|
|
Client: &fosite.DefaultClient{
|
|
|
|
ID: clientID,
|
|
|
|
},
|
2020-12-04 01:16:08 +00:00
|
|
|
Session: &openid.DefaultSession{
|
|
|
|
Claims: &jwt.IDTokenClaims{
|
|
|
|
Subject: goodSubject,
|
|
|
|
},
|
|
|
|
Subject: goodSubject,
|
|
|
|
Username: goodUsername,
|
|
|
|
},
|
2020-12-04 15:06:55 +00:00
|
|
|
Form: url.Values{
|
|
|
|
"nonce": {goodNonce},
|
|
|
|
},
|
2020-12-03 20:34:58 +00:00
|
|
|
}
|
|
|
|
idToken, err := s.GenerateIDToken(context.Background(), requester)
|
2020-12-07 19:53:24 +00:00
|
|
|
if test.wantErrorType != nil {
|
|
|
|
require.True(t, errors.Is(err, test.wantErrorType))
|
|
|
|
require.EqualError(t, err.(*fosite.RFC6749Error).Cause(), test.wantErrorCause)
|
2020-12-03 20:34:58 +00:00
|
|
|
} else {
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
privateKey, ok := test.wantSigningJWK.Key.(*ecdsa.PrivateKey)
|
|
|
|
require.True(t, ok, "wanted private key to be *ecdsa.PrivateKey, but was %T", test.wantSigningJWK)
|
|
|
|
|
2020-12-04 15:06:55 +00:00
|
|
|
// Perform a light validation on the token to make sure 1) we passed through the correct
|
|
|
|
// signing key and 2) we forwarded the fosite.Requester correctly. Token generation is
|
|
|
|
// tested more expansively in the token endpoint.
|
|
|
|
token := oidctestutil.VerifyECDSAIDToken(t, goodIssuer, clientID, privateKey, idToken)
|
|
|
|
require.Equal(t, goodSubject, token.Subject)
|
|
|
|
require.Equal(t, goodNonce, token.Nonce)
|
2020-12-03 20:34:58 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|