snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-16 01:08:34 +05:00
commit 9456a4e922
2560 changed files with 120911 additions and 0 deletions

View File

@@ -0,0 +1,7 @@
{
"alg": "HS256",
"k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU",
"kid": "8d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8",
"kty": "oct",
"use": "sig"
}

View File

@@ -0,0 +1,7 @@
{
"alg": "HS256",
"k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU",
"kid": "7d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8",
"kty": "oct",
"use": "sig"
}

View File

@@ -0,0 +1,7 @@
{
"alg": "HS256",
"k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU",
"kid": "7d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8",
"kty": "oct",
"use": "sig"
}

View File

@@ -0,0 +1,7 @@
{
"alg": "HS256",
"k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU",
"kid": "7d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8",
"kty": "oct",
"use": "sig"
}

View File

@@ -0,0 +1,7 @@
{
"alg": "HS256",
"k": "Y2hhbmdlbWVjaGFuZ2VtZWNoYW5nZW1lY2hhbmdlbWU",
"kid": "7d5f5ad0674ec2f2960b1a34f33370a0f71471fa0e3ef0c0a692977d276dafe8",
"kty": "oct",
"use": "sig"
}

74
oryx/jwksx/fetcher.go Normal file
View File

@@ -0,0 +1,74 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package jwksx
import (
"encoding/json"
"net/http"
"sync"
"github.com/go-jose/go-jose/v3"
"github.com/pkg/errors"
)
// Fetcher is a small helper for fetching JSON Web Keys from remote endpoints.
//
// DEPRECATED: Use FetcherNext instead.
type Fetcher struct {
sync.RWMutex
remote string
c *http.Client
keys map[string]jose.JSONWebKey
}
// NewFetcher returns a new fetcher that can download JSON Web Keys from remote endpoints.
//
// DEPRECATED: Use FetcherNext instead.
func NewFetcher(remote string) *Fetcher {
return &Fetcher{
remote: remote,
c: http.DefaultClient,
keys: make(map[string]jose.JSONWebKey),
}
}
// GetKey retrieves a JSON Web Key from the cache, fetches it from a remote if it is not yet cached or returns an error.
//
// DEPRECATED: Use FetcherNext instead.
func (f *Fetcher) GetKey(kid string) (*jose.JSONWebKey, error) {
f.RLock()
if k, ok := f.keys[kid]; ok {
f.RUnlock()
return &k, nil
}
f.RUnlock()
res, err := f.c.Get(f.remote)
if err != nil {
return nil, errors.WithStack(err)
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, errors.Errorf("expected status code 200 but got %d when requesting %s", res.StatusCode, f.remote)
}
var set jose.JSONWebKeySet
if err := json.NewDecoder(res.Body).Decode(&set); err != nil {
return nil, errors.WithStack(err)
}
for _, k := range set.Keys {
f.Lock()
f.keys[k.KeyID] = k
f.Unlock()
}
f.RLock()
defer f.RUnlock()
if k, ok := f.keys[kid]; ok {
return &k, nil
}
return nil, errors.Errorf("unable to find JSON Web Key with ID: %s", kid)
}

179
oryx/jwksx/fetcher_v2.go Normal file
View File

