VYPR
Moderate severityNVD Advisory· Published Apr 6, 2020· Updated Aug 4, 2024

Disallow replay of `private_key_jwt` by blacklisting JTIs in Hydra

CVE-2020-5300

Description

In Hydra (an OAuth2 Server and OpenID Certified™ OpenID Connect Provider written in Go), before version 1.4.0+oryOS.17, when using client authentication method 'private_key_jwt' [1], OpenId specification says the following about assertion jti: "A unique identifier for the token, which can be used to prevent reuse of the token. These tokens MUST only be used once, unless conditions for reuse were negotiated between the parties". Hydra does not check the uniqueness of this jti value. Exploiting this vulnerability is somewhat difficult because: - TLS protects against MITM which makes it difficult to intercept valid tokens for replay attacks - The expiry time of the JWT gives only a short window of opportunity where it could be replayed This has been patched in version v1.4.0+oryOS.17

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/ory/hydraGo
< 1.4.01.4.0

Affected products

1

Patches

1
700d17d3b7d5

Merge pull request from GHSA-3p3g-vpw6-4w66

https://github.com/ory/hydraPatrikApr 2, 2020via ghsa
17 files changed · +464 47
  • client/manager.go+1 1 modified
    @@ -35,7 +35,7 @@ type Manager interface {
     }
     
     type Storage interface {
    -	fosite.Storage
    +	GetClient(ctx context.Context, id string) (fosite.Client, error)
     
     	CreateClient(ctx context.Context, c *Client) error
     
    
  • driver/configuration/provider_viper_test.go+4 3 modified
    @@ -8,13 +8,14 @@ import (
     	"strings"
     	"testing"
     
    -	"github.com/ory/hydra/x"
    -	"github.com/ory/viper"
    -	"github.com/ory/x/logrusx"
     	"github.com/rs/cors"
     	"github.com/sirupsen/logrus"
     	"github.com/stretchr/testify/assert"
     	"github.com/stretchr/testify/require"
    +
    +	"github.com/ory/hydra/x"
    +	"github.com/ory/viper"
    +	"github.com/ory/x/logrusx"
     )
     
     func setupEnv(env map[string]string) func(t *testing.T) (func(), func()) {
    
  • go.mod+3 3 modified
    @@ -14,7 +14,7 @@ require (
     	github.com/go-swagger/go-swagger v0.22.1-0.20200306221957-4aad3a5f78b8
     	github.com/gobuffalo/packr v1.24.0
     	github.com/gobwas/glob v0.2.3
    -	github.com/golang/mock v1.3.1
    +	github.com/golang/mock v1.4.3
     	github.com/google/uuid v1.1.1
     	github.com/gorilla/sessions v1.1.4-0.20181208214519-12bd4761fc66
     	github.com/gtank/cryptopasta v0.0.0-20170601214702-1f550f6f2f69
    @@ -26,7 +26,7 @@ require (
     	github.com/oleiade/reflections v1.0.0
     	github.com/olekukonko/tablewriter v0.0.1
     	github.com/opentracing/opentracing-go v1.1.1-0.20190913142402-a7454ce5950e
    -	github.com/ory/fosite v0.30.6
    +	github.com/ory/fosite v0.31.0
     	github.com/ory/go-acc v0.2.1
     	github.com/ory/graceful v0.1.1
     	github.com/ory/herodot v0.7.0
    @@ -53,7 +53,7 @@ require (
     	github.com/uber/jaeger-client-go v2.22.1+incompatible
     	github.com/urfave/negroni v1.0.0
     	go.opentelemetry.io/otel v0.2.1
    -	golang.org/x/crypto v0.0.0-20200320181102-891825fb96df
    +	golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59
     	golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
     	golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
     	golang.org/x/tools v0.0.0-20200313205530-4303120df7d8
    
  • go.sum+9 0 modified
    @@ -452,6 +452,8 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb
     github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
     github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
     github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
    +github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
    +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
     github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
     github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
     github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
    @@ -737,6 +739,8 @@ github.com/ory/dockertest/v3 v3.5.4/go.mod h1:J8ZUbNB2FOhm1cFZW9xBpDsODqsSWcyYgt
     github.com/ory/fosite v0.29.0/go.mod h1:0atSZmXO7CAcs6NPMI/Qtot8tmZYj04Nddoold4S2h0=
     github.com/ory/fosite v0.30.6 h1:t1EQHkGv3gVODC9oBvoEi3nIyZ4kvC/ayCsvyswDvis=
     github.com/ory/fosite v0.30.6/go.mod h1:Lq9qQ9Sl6mcea2Tt8J7PU+wUeFYPZ+vg7N3zPVKGbN8=
    +github.com/ory/fosite v0.31.0 h1:NZ0FA4ywPEYrCGLNVBAz2dq8vTacLDbbO4Iiy68WCKQ=
    +github.com/ory/fosite v0.31.0/go.mod h1:lSSqjo8Kr/U1P3kJWxsNGHmq7TnH/7pS1ijvQRT7G+g=
     github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90 h1:Bpk3eqc3rbJT2mE+uS9ETzmi2cEL4RuIKz2iUeteh04=
     github.com/ory/go-acc v0.0.0-20181118080137-ddc355013f90/go.mod h1:sxnvPCxChFuSmTJGj8FdMupeq1BezCiEpDjTUXQ4hf4=
     github.com/ory/go-acc v0.2.1 h1:Pwcmwd/cSnwJsYN76+w3HU7oXeWFTkwj/KUj1qGDrVw=
    @@ -1025,6 +1029,8 @@ golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAak
     golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
     golang.org/x/crypto v0.0.0-20200320181102-891825fb96df h1:lDWgvUvNnaTnNBc/dwOty86cFeKoKWbwy2wQj0gIxbU=
     golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
    +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 h1:3zb4D3T4G8jdExgVU/95+vQXfpEPiMdCaZgmGVxjNHM=
    +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
     golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
     golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
    @@ -1153,6 +1159,7 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepx
     golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
     golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
     golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
    +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
     golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
    @@ -1324,5 +1331,7 @@ mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZI
     mvdan.cc/unparam v0.0.0-20190917161559-b83a221c10a2/go.mod h1:rCqoQrfAmpTX/h2APczwM7UymU/uvaOluiVPIYCSY/k=
     rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
     rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
    +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
    +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
     sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
     sourcegraph.com/sqs/pbtypes v1.0.0/go.mod h1:3AciMUv4qUuRHRHhOG4TZOB+72GdPVz5k+c648qsFS4=
    
  • oauth2/fosite_store_helpers.go+116 0 modified
    @@ -22,11 +22,14 @@ package oauth2
     
     import (
     	"context"
    +	"crypto/sha256"
     	"fmt"
     	"net/url"
     	"testing"
     	"time"
     
    +	"github.com/ory/hydra/x"
    +
     	"github.com/ory/fosite/storage"
     	"github.com/ory/x/sqlxx"
     
    @@ -44,6 +47,33 @@ import (
     	"github.com/ory/hydra/consent"
     )
     
    +func signatureFromJTI(jti string) string {
    +	return fmt.Sprintf("%x", sha256.Sum256([]byte(jti)))
    +}
    +
    +type blacklistedJTI struct {
    +	JTI       string
    +	Signature string    `db:"signature"`
    +	Expiry    time.Time `db:"expires_at"`
    +}
    +
    +func newBlacklistedJTI(jti string, exp time.Time) *blacklistedJTI {
    +	return &blacklistedJTI{
    +		JTI:       jti,
    +		Signature: signatureFromJTI(jti),
    +		// because the database timestamp types are not as accurate as time.Time we truncate to seconds (which should always work)
    +		Expiry: exp.UTC().Truncate(time.Second),
    +	}
    +}
    +
    +type assertionJWTReader interface {
    +	x.FositeStorer
    +
    +	getClientAssertionJWT(ctx context.Context, jti string) (*blacklistedJTI, error)
    +
    +	setClientAssertionJWT(context.Context, *blacklistedJTI) error
    +}
    +
     var defaultRequest = fosite.Request{
     	ID:                "blank",
     	RequestedAt:       time.Now().UTC().Round(time.Second),
    @@ -135,6 +165,8 @@ func TestHelperRunner(t *testing.T, store InternalRegistry, k string) {
     	t.Run(fmt.Sprintf("case=testHelperRevokeRefreshToken/db=%s", k), testHelperRevokeRefreshToken(store))
     	t.Run(fmt.Sprintf("case=testHelperCreateGetDeletePKCERequestSession/db=%s", k), testHelperCreateGetDeletePKCERequestSession(store))
     	t.Run(fmt.Sprintf("case=testHelperFlushTokens/db=%s", k), testHelperFlushTokens(store, time.Hour))
    +	t.Run(fmt.Sprintf("case=testFositeStoreSetClientAssertionJWT/db=%s", k), testFositeStoreSetClientAssertionJWT(store))
    +	t.Run(fmt.Sprintf("case=testFositeStoreClientAssertionJWTValid/db=%s", k), testFositeStoreClientAssertionJWTValid(store))
     }
     
     func testHelperUniqueConstraints(m InternalRegistry, storageType string) func(t *testing.T) {
    @@ -531,6 +563,90 @@ func testFositeSqlStoreTransactionRollbackOpenIdConnectSession(m InternalRegistr
     	}
     }
     
    +func testFositeStoreSetClientAssertionJWT(m InternalRegistry) func(*testing.T) {
    +	return func(t *testing.T) {
    +		t.Run("case=basic setting works", func(t *testing.T) {
    +			store, ok := m.OAuth2Storage().(assertionJWTReader)
    +			require.True(t, ok)
    +			jti := newBlacklistedJTI("basic jti", time.Now().Add(time.Minute))
    +
    +			require.NoError(t, store.SetClientAssertionJWT(context.Background(), jti.JTI, jti.Expiry))
    +
    +			cmp, err := store.getClientAssertionJWT(context.Background(), jti.JTI)
    +			require.NoError(t, err)
    +			assert.Equal(t, jti, cmp)
    +		})
    +
    +		t.Run("case=errors when the JTI is blacklisted", func(t *testing.T) {
    +			store, ok := m.OAuth2Storage().(assertionJWTReader)
    +			require.True(t, ok)
    +			jti := newBlacklistedJTI("already set jti", time.Now().Add(time.Minute))
    +			require.NoError(t, store.setClientAssertionJWT(context.Background(), jti))
    +
    +			assert.True(t, errors.Is(store.SetClientAssertionJWT(context.Background(), jti.JTI, jti.Expiry), fosite.ErrJTIKnown))
    +		})
    +
    +		t.Run("case=deletes expired JTIs", func(t *testing.T) {
    +			store, ok := m.OAuth2Storage().(assertionJWTReader)
    +			require.True(t, ok)
    +			expiredJTI := newBlacklistedJTI("expired jti", time.Now().Add(-time.Minute))
    +			require.NoError(t, store.setClientAssertionJWT(context.Background(), expiredJTI))
    +			newJTI := newBlacklistedJTI("some new jti", time.Now().Add(time.Minute))
    +
    +			require.NoError(t, store.SetClientAssertionJWT(context.Background(), newJTI.JTI, newJTI.Expiry))
    +
    +			_, err := store.getClientAssertionJWT(context.Background(), expiredJTI.JTI)
    +			assert.True(t, errors.Is(err, sqlcon.ErrNoRows))
    +			cmp, err := store.getClientAssertionJWT(context.Background(), newJTI.JTI)
    +			assert.Equal(t, newJTI, cmp)
    +		})
    +
    +		t.Run("case=inserts same JTI if expired", func(t *testing.T) {
    +			store, ok := m.OAuth2Storage().(assertionJWTReader)
    +			require.True(t, ok)
    +			jti := newBlacklistedJTI("going to be reused jti", time.Now().Add(-time.Minute))
    +			require.NoError(t, store.setClientAssertionJWT(context.Background(), jti))
    +
    +			jti.Expiry = jti.Expiry.Add(2 * time.Minute)
    +			assert.NoError(t, store.SetClientAssertionJWT(context.Background(), jti.JTI, jti.Expiry))
    +			cmp, err := store.getClientAssertionJWT(context.Background(), jti.JTI)
    +			assert.NoError(t, err)
    +			assert.Equal(t, jti, cmp)
    +		})
    +	}
    +}
    +
    +func testFositeStoreClientAssertionJWTValid(m InternalRegistry) func(*testing.T) {
    +	return func(t *testing.T) {
    +		t.Run("case=returns valid on unknown JTI", func(t *testing.T) {
    +			store, ok := m.OAuth2Storage().(assertionJWTReader)
    +			require.True(t, ok)
    +
    +			assert.NoError(t, store.ClientAssertionJWTValid(context.Background(), "unknown jti"))
    +		})
    +
    +		t.Run("case=returns invalid on known JTI", func(t *testing.T) {
    +			store, ok := m.OAuth2Storage().(assertionJWTReader)
    +			require.True(t, ok)
    +			jti := newBlacklistedJTI("known jti", time.Now().Add(time.Minute))
    +
    +			require.NoError(t, store.setClientAssertionJWT(context.Background(), jti))
    +
    +			assert.True(t, errors.Is(store.ClientAssertionJWTValid(context.Background(), jti.JTI), fosite.ErrJTIKnown))
    +		})
    +
    +		t.Run("case=returns valid on expired JTI", func(t *testing.T) {
    +			store, ok := m.OAuth2Storage().(assertionJWTReader)
    +			require.True(t, ok)
    +			jti := newBlacklistedJTI("expired jti", time.Now().Add(-time.Minute))
    +
    +			require.NoError(t, store.setClientAssertionJWT(context.Background(), jti))
    +
    +			assert.NoError(t, store.ClientAssertionJWTValid(context.Background(), jti.JTI))
    +		})
    +	}
    +}
    +
     func doTestCommit(m InternalRegistry, t *testing.T,
     	createFn func(context.Context, string, fosite.Requester) error,
     	getFn func(context.Context, string, fosite.Session) (fosite.Requester, error),
    
  • oauth2/fosite_store_memory.go+59 10 modified
    @@ -34,11 +34,12 @@ import (
     )
     
     type FositeMemoryStore struct {
    -	AuthorizeCodes map[string]authorizeCode
    -	IDSessions     map[string]fosite.Requester
    -	AccessTokens   map[string]fosite.Requester
    -	RefreshTokens  map[string]fosite.Requester
    -	PKCES          map[string]fosite.Requester
    +	AuthorizeCodes  map[string]authorizeCode
    +	IDSessions      map[string]fosite.Requester
    +	AccessTokens    map[string]fosite.Requester
    +	RefreshTokens   map[string]fosite.Requester
    +	PKCES           map[string]fosite.Requester
    +	BlacklistedJTIs map[string]time.Time
     
     	c Configuration
     	r InternalRegistry
    @@ -53,11 +54,12 @@ func NewFositeMemoryStore(
     	c Configuration,
     ) *FositeMemoryStore {
     	return &FositeMemoryStore{
    -		AuthorizeCodes: make(map[string]authorizeCode),
    -		IDSessions:     make(map[string]fosite.Requester),
    -		AccessTokens:   make(map[string]fosite.Requester),
    -		PKCES:          make(map[string]fosite.Requester),
    -		RefreshTokens:  make(map[string]fosite.Requester),
    +		AuthorizeCodes:  make(map[string]authorizeCode),
    +		IDSessions:      make(map[string]fosite.Requester),
    +		AccessTokens:    make(map[string]fosite.Requester),
    +		PKCES:           make(map[string]fosite.Requester),
    +		RefreshTokens:   make(map[string]fosite.Requester),
    +		BlacklistedJTIs: make(map[string]time.Time),
     
     		c: c,
     		r: r,
    @@ -73,6 +75,53 @@ func (s *FositeMemoryStore) GetClient(ctx context.Context, id string) (fosite.Cl
     	return s.r.ClientManager().GetClient(ctx, id)
     }
     
    +func (s *FositeMemoryStore) ClientAssertionJWTValid(_ context.Context, jti string) error {
    +	s.RLock()
    +	defer s.RUnlock()
    +	if exp, exists := s.BlacklistedJTIs[jti]; exists && exp.After(time.Now()) {
    +		return errors.WithStack(fosite.ErrJTIKnown)
    +	}
    +
    +	return nil
    +}
    +
    +func (s *FositeMemoryStore) SetClientAssertionJWT(_ context.Context, jti string, exp time.Time) error {
    +	s.Lock()
    +	defer s.Unlock()
    +
    +	for j, e := range s.BlacklistedJTIs {
    +		if e.Before(time.Now()) {
    +			delete(s.BlacklistedJTIs, j)
    +		}
    +	}
    +
    +	if _, exists := s.BlacklistedJTIs[jti]; exists {
    +		return errors.WithStack(fosite.ErrJTIKnown)
    +	}
    +
    +	s.BlacklistedJTIs[jti] = exp
    +	return nil
    +}
    +
    +func (s *FositeMemoryStore) getClientAssertionJWT(_ context.Context, jti string) (*blacklistedJTI, error) {
    +	s.RLock()
    +	defer s.RUnlock()
    +
    +	if exp, exists := s.BlacklistedJTIs[jti]; exists {
    +		return newBlacklistedJTI(jti, exp), nil
    +	}
    +
    +	return nil, errors.WithStack(sqlcon.ErrNoRows)
    +}
    +
    +func (s *FositeMemoryStore) setClientAssertionJWT(_ context.Context, jti *blacklistedJTI) error {
    +	s.Lock()
    +	defer s.Unlock()
    +
    +	s.BlacklistedJTIs[jti.JTI] = jti.Expiry
    +	return nil
    +}
    +
     func (s *FositeMemoryStore) Authenticate(ctx context.Context, id string, secret []byte) (*client.Client, error) {
     	return s.r.ClientManager().Authenticate(ctx, id, secret)
     }
    
  • oauth2/fosite_store_sql.go+62 8 modified
    @@ -31,18 +31,20 @@ import (
     	"time"
     
     	"github.com/jmoiron/sqlx"
    -	"github.com/ory/herodot"
     	"github.com/pkg/errors"
     	migrate "github.com/rubenv/sql-migrate"
     	"github.com/sirupsen/logrus"
     	"github.com/tidwall/gjson"
     
    +	"github.com/ory/herodot"
    +
     	"github.com/ory/fosite"
    -	"github.com/ory/hydra/client"
    -	"github.com/ory/hydra/jwk"
     	"github.com/ory/x/dbal"
     	"github.com/ory/x/sqlcon"
     	"github.com/ory/x/stringsx"
    +
    +	"github.com/ory/hydra/client"
    +	"github.com/ory/hydra/jwk"
     )
     
     type FositeSQLStore struct {
    @@ -69,11 +71,12 @@ func NewFositeSQLStore(db *sqlx.DB, r InternalRegistry, c Configuration, kc *jwk
     type tableName string
     
     const (
    -	sqlTableOpenID  tableName = "oidc"
    -	sqlTableAccess  tableName = "access"
    -	sqlTableRefresh tableName = "refresh"
    -	sqlTableCode    tableName = "code"
    -	sqlTablePKCE    tableName = "pkce"
    +	sqlTableOpenID         tableName = "oidc"
    +	sqlTableAccess         tableName = "access"
    +	sqlTableRefresh        tableName = "refresh"
    +	sqlTableCode           tableName = "code"
    +	sqlTablePKCE           tableName = "pkce"
    +	sqlTableBlacklistedJTI tableName = "jti_blacklist"
     )
     
     var Migrations = map[string]*dbal.PackrMigrationSource{
    @@ -228,6 +231,57 @@ func (s *FositeSQLStore) GetClient(ctx context.Context, id string) (fosite.Clien
     	return s.r.ClientManager().GetClient(ctx, id)
     }
     
    +func (s *FositeSQLStore) ClientAssertionJWTValid(ctx context.Context, jti string) error {
    +	d, err := s.getClientAssertionJWT(ctx, jti)
    +	if errors.Is(err, sqlcon.ErrNoRows) {
    +		// the jti is not known => valid
    +		return nil
    +	} else if err != nil {
    +		return err
    +	}
    +	if d.Expiry.After(time.Now()) {
    +		// the jti is not expired yet => invalid
    +		return errors.WithStack(fosite.ErrJTIKnown)
    +	}
    +	// the jti is expired => valid
    +	return nil
    +}
    +
    +func (s *FositeSQLStore) SetClientAssertionJWT(ctx context.Context, j string, exp time.Time) error {
    +	db := s.db(ctx)
    +
    +	// delete expired
    +	if _, err := db.ExecContext(ctx, fmt.Sprintf("DELETE FROM hydra_oauth2_%s WHERE expires_at < now()", sqlTableBlacklistedJTI)); err != nil {
    +		return sqlcon.HandleError(err)
    +	}
    +
    +	if err := s.setClientAssertionJWT(ctx, newBlacklistedJTI(j, exp)); errors.Is(err, sqlcon.ErrUniqueViolation) {
    +		// found a jti
    +		return errors.WithStack(fosite.ErrJTIKnown)
    +	} else if err != nil {
    +		return err
    +	}
    +	// setting worked without a problem
    +	return nil
    +}
    +
    +func (s *FositeSQLStore) getClientAssertionJWT(ctx context.Context, j string) (*blacklistedJTI, error) {
    +	sig := signatureFromJTI(j)
    +	jti := blacklistedJTI{
    +		JTI: j,
    +	}
    +	db := s.db(ctx)
    +
    +	return &jti, sqlcon.HandleError(db.GetContext(ctx, &jti, db.Rebind(fmt.Sprintf("SELECT * FROM hydra_oauth2_%s WHERE signature=?", sqlTableBlacklistedJTI)), sig))
    +}
    +
    +func (s *FositeSQLStore) setClientAssertionJWT(ctx context.Context, jti *blacklistedJTI) error {
    +	db := s.db(ctx)
    +	_, err := db.ExecContext(ctx, db.Rebind(fmt.Sprintf("INSERT INTO hydra_oauth2_%s (signature, expires_at) VALUES (?, ?)", sqlTableBlacklistedJTI)), jti.Signature, jti.Expiry)
    +
    +	return sqlcon.HandleError(err)
    +}
    +
     func (s *FositeSQLStore) Authenticate(ctx context.Context, id string, secret []byte) (*client.Client, error) {
     	return s.r.ClientManager().Authenticate(ctx, id, secret)
     }
    
  • oauth2/handler.go+6 6 modified
    @@ -659,12 +659,12 @@ func (h *Handler) AuthHandler(w http.ResponseWriter, r *http.Request, _ httprout
     	authorizeRequest.SetID(session.Challenge)
     
     	claims := &jwt.IDTokenClaims{
    -		Subject:     session.ConsentRequest.SubjectIdentifier,
    -		Issuer:      strings.TrimRight(h.c.IssuerURL().String(), "/") + "/",
    -		IssuedAt:    time.Now().UTC(),
    -		AuthTime:    time.Time(session.AuthenticatedAt),
    -		RequestedAt: session.RequestedAt,
    -		Extra:       session.Session.IDToken,
    +		Subject:                             session.ConsentRequest.SubjectIdentifier,
    +		Issuer:                              strings.TrimRight(h.c.IssuerURL().String(), "/") + "/",
    +		IssuedAt:                            time.Now().UTC(),
    +		AuthTime:                            time.Time(session.AuthenticatedAt),
    +		RequestedAt:                         session.RequestedAt,
    +		Extra:                               session.Session.IDToken,
     		AuthenticationContextClassReference: session.ConsentRequest.ACR,
     
     		// We do not need to pass the audience because it's included directly by ORY Fosite
    
  • oauth2/migrations/sql/cockroach/10.sql+6 0 added
    @@ -0,0 +1,6 @@
    +-- Empty because CockroachDB creates indices for foreign keys automatically:
    +-- https://www.cockroachlabs.com/docs/stable/foreign-key.html
    +
    +-- +migrate Up
    +
    +-- +migrate Down
    
  • oauth2/migrations/sql/cockroach/11.sql+10 0 added
    @@ -0,0 +1,10 @@
    +-- +migrate Up
    +CREATE TABLE IF NOT EXISTS hydra_oauth2_jti_blacklist (
    +	signature      	varchar(64) NOT NULL PRIMARY KEY,
    +	expires_at  	timestamp NOT NULL DEFAULT now()
    +);
    +
    +CREATE INDEX ON hydra_oauth2_jti_blacklist ( expires_at );
    +
    +-- +migrate Down
    +DROP TABLE hydra_oauth2_jti_blacklist;
    
  • oauth2/migrations/sql/shared/11.sql+11 0 added
    @@ -0,0 +1,11 @@
    +-- +migrate Up
    +CREATE TABLE IF NOT EXISTS hydra_oauth2_jti_blacklist (
    +	signature       varchar(64) NOT NULL PRIMARY KEY,
    +	expires_at  	timestamp NOT NULL DEFAULT now()
    +);
    +
    +-- mysql requires the index to be named
    +CREATE INDEX hydra_oauth2_jti_blacklist_expiry ON hydra_oauth2_jti_blacklist ( expires_at );
    +
    +-- +migrate Down
    +DROP TABLE hydra_oauth2_jti_blacklist;
    
  • oauth2/migrations/sql/tests/11_test.sql+59 0 added
    @@ -0,0 +1,59 @@
    +-- +migrate Up
    +INSERT INTO hydra_client (id, allowed_cors_origins, client_name, client_secret, redirect_uris, grant_types, response_types, scope, owner, policy_uri, tos_uri, client_uri, logo_uri, contacts, client_secret_expires_at, sector_identifier_uri, jwks, jwks_uri, token_endpoint_auth_method, request_uris, request_object_signing_alg, userinfo_signed_response_alg, subject_type, audience, frontchannel_logout_uri, frontchannel_logout_session_required, post_logout_redirect_uris, backchannel_logout_uri, backchannel_logout_session_required, metadata)
    +VALUES
    +  ('11-client', 'http://localhost|http://google', 'some-client', 'abcdef', 'http://localhost|http://google', 'authorize_code|implicit', 'token|id_token', 'foo|bar', 'aeneas', 'http://policy', 'http://tos', 'http://client', 'http://logo', 'aeneas|foo', 0, 'http://sector', '{"keys": []}', 'http://jwks', 'none', 'http://uri1|http://uri2', 'rs256', 'rs526', 'public', 'https://www.ory.sh/api', 'http://fc-logout/', true, 'http://redir1/|http://redir2/', 'http://bc-logout/', true, '{"foo":"bar"}');
    +
    +INSERT INTO
    +	hydra_oauth2_authentication_session (id, authenticated_at, subject)
    +VALUES
    +	('11-login-session-id', NOW(), '11-sub');
    +
    +INSERT INTO
    +	hydra_oauth2_authentication_request (challenge, verifier, client_id, subject, request_url, skip, requested_scope, csrf, authenticated_at, requested_at, oidc_context, login_session_id, requested_at_audience)
    +VALUES
    +	('11-challenge', '11-verifier', '11-client', '11-subject', '11-redirect', false, '11-scope', '11-csrf', NOW(), NOW(), '{}', '11-login-session-id', '11-aud');
    +
    +INSERT INTO
    +	hydra_oauth2_consent_request (challenge, verifier, client_id, subject, request_url, skip, requested_scope, csrf, authenticated_at, requested_at, oidc_context, forced_subject_identifier, login_session_id, login_challenge, requested_at_audience, acr, context)
    +VALUES
    +	('11-challenge', '11-verifier', '11-client', '11-subject', '11-redirect', false, '11-scope', '11-csrf', NOW(), NOW(), '{}', '11-forced-sub', '11-login-session-id', '11-challenge', '11-aud', '11-acr', '{}');
    +
    +INSERT INTO
    +	hydra_oauth2_consent_request_handled (challenge, granted_scope, remember, remember_for, error, requested_at, session_access_token, session_id_token, authenticated_at, was_used, granted_at_audience)
    +VALUES
    +	('11-challenge', '11-scope', true, 3600, '{}', NOW(), '{}', '{}', NOW(), false, '11-aud');
    +
    +-- The previous block is just to get foreign keys working
    +
    +INSERT INTO
    +	hydra_oauth2_access (signature, request_id, requested_at, client_id, scope, granted_scope, form_data, session_data, subject, active, requested_audience, granted_audience, challenge_id)
    +VALUES
    +	('11-sig', '11-request', NOW(), '11-client', '11-scope', '11-granted-scope', '', '{}', '11-subject', true, '11-challengeed-aud', '11-granted-aud', '11-challenge');
    +
    +INSERT INTO
    +	hydra_oauth2_refresh (signature, request_id, requested_at, client_id, scope, granted_scope, form_data, session_data, subject, active, requested_audience, granted_audience, challenge_id)
    +VALUES
    +	('11-sig', '11-request', NOW(), '11-client', '11-scope', '11-granted-scope', '', '{}', '11-subject', true, '11-challengeed-aud', '11-granted-aud', '11-challenge');
    +
    +INSERT INTO
    +	hydra_oauth2_code (signature, request_id, requested_at, client_id, scope, granted_scope, form_data, session_data, subject, active, requested_audience, granted_audience, challenge_id)
    +VALUES
    +	('11-sig', '11-request', NOW(), '11-client', '11-scope', '11-granted-scope', '', '{}', '11-subject', true, '11-challengeed-aud', '11-granted-aud', '11-challenge');
    +
    +INSERT INTO
    +	hydra_oauth2_oidc (signature, request_id, requested_at, client_id, scope, granted_scope, form_data, session_data, subject, active, requested_audience, granted_audience, challenge_id)
    +VALUES
    +	('11-sig', '11-request', NOW(), '11-client', '11-scope', '11-granted-scope', '', '{}', '11-subject', true, '11-challengeed-aud', '11-granted-aud', '11-challenge');
    +
    +INSERT INTO
    +	hydra_oauth2_pkce (signature, request_id, requested_at, client_id, scope, granted_scope, form_data, session_data, subject, active, requested_audience, granted_audience, challenge_id)
    +VALUES
    +	('11-sig', '11-request', NOW(), '11-client', '11-scope', '11-granted-scope', '', '{}', '11-subject', true, '11-challengeed-aud', '11-granted-aud', '11-challenge');
    +
    + -- 11-sig
    + INSERT INTO
    +     hydra_oauth2_jti_blacklist (signature, expires_at)
    + VALUES
    +     ('c5b4a7dc4798eb3047523b0db7a899958d64f92a8b24ef38057539ef32763abc', '2038-01-19 01:00:00');
    +
    +-- +migrate Down
    
  • oauth2/oauth2_auth_code_test.go+6 5 modified
    @@ -38,6 +38,12 @@ import (
     	djwt "github.com/dgrijalva/jwt-go"
     	"github.com/jmoiron/sqlx"
     	"github.com/julienschmidt/httprouter"
    +	"github.com/sirupsen/logrus"
    +	"github.com/stretchr/testify/assert"
    +	"github.com/stretchr/testify/require"
    +	"golang.org/x/oauth2"
    +	"golang.org/x/oauth2/clientcredentials"
    +
     	"github.com/ory/fosite"
     	"github.com/ory/fosite/token/jwt"
     	hc "github.com/ory/hydra/client"
    @@ -52,11 +58,6 @@ import (
     	"github.com/ory/x/pointerx"
     	"github.com/ory/x/sqlcon/dockertest"
     	"github.com/ory/x/urlx"
    -	"github.com/sirupsen/logrus"
    -	"github.com/stretchr/testify/assert"
    -	"github.com/stretchr/testify/require"
    -	"golang.org/x/oauth2"
    -	"golang.org/x/oauth2/clientcredentials"
     )
     
     func newCookieJar() http.CookieJar {
    
  • oauth2/oauth2_refresh_token_test.go+4 3 modified
    @@ -11,6 +11,10 @@ import (
     	"time"
     
     	"github.com/jmoiron/sqlx"
    +	"github.com/pborman/uuid"
    +	"github.com/stretchr/testify/assert"
    +	"github.com/stretchr/testify/require"
    +
     	"github.com/ory/fosite"
     	hc "github.com/ory/hydra/client"
     	"github.com/ory/hydra/driver"
    @@ -19,9 +23,6 @@ import (
     	"github.com/ory/x/dbal"
     	"github.com/ory/x/errorsx"
     	"github.com/ory/x/sqlcon/dockertest"
    -	"github.com/pborman/uuid"
    -	"github.com/stretchr/testify/assert"
    -	"github.com/stretchr/testify/require"
     )
     
     // TestCreateRefreshTokenSessionStress is a sanity test to verify the fix for https://github.com/ory/hydra/issues/1719 &
    
  • oauth2/sql_migration_files.go+98 6 modified
    @@ -1,5 +1,7 @@
     // Code generated for package oauth2 by go-bindata DO NOT EDIT. (@generated)
     // sources:
    +// migrations/sql/cockroach/10.sql
    +// migrations/sql/cockroach/11.sql
     // migrations/sql/cockroach/9.sql
     // migrations/sql/mysql/.gitkeep
     // migrations/sql/mysql/10.sql
    @@ -14,12 +16,14 @@
     // migrations/sql/postgres/7.sql
     // migrations/sql/postgres/9.sql
     // migrations/sql/shared/1.sql
    +// migrations/sql/shared/11.sql
     // migrations/sql/shared/2.sql
     // migrations/sql/shared/3.sql
     // migrations/sql/shared/4.sql
     // migrations/sql/shared/8.sql
     // migrations/sql/tests/.gitkeep
     // migrations/sql/tests/10_test.sql
    +// migrations/sql/tests/11_test.sql
     // migrations/sql/tests/1_test.sql
     // migrations/sql/tests/2_test.sql
     // migrations/sql/tests/3_test.sql
    @@ -105,6 +109,46 @@ func (fi bindataFileInfo) Sys() interface{} {
     	return nil
     }
     
    +var _migrationsSqlCockroach10Sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x54\xcc\x31\x0e\x83\x30\x0c\x85\xe1\x9d\x53\x78\xaf\x42\x76\xc6\x96\x1e\xa1\x07\x30\xc6\x85\x88\x24\x8e\x62\xa3\x28\xb7\xaf\x90\xda\xa1\xc3\x1b\xde\xf0\x7f\xce\xc1\x33\x15\xeb\xb0\x30\xe1\xa9\x0c\x0f\xa1\xa3\x0a\xd2\x3e\xdf\x81\x2a\xa3\xb1\x42\xc8\x6b\x20\x56\x78\x4b\xbd\xc6\x61\xcb\x70\x70\x57\xc0\xd3\x24\xa1\x05\xc2\x18\xfb\x34\x38\x07\xbb\x59\xd1\xc9\xfb\xd6\xda\x48\x3f\x2a\xe2\xa2\x23\x49\xf2\xab\x90\x7a\x35\x5c\x22\xfb\x2f\xe4\x0e\xee\xe3\x6e\x29\x0e\x57\x7e\x4b\x61\xab\x68\x0c\xaf\xf2\xff\x67\x69\x79\xf8\x04\x00\x00\xff\xff\xb3\xf4\x91\x1c\xad\x00\x00\x00")
    +
    +func migrationsSqlCockroach10SqlBytes() ([]byte, error) {
    +	return bindataRead(
    +		_migrationsSqlCockroach10Sql,
    +		"migrations/sql/cockroach/10.sql",
    +	)
    +}
    +
    +func migrationsSqlCockroach10Sql() (*asset, error) {
    +	bytes, err := migrationsSqlCockroach10SqlBytes()
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	info := bindataFileInfo{name: "migrations/sql/cockroach/10.sql", size: 173, mode: os.FileMode(420), modTime: time.Unix(1585815362, 0)}
    +	a := &asset{bytes: bytes, info: info}
    +	return a, nil
    +}
    +
    +var _migrationsSqlCockroach11Sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x8f\xbd\x4e\xc3\x30\x14\x46\xe7\xf8\x29\xbe\x31\x11\x74\x41\x88\xa5\x53\x20\xae\x14\x61\x9c\xca\x75\xa4\x76\xb2\x2e\xc5\x6a\x0c\xcd\x8f\xec\x5b\x0a\x6f\x8f\xa8\x40\x74\xa1\x77\xbe\xdf\xd1\x39\xb3\x19\xae\xfa\xb0\x8b\xc4\x1e\xed\x24\x1e\x8c\x2c\xad\x84\x2d\xef\x95\x44\xbd\x80\x6e\x2c\xe4\xba\x5e\xd9\x15\xba\xcf\x97\x48\x6e\xa4\x03\x77\x37\xee\x95\x83\x7b\xde\xd3\xf6\x6d\x1f\x12\x23\x17\x59\x0a\xbb\x81\xf8\x10\x3d\x4e\x97\xbd\x53\xdc\x76\x14\xf3\xbb\xdb\xe2\x04\xd1\xad\x52\x58\x9a\xfa\xa9\x34\x1b\x3c\xca\xcd\xb5\xc8\xfc\xc7\x14\xa2\x4f\x8e\x18\xc8\x38\xf4\x3e\x31\xf5\xd3\xdf\x77\x25\x17\x65\xab\x2c\x86\xf1\x98\x17\xa2\x98\x8b\x5f\xbb\x5a\x57\x72\x8d\x46\x5f\x54\xc2\x19\xfe\x7b\x7b\x1e\x5a\x8d\xc7\x41\x54\xa6\x59\xfe\x84\xfe\xcf\x99\x8b\xaf\x00\x00\x00\xff\xff\x52\x42\x55\x41\x21\x01\x00\x00")
    +
    +func migrationsSqlCockroach11SqlBytes() ([]byte, error) {
    +	return bindataRead(
    +		_migrationsSqlCockroach11Sql,
    +		"migrations/sql/cockroach/11.sql",
    +	)
    +}
    +
    +func migrationsSqlCockroach11Sql() (*asset, error) {
    +	bytes, err := migrationsSqlCockroach11SqlBytes()
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	info := bindataFileInfo{name: "migrations/sql/cockroach/11.sql", size: 289, mode: os.FileMode(420), modTime: time.Unix(1585817202, 0)}
    +	a := &asset{bytes: bytes, info: info}
    +	return a, nil
    +}
    +
     var _migrationsSqlCockroach9Sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x98\x41\x6f\xda\x30\x14\xc7\xcf\xf8\x53\xbc\x5b\x41\xa3\xd2\x54\xad\x27\x4e\x19\x79\x4c\xd1\xb2\xd0\x05\x47\x6a\x4f\x91\xeb\x3c\x48\x56\x48\x58\x6c\xda\xed\xdb\x4f\xa6\x04\x82\x42\x28\x9b\x80\xa1\x2d\x57\xbf\x7f\xe2\x9f\xed\xdf\x33\x11\xd7\xd7\xf0\x6e\x96\x4c\x72\xa1\x09\x82\x39\xeb\xfb\x68\x71\x04\x6e\x7d\x74\x11\x9c\x01\x78\x43\x0e\x78\xef\x8c\xf8\x08\xe2\x9f\x51\x2e\xc2\x4c\x2c\x74\x7c\x13\x0a\x29\x49\x29\x68\xb3\x96\x4a\x26\xa9\xd0\x8b\x9c\xe0\x59\xe4\x32\x16\x79\xfb\xe6\xf6\xb6\xb3\x7c\xd0\x0b\x5c\x17\xee\x7c\xe7\x8b\xe5\x3f\xc0\x67\x7c\xe8\xb2\x56\x4e\xdf\x17\xa4\x74\x98\x44\xeb\xf8\x87\xf7\x9b\xf4\x26\x41\x51\x28\x34\xe8\x64\x46\x4a\x8b\xd9\x7c\xf3\x3e\x1b\x07\x56\xe0\x72\x48\xb3\x97\x76\xa7\xcb\x5a\x72\x9a\x50\xba\xf5\xc2\xad\xf9\xbb\xac\xa5\x64\x36\x27\xd0\xf4\x43\x97\x47\x27\xb9\x48\xcd\x2c\xbb\xab\xe3\x2c\x9f\x85\x91\xd0\xa2\x52\x51\xa4\x54\x92\xa5\x35\xc5\xc5\xe3\x37\x92\xba\x66\x2b\x0a\xf4\xab\xab\x2e\x6b\x09\xa9\x93\x67\x82\xc7\x2c\x9b\x56\x13\xdc\x0f\x70\x7b\x2f\x16\x51\x42\xa9\x2c\x40\x2b\x6f\x2b\x56\xf3\x56\x4e\xc6\x62\x3a\xa5\x74\x42\x95\x13\x78\x5d\x40\xe0\x39\x5f\x03\x84\xf6\xe6\x9c\xcc\x1e\x3b\x9e\x8d\xf7\xeb\xc1\xe5\xd1\x94\x86\xd7\x27\x50\x1e\x2b\xcd\xd3\x61\x9d\xde\xc1\x62\xe5\x34\xce\x49\xc5\x8d\x59\xff\x95\x59\x47\x55\x48\x66\x11\x35\xfe\xfc\x6b\xfe\x9c\xc2\x94\x2c\x89\x64\x63\x4a\x63\xca\xdb\xa6\xcc\x9f\x64\x73\xa7\xfc\x96\x29\x7f\xae\x07\xc7\xfb\x5d\xb2\x55\xf4\xa8\xc9\x1d\x53\x0f\x66\xb9\x1c\xfd\x95\x1e\xbb\x3e\x80\x2d\xdb\x86\xfe\xd0\x1b\x71\xdf\x72\x3c\xbe\x2b\x12\xae\x27\x0a\xc7\x4f\x30\x18\xfa\xe8\x7c\xf2\x8c\x1f\x65\x04\xf0\x71\x80\x3e\x7a\x7d\x2c\xbc\x7b\xad\xb5\x4d\x6d\xe8\x81\x8d\x2e\x72\x84\xbe\x35\xea\x5b\x36\xf6\xea\xb1\x8a\xcf\xa7\x7d\x5c\xab\xcc\x79\xc1\x96\x3f\xca\xfb\xa8\x4c\xe0\xbc\x48\xcb\xdb\x7f\x1f\x92\x09\x9c\x17\x69\x79\xcd\xec\x43\x32\x81\x93\x20\x1d\xc5\xf4\x52\xfb\x54\xd1\xca\xbd\x55\xa5\x5b\x5b\x90\x2a\xb3\x82\xe2\xe6\x8c\x45\x1a\x4d\x29\xda\x3c\x7d\xc2\x76\xb8\x38\xfa\x03\x7b\xe6\xe2\xb8\x0f\x6c\xac\x8b\xe3\x3e\xb0\xfb\xfe\x16\x37\x2b\xff\x57\x63\x67\x2f\x29\xb3\xfd\xe1\x5d\x7d\xcb\xf6\x6a\xeb\x2b\xe5\xeb\x03\x46\xac\xfa\xaa\x39\xbe\xfa\xaa\xd9\xa4\x1e\xfb\x15\x00\x00\xff\xff\x15\x33\x61\x06\x58\x12\x00\x00")
     
     func migrationsSqlCockroach9SqlBytes() ([]byte, error) {
    @@ -385,6 +429,26 @@ func migrationsSqlShared1Sql() (*asset, error) {
     	return a, nil
     }
     
    +var _migrationsSqlShared11Sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\x90\xcd\x4e\x83\x50\x10\x85\xd7\xbd\x4f\x71\x96\x10\x65\x63\x8c\x9b\xae\x50\x6e\x13\x22\x42\x43\x21\x69\x57\x64\x5a\x26\xe5\x2a\x7f\xbd\x0c\xb6\xbc\xbd\xb1\xd6\x9f\x8d\x9d\xf5\x9c\x93\xef\x7c\x9e\x87\x9b\xc6\xec\x2d\x09\x23\xef\xd5\x53\xaa\xfd\x4c\x23\xf3\x1f\x23\x8d\x70\x81\x38\xc9\xa0\xd7\xe1\x2a\x5b\xa1\x9a\x4a\x4b\x45\x47\xa3\x54\x77\xc5\xab\x98\x62\x5b\xd3\xee\xad\x36\x83\xc0\x51\xb3\xc1\xec\x5b\x92\xd1\x32\xbe\xee\x9d\xec\xae\x22\xeb\x3c\xdc\xbb\xe7\x92\x38\x8f\x22\x2c\xd3\xf0\xc5\x4f\x37\x78\xd6\x9b\x5b\x35\xe3\x53\x6f\x2c\x0f\x05\x09\x30\x13\xd3\xf0\x20\xd4\xf4\xbf\xdf\x81\x5e\xf8\x79\x94\xa1\xed\x8e\x8e\xab\xdc\xb9\x52\x9e\x87\x66\x1a\x0e\x35\x2c\x1f\xc6\xcf\x2c\xa4\x62\x98\xb6\xe4\x13\xa4\xc3\x96\xd1\x52\xc3\xe5\xf7\x8a\x30\x0e\xf4\xfa\x0a\x77\x71\x26\x98\x90\xc4\x57\xc7\xe1\x0f\xe8\x85\xe2\x47\x59\xd0\x1d\x5b\x15\xa4\xc9\xf2\xa2\xec\xff\x9e\xb9\xfa\x08\x00\x00\xff\xff\x96\x66\xfa\x03\x6b\x01\x00\x00")
    +
    +func migrationsSqlShared11SqlBytes() ([]byte, error) {
    +	return bindataRead(
    +		_migrationsSqlShared11Sql,
    +		"migrations/sql/shared/11.sql",
    +	)
    +}
    +
    +func migrationsSqlShared11Sql() (*asset, error) {
    +	bytes, err := migrationsSqlShared11SqlBytes()
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	info := bindataFileInfo{name: "migrations/sql/shared/11.sql", size: 363, mode: os.FileMode(420), modTime: time.Unix(1585817202, 0)}
    +	a := &asset{bytes: bytes, info: info}
    +	return a, nil
    +}
    +
     var _migrationsSqlShared2Sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x90\xb1\x0a\xc2\x30\x14\x45\xf7\x7e\xc5\xdd\xaa\x48\x97\x42\x27\xa7\x68\xea\x14\x5b\x29\xc9\x5c\x62\x1a\x4d\x05\x8d\xbc\xb4\x8a\x7f\x2f\x14\x04\x07\x45\x0b\xfd\x80\x73\x2e\xf7\x24\x09\x16\xe7\xf6\x48\xba\xb3\x50\xd7\x88\x09\x99\x57\x90\x6c\x25\x72\xb8\x47\x43\xba\xf6\xba\xef\x5c\x5a\x6b\x63\x6c\x08\x60\x9c\x23\xf4\xfb\x93\x35\x1d\x6e\x9a\x8c\xd3\x34\x4b\xb3\x6c\x8e\xa2\x94\x28\x94\x10\xe0\xf9\x86\x29\x21\x11\xc7\xcb\xef\x36\xb2\x07\xb2\xc1\x4d\xa5\x33\xbe\xb1\x53\xb9\x7c\xdb\x98\x91\xae\xe8\x3d\x22\xf7\xf7\xcb\xcf\x8c\xe0\x55\xb9\xc3\xba\x14\x6a\x5b\xbc\x86\xfe\xc8\x35\x8e\x1a\xaa\x8c\x43\x86\xf3\x1f\x91\x67\x00\x00\x00\xff\xff\xa3\x05\x9b\x27\x28\x02\x00\x00")
     
     func migrationsSqlShared2SqlBytes() ([]byte, error) {
    @@ -505,6 +569,26 @@ func migrationsSqlTests10_testSql() (*asset, error) {
     	return a, nil
     }
     
    +var _migrationsSqlTests11_testSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x58\x4d\x6f\xe3\x36\x13\x3e\xaf\x7f\xc5\xc0\x17\x27\x78\xa5\xb5\x6c\xc7\xf1\xc7\x7b\x2a\xd0\x3d\x2c\x50\x64\x81\x6e\xb6\x3d\x14\x05\x41\x91\x23\x99\xb1\xcc\x51\x49\x2a\x4e\x1a\xe7\xbf\x17\xd4\xb7\xbd\x46\x76\x0b\x14\x69\x0f\xc9\x21\x20\x67\x34\x24\xe7\x79\x9e\x19\x4a\x0e\x43\xf8\xdf\x4e\xa5\x86\x3b\x84\x2f\xf9\xe0\xe3\xcd\xe7\x0f\x3f\xdf\xc2\xc7\x9b\xdb\x4f\xb0\x79\x94\x86\x33\x91\x29\xd4\x0e\x2e\x94\x0c\x80\x67\x19\xed\x51\x32\x41\xc6\x32\x32\x2a\x55\xda\x06\x50\x3d\xc1\x34\xdf\x61\x3b\xb1\x28\x0c\xba\x00\x0c\x4a\x65\x50\x38\x56\x18\x65\x03\x48\x0d\xd7\x8e\xb9\xc7\x1c\xad\xf7\xd9\x9c\xb4\xc5\x66\x6e\x05\xe5\x18\x00\xed\x35\x9a\x00\x72\xca\x94\x78\xf4\x71\x01\x38\xb2\xd5\xa0\x5e\xbd\x1c\x67\x94\x52\x6d\x25\xed\xb8\x70\xf6\x64\x77\x86\x0f\xb9\x32\x68\x19\x77\x01\x58\x14\x8e\x0c\x53\x12\xb5\x53\x89\x42\x53\x85\xde\xed\xb7\xb6\xfa\xdf\xec\xb4\x45\xcd\x50\xcb\x9c\x94\x76\x8c\x17\x6e\xc3\x76\xe8\x36\x24\xfd\x79\xff\x28\xd0\x36\xa9\x34\x33\x8a\xef\x7c\x7e\x56\xa5\x5a\xe9\x94\xf1\x2c\x0d\xa0\xb0\x68\x94\x4e\xa8\xb4\xa2\x64\x6d\xa6\xa5\xd7\x16\x55\x88\x4f\x3b\x00\x5e\x48\x85\x5a\x60\x00\x89\x21\xed\xc4\x86\x6b\x8d\x19\xf3\xd9\x15\x75\xa6\xe7\x1c\x16\xad\x55\xa4\x99\x3f\x86\x32\x28\x3d\x60\xd6\x35\xde\x13\xd8\x63\x2e\xb6\xe7\x16\x3e\x63\xff\x7a\xdd\x1d\x3a\x2e\xb9\xe3\x97\x83\x5f\x7e\xf8\xe9\xcb\x87\xcf\x03\x80\x8b\xd1\x64\x12\x56\x58\x8f\x02\x18\x6d\x9c\xcb\xd7\xe3\x71\x46\x82\x67\x1b\xb2\xee\x50\x1b\x52\xa2\x34\x43\xff\x84\xa5\x1d\xf6\x02\x78\x2c\x24\x26\xdf\x17\xea\x29\x20\xa3\xfe\x44\x26\x48\xe2\x41\xed\xf2\x4c\x09\x55\x2e\x53\x92\x75\x50\x92\x95\x03\x6f\x49\x88\x0e\x31\x37\x65\x1c\x6a\xe4\xb6\xb7\x47\x25\xa8\x9e\xc1\x51\xdf\x7d\x26\x9d\x94\xba\x85\x0e\x09\xf9\x59\xd4\xf9\x2b\x45\xf9\x27\x9e\x86\x5b\x7c\xb4\xc3\x35\xfc\xf6\xfb\x73\x6f\x01\xaf\x2a\x3f\xd5\xa4\xb1\x67\x2e\x8c\x9a\x1c\xba\xf1\xd4\xbb\x8c\x9d\xce\xaf\xab\xc1\x7c\x5a\x0e\xf2\x22\xce\x94\x68\xc2\xec\x7a\x3c\xde\xef\xf7\xef\xc9\x3c\xbe\xb7\x9b\x31\xcf\x55\x6f\xc1\x44\x84\x15\x79\xe3\x51\x00\xce\x14\xd8\xb9\x4a\x1d\x4c\xc6\x87\xfe\x74\x3a\xee\xc5\xc6\x67\x62\x9f\x86\x09\xd1\x70\x3d\x8c\xb9\x19\x3e\x8f\x2e\xff\x3f\xe8\x77\x85\xc1\xbb\xaa\x2d\x90\xe7\x65\x5a\x16\x88\xaf\x28\xc1\x9d\x57\x4d\xad\x9e\xba\x5b\x74\x3e\x94\x55\x11\x56\xca\x6f\x95\xf4\xae\x14\x52\x46\xa9\xd2\x61\x1d\x1a\x2a\x39\x0a\xe0\xe6\xd3\xaf\x17\x97\x01\x78\xaf\x2d\xe2\xbf\x77\x86\xba\x30\xe1\x42\x6c\x78\x96\xa1\x4e\x31\x80\x7b\x34\x65\xd1\xb7\x1d\xc2\x9f\xaf\x3e\x4d\xbf\xb0\xb3\x00\xec\x56\xe5\xad\x09\x25\xab\xbb\x92\xb0\x26\x39\x97\x52\xf7\xa0\x9f\x91\x92\x82\xf9\x76\x84\x0f\xae\xec\x50\xaa\xc5\xa4\xdc\xb2\xff\x34\x6b\x2a\xff\x04\x8e\xf6\xd8\xa3\x0a\x80\xe6\xec\xf5\xb4\xd3\x69\x05\x8e\x4f\xa1\x9e\x35\x55\x3f\x0a\x20\xe1\x99\xc5\xfa\x19\x9f\x40\x13\x6c\x4d\xd2\xc1\xdb\xa0\xfc\xf4\x5c\xbb\xcf\x30\xe1\xcd\xbc\x90\xdf\xa0\x40\xf8\xee\xa6\xdd\x7f\x08\xfb\x84\x8c\xf0\x2b\xd4\xbd\xb6\xeb\xfb\xe7\x68\xa9\x2c\xbd\x33\x9f\xe5\x29\x00\x2e\x4c\x75\xdb\xe0\xc3\xa9\x88\xff\x45\xd6\xaa\x54\xcb\x42\x79\x91\xc6\xd3\x23\x7a\x5a\xeb\x91\xa8\xfa\xd8\xb7\xaa\xfd\x84\x66\xb6\xe1\x5a\x66\x28\x8f\xe8\x2e\xaf\xf8\x8e\x3b\x83\x3b\xdc\xc5\x1e\xf6\x66\xc4\x12\x32\x01\xa0\x31\x64\x4e\x29\x6c\x38\xe1\x42\xa0\xb5\x55\x5b\xef\xac\x4d\xa3\x3f\x27\x85\x3d\xb7\xac\xb0\xfe\xba\x6a\xf6\xff\xfe\x02\x6b\xa0\xae\xda\xdf\xec\x3a\x8a\x1a\x74\x8f\xa1\xee\x9b\x7a\x4c\x35\xd5\x11\x86\x70\xbb\x41\xc8\x0d\xde\x2b\x2a\x2c\xc4\x19\x89\x2d\x28\x0b\x77\x85\x75\xe0\x08\x52\x74\x5e\x96\xa8\x52\x0d\xfe\xc2\x80\x3d\x99\xad\xd2\xe9\x8b\xbd\xad\x44\x02\x2e\xfc\x8b\x04\x77\x85\xe9\xb4\xf9\x55\x3f\x39\xae\xaf\x0a\xfd\x13\x32\x12\x32\x3b\xe6\xef\xf2\x0e\xd4\x7a\xd6\x94\x23\x17\x4e\xdd\x1f\x17\x40\xab\xfe\x16\xd9\xd6\xd2\x22\xc9\x94\x3c\x01\xd9\xaa\xb4\xd5\x77\xb9\xd4\x71\x57\x3f\xae\x87\x9e\xd6\xeb\x4d\x3a\xdb\xa8\x2f\xf5\xae\x72\xea\xbb\xaa\x4f\x27\xca\x9e\xa4\x9b\x75\x3a\x4b\x47\xfb\xcb\x22\x37\x98\x18\xb4\x9b\x37\xcc\x5f\x11\x73\xff\x6a\xf7\x06\xf8\x2b\x02\xee\x2f\xcb\x37\xc0\x5f\x11\xf0\x7c\x2b\xde\x14\xfe\xcf\x03\x0e\x61\x08\xd5\x31\x07\xd0\x07\x1f\xfc\xdf\x11\x01\x77\x4e\xb1\x38\xe3\x62\x9b\x29\xff\x82\xda\x63\xa2\xfb\x99\xe0\x72\x00\xed\x37\x2e\xf8\xcf\x5c\x31\x8f\xaf\xf8\x42\x8a\xab\xc5\x6a\x89\xf1\x2c\xba\x5a\xcc\xa7\xb3\x38\x92\xf1\x82\x2f\x57\xab\xd5\x7c\x29\xaf\xaf\x92\xd5\x94\x2f\xe3\xe9\x15\x26\xb3\x65\x34\x5f\xcc\x67\x2b\x4c\x66\xd3\xc5\xf5\x8c\xc7\xe5\xf7\xdb\x34\x9a\x2d\xc3\x68\x12\x4e\x56\x10\x4d\xd6\x51\xb4\x8e\xa2\xe6\x7d\xa1\xfd\xe5\xe5\x47\xda\xeb\xc1\x5f\x01\x00\x00\xff\xff\x20\xb9\x94\xb5\x8b\x11\x00\x00")
    +
    +func migrationsSqlTests11_testSqlBytes() ([]byte, error) {
    +	return bindataRead(
    +		_migrationsSqlTests11_testSql,
    +		"migrations/sql/tests/11_test.sql",
    +	)
    +}
    +
    +func migrationsSqlTests11_testSql() (*asset, error) {
    +	bytes, err := migrationsSqlTests11_testSqlBytes()
    +	if err != nil {
    +		return nil, err
    +	}
    +
    +	info := bindataFileInfo{name: "migrations/sql/tests/11_test.sql", size: 4491, mode: os.FileMode(420), modTime: time.Unix(1585818782, 0)}
    +	a := &asset{bytes: bytes, info: info}
    +	return a, nil
    +}
    +
     var _migrationsSqlTests1_testSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xdc\x93\x41\x4b\x03\x31\x10\x85\xcf\xcd\xaf\x98\x5b\x76\x31\x7b\xa8\x57\x4f\x82\x3d\x14\x64\x0b\xb6\xd5\x63\x18\x92\x69\x36\x60\x93\x3a\x93\x45\x44\xfc\xef\xd2\xcd\xa2\x9e\xbc\xb7\x87\xc0\xbc\xf7\xe0\x3d\xbe\x43\xba\x0e\x6e\x8e\x31\x30\x16\x82\xfd\x49\xad\xfb\xed\xea\x69\x07\xeb\x7e\xb7\x51\x8b\xe1\xc3\x33\xda\x8c\x63\x19\x6e\x2d\x3a\x47\x22\xd0\x48\x0c\x09\xcb\xc8\x64\x80\xe9\x6d\x24\x29\x36\xfa\x9f\x9b\xbc\xc5\x62\xc0\xbd\x46\x4a\x35\x10\x97\x4f\x64\x20\x30\xa6\x73\x3a\xcb\x43\xe6\xa3\xf5\x58\xd0\x80\x90\x48\xcc\x69\x52\xad\x7a\xbe\x7f\xdc\xaf\xb6\x6a\xd1\xe8\x65\x27\x31\x68\x03\x7a\xd9\xcd\xe5\xda\x40\xbf\x79\x69\xda\xc9\xab\x13\x35\x9f\x4a\xeb\x39\xef\xfc\x5a\xe7\xf7\xf9\xa5\xdb\x3b\xf5\x0f\x1c\xd3\x81\x49\x86\x2b\xa5\x73\xd9\xd3\x95\xa2\xe5\xe8\xdd\x45\xa3\xfd\xfd\x7f\x0f\xf9\x3d\xa9\xef\x00\x00\x00\xff\xff\x72\x0a\x2b\x58\x91\x03\x00\x00")
     
     func migrationsSqlTests1_testSqlBytes() ([]byte, error) {
    @@ -737,6 +821,8 @@ func AssetNames() []string {
     
     // _bindata is a table, holding each asset generator, mapped to its name.
     var _bindata = map[string]func() (*asset, error){
    +	"migrations/sql/cockroach/10.sql":  migrationsSqlCockroach10Sql,
    +	"migrations/sql/cockroach/11.sql":  migrationsSqlCockroach11Sql,
     	"migrations/sql/cockroach/9.sql":   migrationsSqlCockroach9Sql,
     	"migrations/sql/mysql/.gitkeep":    migrationsSqlMysqlGitkeep,
     	"migrations/sql/mysql/10.sql":      migrationsSqlMysql10Sql,
    @@ -751,12 +837,14 @@ var _bindata = map[string]func() (*asset, error){
     	"migrations/sql/postgres/7.sql":    migrationsSqlPostgres7Sql,
     	"migrations/sql/postgres/9.sql":    migrationsSqlPostgres9Sql,
     	"migrations/sql/shared/1.sql":      migrationsSqlShared1Sql,
    +	"migrations/sql/shared/11.sql":     migrationsSqlShared11Sql,
     	"migrations/sql/shared/2.sql":      migrationsSqlShared2Sql,
     	"migrations/sql/shared/3.sql":      migrationsSqlShared3Sql,
     	"migrations/sql/shared/4.sql":      migrationsSqlShared4Sql,
     	"migrations/sql/shared/8.sql":      migrationsSqlShared8Sql,
     	"migrations/sql/tests/.gitkeep":    migrationsSqlTestsGitkeep,
     	"migrations/sql/tests/10_test.sql": migrationsSqlTests10_testSql,
    +	"migrations/sql/tests/11_test.sql": migrationsSqlTests11_testSql,
     	"migrations/sql/tests/1_test.sql":  migrationsSqlTests1_testSql,
     	"migrations/sql/tests/2_test.sql":  migrationsSqlTests2_testSql,
     	"migrations/sql/tests/3_test.sql":  migrationsSqlTests3_testSql,
    @@ -812,7 +900,9 @@ var _bintree = &bintree{nil, map[string]*bintree{
     	"migrations": &bintree{nil, map[string]*bintree{
     		"sql": &bintree{nil, map[string]*bintree{
     			"cockroach": &bintree{nil, map[string]*bintree{
    -				"9.sql": &bintree{migrationsSqlCockroach9Sql, map[string]*bintree{}},
    +				"10.sql": &bintree{migrationsSqlCockroach10Sql, map[string]*bintree{}},
    +				"11.sql": &bintree{migrationsSqlCockroach11Sql, map[string]*bintree{}},
    +				"9.sql":  &bintree{migrationsSqlCockroach9Sql, map[string]*bintree{}},
     			}},
     			"mysql": &bintree{nil, map[string]*bintree{
     				".gitkeep": &bintree{migrationsSqlMysqlGitkeep, map[string]*bintree{}},
    @@ -831,15 +921,17 @@ var _bintree = &bintree{nil, map[string]*bintree{
     				"9.sql":    &bintree{migrationsSqlPostgres9Sql, map[string]*bintree{}},
     			}},
     			"shared": &bintree{nil, map[string]*bintree{
    -				"1.sql": &bintree{migrationsSqlShared1Sql, map[string]*bintree{}},
    -				"2.sql": &bintree{migrationsSqlShared2Sql, map[string]*bintree{}},
    -				"3.sql": &bintree{migrationsSqlShared3Sql, map[string]*bintree{}},
    -				"4.sql": &bintree{migrationsSqlShared4Sql, map[string]*bintree{}},
    -				"8.sql": &bintree{migrationsSqlShared8Sql, map[string]*bintree{}},
    +				"1.sql":  &bintree{migrationsSqlShared1Sql, map[string]*bintree{}},
    +				"11.sql": &bintree{migrationsSqlShared11Sql, map[string]*bintree{}},
    +				"2.sql":  &bintree{migrationsSqlShared2Sql, map[string]*bintree{}},
    +				"3.sql":  &bintree{migrationsSqlShared3Sql, map[string]*bintree{}},
    +				"4.sql":  &bintree{migrationsSqlShared4Sql, map[string]*bintree{}},
    +				"8.sql":  &bintree{migrationsSqlShared8Sql, map[string]*bintree{}},
     			}},
     			"tests": &bintree{nil, map[string]*bintree{
     				".gitkeep":    &bintree{migrationsSqlTestsGitkeep, map[string]*bintree{}},
     				"10_test.sql": &bintree{migrationsSqlTests10_testSql, map[string]*bintree{}},
    +				"11_test.sql": &bintree{migrationsSqlTests11_testSql, map[string]*bintree{}},
     				"1_test.sql":  &bintree{migrationsSqlTests1_testSql, map[string]*bintree{}},
     				"2_test.sql":  &bintree{migrationsSqlTests2_testSql, map[string]*bintree{}},
     				"3_test.sql":  &bintree{migrationsSqlTests3_testSql, map[string]*bintree{}},
    
  • oauth2/x_fosite_migrations_test.go+9 2 modified
    @@ -6,16 +6,18 @@ import (
     	"testing"
     
     	"github.com/jmoiron/sqlx"
    +	"github.com/pkg/errors"
     	"github.com/stretchr/testify/require"
     
     	"github.com/ory/fosite"
    +	"github.com/ory/x/dbal"
    +	"github.com/ory/x/dbal/migratest"
    +
     	"github.com/ory/hydra/client"
     	"github.com/ory/hydra/consent"
     	"github.com/ory/hydra/internal"
     	"github.com/ory/hydra/oauth2"
     	"github.com/ory/hydra/x"
    -	"github.com/ory/x/dbal"
    -	"github.com/ory/x/dbal/migratest"
     )
     
     func TestXXMigrations(t *testing.T) {
    @@ -52,6 +54,7 @@ func TestXXMigrations(t *testing.T) {
     					require.Error(t, err)
     					return
     				}
    +
     				_, err := s.GetAccessTokenSession(context.Background(), sig, oauth2.NewSession(""))
     				require.NoError(t, err)
     				_, err = s.GetRefreshTokenSession(context.Background(), sig, oauth2.NewSession(""))
    @@ -64,6 +67,10 @@ func TestXXMigrations(t *testing.T) {
     					_, err = s.GetPKCERequestSession(context.Background(), sig, oauth2.NewSession(""))
     					require.NoError(t, err)
     				}
    +
    +				if k >= 11 {
    +					require.True(t, errors.Is(s.ClientAssertionJWTValid(context.Background(), sig), fosite.ErrJTIKnown), "%+v", err)
    +				}
     			})
     		},
     	)
    
  • x/clean_sql.go+1 0 modified
    @@ -21,6 +21,7 @@ func CleanSQL(t *testing.T, db *sqlx.DB) {
     		"hydra_oauth2_authentication_session",
     		"hydra_oauth2_obfuscated_authentication_session",
     		"hydra_oauth2_logout_request",
    +		"hydra_oauth2_jti_blacklist",
     		"hydra_jwk",
     		"hydra_client",
     		// Migrations
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

6

News mentions

0

No linked articles in our index yet.