CVE-2020-15222 (Go, published 2020-09-24T17:15Z)
Description: value: In ORY Fosite (the security first OAuth2 & OpenID Connect framework for Go) before version 0.31.0, when using "private_key_jwt" authentication the uniqueness of the `jti` value is not checked. When using client authentication method "private_key_jwt", 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 seem to check the uniqueness of this `jti` value. This problem is fixed in version 0.31.0.
Severity: MEDIUM (CVSS v3 base score: 8.1)
Weakness: CWE-345 \u2014 Insufficient Verification of Data Authenticity
The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data.
Repository: https://github.com/ory/fosite
Files changed:
client_authentication.go
client_authentication_test.go
client_manager.go
errors.go
go.mod
go.sum
handler/oauth2/flow_refresh.go
internal/access_request.go
internal/access_response.go
internal/access_token_storage.go
internal/access_token_strategy.go
internal/authorize_code_storage.go
internal/authorize_code_strategy.go
internal/authorize_handler.go
internal/authorize_request.go
internal/authorize_response.go
internal/client.go
internal/hash.go
internal/id_token_strategy.go
internal/introspector.go
internal/oauth2_client_storage.go
internal/oauth2_owner_storage.go
internal/oauth2_revoke_storage.go
internal/oauth2_storage.go
internal/oauth2_strategy.go
internal/openid_id_token_storage.go
internal/pkce_storage_strategy.go
internal/refresh_token_strategy.go
internal/request.go
internal/revoke_handler.go
internal/storage.go
internal/token_handler.go
internal/transactional.go
storage/memory.go
Commit message: Merge pull request from GHSA-v3q9-2p3m-7g43

* u

* u

Vulnerable code:
		} else if jti, ok := (*claims)["jti"].(string); !ok || len(jti) == 0 {
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
				"exp": time.Now().Add(time.Hour),
import "context"
	github.com/golang/mock v1.1.1
	golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4
	golang.org/x/net v0.0.0-20181005035420-146acd28ed58 // indirect
	golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
	"github.com/pkg/errors"
	Clients        map[string]fosite.Client
	AuthorizeCodes map[string]StoreAuthorizeCode
	IDSessions     map[string]fosite.Requester
	AccessTokens   map[string]fosite.Requester
	Implicit       map[string]fosite.Requester
	RefreshTokens  map[string]fosite.Requester
	PKCES          map[string]fosite.Requester
	Users          map[string]MemoryUserRelation

Fixed code:
	"encoding/json"
	"time"
		var jti string
		} else if jti, ok = (*claims)["jti"].(string); !ok || len(jti) == 0 {
		} else if f.Store.ClientAssertionJWTValid(context.Background(), jti) != nil {
			return nil, errors.WithStack(ErrJTIKnown.WithHint("Claim \"jti\" from \"client_assertion\" MUST only be used once."))
		}

		// type conversion according to jwt.MapClaims.VerifyExpiresAt
		var expiry int64
		err = nil
		switch exp := (*claims)["exp"].(type) {
		case float64:
			expiry = int64(exp)
		case json.Number:
			expiry, err = exp.Int64()
		default:
			err = ErrInvalidClient.WithHint("Unable to type assert the expiry time from claims. This should not happen as we validate the expiry time already earlier with token.Claims.Valid()")
		}

		if err != nil {
			return nil, errors.WithStack(err)
		}
		if err := f.Store.SetClientAssertionJWT(context.Background(), jti, time.Unix(expiry, 0)); err != nil {
			return nil, err
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),
				"exp": time.Now().Add(time.Hour).Unix(),