@@ -0,0 +1,179 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package jwksx
import (
"context"
"crypto/sha256"
"time"
"github.com/ory/herodot"
"github.com/hashicorp/go-retryablehttp"
"github.com/ory/x/fetcher"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/ory/x/otelx"
"github.com/dgraph-io/ristretto/v2"
"github.com/lestrrat-go/jwx/jwk"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
)
var ErrUnableToFindKeyID = errors.New("specified JWK kid can not be found in the JWK sets")
type (
fetcherNextOptions struct {
forceKID string
cacheTTL time.Duration
useCache bool
httpClient *retryablehttp.Client
schemes []string
}
// FetcherNext is a JWK fetcher that can be used to fetch JWKs from multiple locations.
FetcherNext struct {
cache *ristretto.Cache[[]byte, jwk.Set]
}
// FetcherNextOption is a functional option for the FetcherNext.
FetcherNextOption func(*fetcherNextOptions)
)
// NewFetcherNext returns a new FetcherNext instance.
func NewFetcherNext(cache *ristretto.Cache[[]byte, jwk.Set]) *FetcherNext {
return &FetcherNext{
cache: cache,
}
}
// WithForceKID forces the key ID to be used. Required when multiple JWK sets are configured.
func WithForceKID(kid string) FetcherNextOption {
return func(o *fetcherNextOptions) {
o.forceKID = kid
}
}
// WithCacheTTL sets the cache TTL. If not set, the TTL is unlimited.
func WithCacheTTL(ttl time.Duration) FetcherNextOption {
return func(o *fetcherNextOptions) {
o.cacheTTL = ttl
}
}
// WithCacheEnabled enables the cache.
func WithCacheEnabled() FetcherNextOption {
return func(o *fetcherNextOptions) {
o.useCache = true
}
}
func WithAllowedSchemes(schemes ...string) FetcherNextOption {
return func(o *fetcherNextOptions) {
o.schemes = schemes
}
}
// WithHTTPClient will use the given HTTP client to fetch the JSON Web Keys.
func WithHTTPClient(c *retryablehttp.Client) FetcherNextOption {
return func(o *fetcherNextOptions) {
o.httpClient = c
}
}
func (f *FetcherNext) ResolveKey(ctx context.Context, locations string, modifiers ...FetcherNextOption) (jwk.Key, error) {
return f.ResolveKeyFromLocations(ctx, []string{locations}, modifiers...)
}
func (f *FetcherNext) ResolveKeyFromLocations(ctx context.Context, locations []string, modifiers ...FetcherNextOption) (jwk.Key, error) {
opts := new(fetcherNextOptions)
for _, m := range modifiers {
m(opts)
}
if len(locations) > 1 && opts.forceKID == "" {
return nil, errors.Errorf("a key ID must be specified when multiple JWK sets are configured")
}
set := jwk.NewSet()
eg := new(errgroup.Group)
for k := range locations {
location := locations[k]
eg.Go(func() error {
remoteSet, err := f.fetch(ctx, location, opts)
if err != nil {
return err
}
iterator := remoteSet.Iterate(ctx)
for iterator.Next(ctx) {
// Pair().Value is always of type jwk.Key when generated by Iterate.
set.Add(iterator.Pair().Value.(jwk.Key))
}
return nil
})
}
if err := eg.Wait(); err != nil {
return nil, err
}
if opts.forceKID != "" {
key, found := set.LookupKeyID(opts.forceKID)
if !found {
return nil, errors.WithStack(ErrUnableToFindKeyID)
}
return key, nil
}
// No KID was forced? Use the first key we can find.
key, found := set.Get(0)
if !found {
return nil, errors.WithStack(ErrUnableToFindKeyID)
}
return key, nil
}
// fetch fetches the JWK set from the given location and if enabled, may use the cache to look up the JWK set.
func (f *FetcherNext) fetch(ctx context.Context, location string, opts *fetcherNextOptions) (_ jwk.Set, err error) {
tracer := trace.SpanFromContext(ctx).TracerProvider().Tracer("")
ctx, span := tracer.Start(ctx, "jwksx.FetcherNext.fetch", trace.WithAttributes(attribute.String("location", location)))
defer otelx.End(span, &err)
cacheKey := sha256.Sum256([]byte(location))
if opts.useCache {
if result, found := f.cache.Get(cacheKey[:]); found {
return result, nil
}
}
var fopts []fetcher.Modifier
if opts.httpClient != nil {
fopts = append(fopts, fetcher.WithClient(opts.httpClient))
}
if len(opts.schemes) > 0 {
fopts = append(fopts, fetcher.WithAllowedSchemes(opts.schemes...))
}
result, err := fetcher.NewFetcher(fopts...).FetchContext(ctx, location)
if err != nil {
return nil, err
}
set, err := jwk.ParseReader(result)
if err != nil {
return nil, errors.WithStack(herodot.ErrBadRequest().WithReason("failed to parse JWK set").WithWrap(err))
}
if opts.useCache {
f.cache.SetWithTTL(cacheKey[:], set, 1, opts.cacheTTL)
}
return set, nil
}

129
oryx/jwksx/generator.go Normal file
View File

