@@ -2,11 +2,14 @@ package oidc
22
33import (
44 "context"
5+ "crypto"
6+ "crypto/x509"
7+ "encoding/pem"
58 "errors"
69 "fmt"
10+ "os"
711
812 "github.com/coreos/go-oidc/v3/oidc"
9− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
1013 k8serrors "k8s.io/apimachinery/pkg/util/errors"
1114)
1215
@@ -38,6 +41,10 @@ type ProviderVerifierOptions struct {
3841 // eg: https://www.googleapis.com/oauth2/v3/certs
3942 JWKsURL string
4043
44+ // PublicKeyFiles is a list of paths pointing to public key files in PEM format to use
45+ // for verifying JWT tokens
46+ PublicKeyFiles []string
47+
4148 // SkipDiscovery allows to skip OIDC discovery and use manually supplied Endpoints
4249 SkipDiscovery bool
4350
@@ -59,8 +66,12 @@ func (p ProviderVerifierOptions) validate() error {
5966 errs = append(errs, errors.New("missing required setting: issuer-url"))
6067 }
6168
62− if p.SkipDiscovery && p.JWKsURL == "" {
63− errs = append(errs, errors.New("missing required setting: jwks-url"))
69+ if p.SkipDiscovery && p.JWKsURL == "" && len(p.PublicKeyFiles) == 0 {
70+ errs = append(errs, errors.New("missing required setting: jwks-url or public-key-files"))
71+ }
72+
73+ if p.JWKsURL != "" && len(p.PublicKeyFiles) > 0 {
74+ errs = append(errs, errors.New("mutually exclusive settings: jwks-url and public-key-files"))
6475 }
6576
6677 if len(errs) > 0 {
@@ -91,12 +102,12 @@ func (p ProviderVerifierOptions) toOIDCConfig() *oidc.Config {
91102// NewProviderVerifier constructs a ProviderVerifier from the options given.
92103func NewProviderVerifier(ctx context.Context, opts ProviderVerifierOptions) (ProviderVerifier, error) {
93104 if err := opts.validate(); err != nil {
94− return nil, fmt.Errorf("invalid provider verifier options: %v", err)
105+ return nil, fmt.Errorf("invalid provider verifier options: %w", err)
95106 }
96107
97108 verifierBuilder, provider, err := getVerifierBuilder(ctx, opts)
98109 if err != nil {
99− return nil, fmt.Errorf("could not get verifier builder: %v", err)
110+ return nil, fmt.Errorf("could not get verifier builder: %w", err)
100111 }
101112 verifier := NewVerifier(verifierBuilder(opts.toOIDCConfig()), opts.toVerificationOptions())
102113
@@ -117,22 +128,78 @@ type verifierBuilder func(*oidc.Config) *oidc.IDTokenVerifier
117128
118129func getVerifierBuilder(ctx context.Context, opts ProviderVerifierOptions) (verifierBuilder, DiscoveryProvider, error) {
119130 if opts.SkipDiscovery {
131+ var keySet oidc.KeySet
132+ var err error
133+
134+ if opts.JWKsURL != "" {
135+ keySet = oidc.NewRemoteKeySet(ctx, opts.JWKsURL)
136+ } else {
137+ keySet, err = newKeySetFromStatic(opts.PublicKeyFiles)
138+ if err != nil {
139+ return nil, nil, fmt.Errorf("error while parsing public keys: %w", err)
140+ }
141+ }
120142 // Instead of discovering the JWKs URL, it needs to be specified in the opts already
121− return newVerifierBuilder(ctx, opts.IssuerURL, opts.JWKsURL, opts.SupportedSigningAlgs), nil, nil
143+ return newVerifierBuilder(
144+ opts.IssuerURL,
145+ keySet,
146+ opts.SupportedSigningAlgs,
147+ ), nil, nil
122148 }
123149
124150 provider, err := NewProvider(ctx, opts.IssuerURL, opts.SkipIssuerVerification)
125151 if err != nil {
126− return nil, nil, fmt.Errorf("error while discovery OIDC configuration: %v", err)
152+ return nil, nil, fmt.Errorf("error while discovery OIDC configuration: %w", err)
153+ }
154+
155+ return newVerifierBuilder(
156+ opts.IssuerURL,
157+ oidc.NewRemoteKeySet(ctx, provider.Endpoints().JWKsURL),
158+ provider.SupportedSigningAlgs(),
159+ ), provider, nil
160+}
161+
162+// GetPublicKeyFromBytes parses a PEM-encoded public key from a byte array
163+// and returns a crypto.PublicKey object.
164+func getPublicKeyFromBytes(bytes []byte) (crypto.PublicKey, error) {
165+ block, _ := pem.Decode(bytes)
166+ if block == nil {
167+ return nil, errors.New("failed to parse PEM block containing the public key")
168+ }
169+ publicKey, err := x509.ParsePKIXPublicKey(block.Bytes)
170+ if err != nil {
171+ return nil, fmt.Errorf("failed to parse public key: %w", err)
127172 }
128− verifierBuilder := newVerifierBuilder(ctx, opts.IssuerURL, provider.Endpoints().JWKsURL, provider.SupportedSigningAlgs())
129− return verifierBuilder, provider, nil
173+
174+ cryptoPublicKey, ok := publicKey.(crypto.PublicKey)
175+ if !ok {
176+ return nil, errors.New("failed to cast public key to crypto.PublicKey")
177+ }
178+
179+ return cryptoPublicKey, nil
180+}
181+
182+// newKeySetFromStatic create a StaticKeySet from a set of files
183+func newKeySetFromStatic(keys []string) (*oidc.StaticKeySet, error) {
184+ keySet := []crypto.PublicKey{}
185+ for _, keyFile := range keys {
186+ bytes, err := os.ReadFile(keyFile)
187+ if err != nil {
188+ return nil, fmt.Errorf("failed to read file: %w", err)
189+ }
190+
191+ publicKey, err := getPublicKeyFromBytes(bytes)
192+ if err != nil {
193+ return nil, fmt.Errorf("failed to create keys: %w", err)
194+ }
195+ keySet = append(keySet, publicKey)
196+ }
197+
198+ return &oidc.StaticKeySet{PublicKeys: keySet}, nil
130199}
131200
132201// newVerifierBuilder returns a function to create a IDToken verifier from an OIDC config.
133−func newVerifierBuilder(ctx context.Context, issuerURL, jwksURL string, supportedSigningAlgs []string) verifierBuilder {
134− ctx = oidc.ClientContext(ctx, requests.DefaultHTTPClient)
135− keySet := oidc.NewRemoteKeySet(ctx, jwksURL)
202+func newVerifierBuilder(issuerURL string, keySet oidc.KeySet, supportedSigningAlgs []string) verifierBuilder {
136203 return func(oidcConfig *oidc.Config) *oidc.IDTokenVerifier {
137204 if len(supportedSigningAlgs) > 0 {
138205 oidcConfig.SupportedSigningAlgs = supportedSigningAlgs