func TestAuthenticateClientTwice(t *testing.T) {
	const at = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"

	key := internal.MustRSAKey()
	client := &DefaultOpenIDConnectClient{
		DefaultClient: &DefaultClient{
			ID:     "bar",
			Secret: []byte("secret"),
		},
		JSONWebKeys: &jose.JSONWebKeySet{
			Keys: []jose.JSONWebKey{
				{
					KeyID: "kid-foo",
					Use:   "sig",
					Key:   &key.PublicKey,
				},
			},
		},
		TokenEndpointAuthMethod: "private_key_jwt",
	}
	store := storage.NewMemoryStore()
	store.Clients[client.ID] = client

	hasher := &BCrypt{WorkFactor: 6}
	f := &Fosite{
		JWKSFetcherStrategy: NewDefaultJWKSFetcherStrategy(),
		Store:               store,
		Hasher:              hasher,
		TokenURL:            "token-url",
	}

	formValues := url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
		"sub": "bar",
		"exp": time.Now().Add(time.Hour).Unix(),
		"iss": "bar",
		"jti": "12345",
		"aud": "token-url",
	}, key, "kid-foo")}, "client_assertion_type": []string{at}}

	c, err := f.AuthenticateClient(nil, new(http.Request), formValues)
	require.NoError(t, err, "%#v", err)
	assert.Equal(t, client, c)

	// replay the request and expect it to fail
	c, err = f.AuthenticateClient(nil, new(http.Request), formValues)
	require.Error(t, err)
	assert.EqualError(t, err, ErrJTIKnown.Error())
	assert.Nil(t, c)
}
import (
	"context"
	"time"
)
	// ClientAssertionJWTValid returns an error if the JTI is
	// known or the DB check failed and nil if the JTI is not known.
	ClientAssertionJWTValid(ctx context.Context, jti string) error
	// SetClientAssertionJWT marks a JTI as known for the given
	// expiry time. Before inserting the new JTI, it will clean
	// up any existing JTIs that have expired as those tokens can
	// not be replayed due to the expiry.
	SetClientAssertionJWT(ctx context.Context, jti string, exp time.Time) error
	ErrJTIKnown = &RFC6749Error{
		Description: "The jti was already used.",
		Name:        errJTIKnownName,
		Code:        http.StatusBadRequest,
	}
	errJTIKnownName                 = "jti_known"
	github.com/golang/mock v1.4.3
	golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
	"github.com/pkg/errors"

	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	time "time"
// ClientAssertionJWTValid mocks base method
func (m *MockStorage) ClientAssertionJWTValid(arg0 context.Context, arg1 string) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "ClientAssertionJWTValid", arg0, arg1)
	ret0, _ := ret[0].(error)
	return ret0
}

// ClientAssertionJWTValid indicates an expected call of ClientAssertionJWTValid
func (mr *MockStorageMockRecorder) ClientAssertionJWTValid(arg0, arg1 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClientAssertionJWTValid", reflect.TypeOf((*MockStorage)(nil).ClientAssertionJWTValid), arg0, arg1)
}

	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()

// SetClientAssertionJWT mocks base method
func (m *MockStorage) SetClientAssertionJWT(arg0 context.Context, arg1 string, arg2 time.Time) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "SetClientAssertionJWT", arg0, arg1, arg2)
	ret0, _ := ret[0].(error)
	return ret0
}

// SetClientAssertionJWT indicates an expected call of SetClientAssertionJWT
func (mr *MockStorageMockRecorder) SetClientAssertionJWT(arg0, arg1, arg2 interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetClientAssertionJWT", reflect.TypeOf((*MockStorage)(nil).SetClientAssertionJWT), arg0, arg1, arg2)
}
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	m.ctrl.T.Helper()
	mr.mock.ctrl.T.Helper()
	"time"
	Clients         map[string]fosite.Client
	AuthorizeCodes  map[string]StoreAuthorizeCode
	IDSessions      map[string]fosite.Requester
	AccessTokens    map[string]fosite.Requester
	Implicit        map[string]fosite.Requester
	RefreshTokens   map[string]fosite.Requester
	PKCES           map[string]fosite.Requester
	Users           map[string]MemoryUserRelation
	BlacklistedJTIs map[string]time.Time
		BlacklistedJTIs:        make(map[string]time.Time),
func (s *MemoryStore) ClientAssertionJWTValid(_ context.Context, jti string) error {
	if exp, exists := s.BlacklistedJTIs[jti]; exists && exp.After(time.Now()) {
		return fosite.ErrJTIKnown
	}

	return nil
}

func (s *MemoryStore) SetClientAssertionJWT(_ context.Context, jti string, exp time.Time) error {
	// delete expired jtis
	for j, e := range s.BlacklistedJTIs {
		if e.Before(time.Now()) {
			delete(s.BlacklistedJTIs, j)
		}
	}

	if _, exists := s.BlacklistedJTIs[jti]; exists {
		return fosite.ErrJTIKnown
	}

	s.BlacklistedJTIs[jti] = exp
	return nil
}