@@ -0,0 +1,129 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package jwksx
import (
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"io"
"github.com/go-jose/go-jose/v3"
"github.com/gofrs/uuid"
"github.com/pkg/errors"
"golang.org/x/crypto/ed25519"
)
// GenerateSigningKeys generates a JSON Web Key Set for signing.
func GenerateSigningKeys(id, alg string, bits int) (*jose.JSONWebKeySet, error) {
if id == "" {
id = uuid.Must(uuid.NewV4()).String()
}
key, err := generate(jose.SignatureAlgorithm(alg), bits)
if err != nil {
return nil, err
}
return &jose.JSONWebKeySet{
Keys: []jose.JSONWebKey{
{
Algorithm: alg,
Use: "sig",
Key: key,
KeyID: id,
Certificates: []*x509.Certificate{},
},
},
}, nil
}
// GenerateSigningKeysAvailableAlgorithms lists available algorithms that are supported by GenerateSigningKeys.
func GenerateSigningKeysAvailableAlgorithms() []string {
return []string{
string(jose.HS256), string(jose.HS384), string(jose.HS512),
string(jose.ES256), string(jose.ES384), string(jose.ES512), string(jose.EdDSA),
string(jose.RS256), string(jose.RS384), string(jose.RS512), string(jose.PS256), string(jose.PS384), string(jose.PS512),
}
}
// generate generates keypair for corresponding SignatureAlgorithm.
func generate(alg jose.SignatureAlgorithm, bits int) (crypto.PrivateKey, error) {
switch alg {
case jose.ES256, jose.ES384, jose.ES512, jose.EdDSA:
keylen := map[jose.SignatureAlgorithm]int{
jose.ES256: 256,
jose.ES384: 384,
jose.ES512: 521, // sic!
jose.EdDSA: 256,
}
if bits != 0 && bits != keylen[alg] {
return nil, errors.Errorf(`jwksx: "%s" does not support arbitrary key length`, alg)
}
case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512:
if bits == 0 {
bits = 2048
}
if bits < 2048 {
return nil, errors.Errorf(`jwksx: key size must be at least 2048 bit for algorithm "%s"`, alg)
}
case jose.HS256:
if bits == 0 {
bits = 256
}
if bits < 256 {
return nil, errors.Errorf(`jwksx: key size must be at least 256 bit for algorithm "%s"`, alg)
}
case jose.HS384:
if bits == 0 {
bits = 384
}
if bits < 384 {
return nil, errors.Errorf(`jwksx: key size must be at least 2038448 bit for algorithm "%s"`, alg)
}
case jose.HS512:
if bits == 0 {
bits = 1024
}
if bits < 512 {
return nil, errors.Errorf(`jwksx: key size must be at least 512 bit for algorithm "%s"`, alg)
}
}
switch alg {
case jose.ES256:
// The cryptographic operations are implemented using constant-time algorithms.
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
return key, errors.Wrapf(err, "jwks: unable to generate key")
case jose.ES384:
// NB: The cryptographic operations do not use constant-time algorithms.
key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
return key, errors.Wrapf(err, "jwks: unable to generate key")
case jose.ES512:
// NB: The cryptographic operations do not use constant-time algorithms.
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
return key, errors.Wrapf(err, "jwks: unable to generate key")
case jose.EdDSA:
_, key, err := ed25519.GenerateKey(rand.Reader)
return key, errors.Wrapf(err, "jwks: unable to generate key")
case jose.RS256, jose.RS384, jose.RS512, jose.PS256, jose.PS384, jose.PS512:
key, err := rsa.GenerateKey(rand.Reader, bits)
return key, errors.Wrapf(err, "jwks: unable to generate key")
case jose.HS256, jose.HS384, jose.HS512:
if bits%8 != 0 {
return nil, errors.Errorf(`jwksx: key size must be a multiple of 8 for algorithm "%s" but got: %d`, alg, bits)
}
key := make([]byte, bits/8)
if _, err := io.ReadFull(rand.Reader, key); err != nil {
return nil, errors.Wrapf(err, "jwks: unable to generate key")
}
return key, nil
default:
return nil, errors.Errorf(`jwksx: available algorithms are "%+v" but unknown algorithm was requested: "%s"`, GenerateSigningKeysAvailableAlgorithms(), alg)
}
}