Skip to content

Commit

Permalink
Merge pull request from GHSA-v3q9-2p3m-7g43
Browse files Browse the repository at this point in the history
* u

* u
  • Loading branch information
zepatrik authored Mar 29, 2020
1 parent 35a1558 commit 0c9e0f6
Show file tree
Hide file tree
Showing 34 changed files with 494 additions and 27 deletions.
26 changes: 25 additions & 1 deletion client_authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ package fosite
import (
"context"
"crypto/rsa"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"

jwt "github.com/dgrijalva/jwt-go"
"github.com/pkg/errors"
Expand Down Expand Up @@ -142,14 +144,36 @@ func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form u
return nil, errors.WithStack(ErrInvalidClient.WithHint("Unable to type assert claims from request parameter \"client_assertion\".").WithDebugf(`Got claims of type %T but expected type "*jwt.MapClaims".`, token.Claims))
}

var jti string
if !claims.VerifyIssuer(clientID, true) {
return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim \"iss\" from \"client_assertion\" must match the \"client_id\" of the OAuth 2.0 Client."))
} else if f.TokenURL == "" {
return nil, errors.WithStack(ErrMisconfiguration.WithHint("The authorization server's token endpoint URL has not been set."))
} else if sub, ok := (*claims)["sub"].(string); !ok || sub != clientID {
return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim \"sub\" from \"client_assertion\" must match the \"client_id\" of the OAuth 2.0 Client."))
} else if jti, ok := (*claims)["jti"].(string); !ok || len(jti) == 0 {
} else if jti, ok = (*claims)["jti"].(string); !ok || len(jti) == 0 {
return nil, errors.WithStack(ErrInvalidClient.WithHint("Claim \"jti\" from \"client_assertion\" must be set but is not."))
} 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
}

if auds, ok := (*claims)["aud"].([]interface{}); !ok {
Expand Down
74 changes: 62 additions & 12 deletions client_authentication_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": "token-url",
Expand All @@ -226,7 +226,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "client_secret_jwt"},
form: url.Values{"client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": "token-url",
Expand All @@ -239,7 +239,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": []string{"token-url-2", "token-url"},
Expand All @@ -251,7 +251,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": []string{"token-url-1", "token-url-2"},
Expand All @@ -264,7 +264,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": "token-url",
Expand All @@ -276,7 +276,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateHSAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": "token-url",
Expand All @@ -289,7 +289,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateNoneAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": "token-url",
Expand All @@ -302,7 +302,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeysURI: ts.URL, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": "token-url",
Expand All @@ -314,7 +314,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "not-bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": "token-url",
Expand All @@ -327,7 +327,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "not-bar",
"jti": "12345",
"aud": "token-url",
Expand All @@ -340,7 +340,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"aud": "token-url",
}, key, "kid-foo")}, "client_assertion_type": []string{at}},
Expand All @@ -352,7 +352,7 @@ func TestAuthenticateClient(t *testing.T) {
client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: jwks, TokenEndpointAuthMethod: "private_key_jwt"},
form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateAssertion(t, jwt.MapClaims{
"sub": "bar",
"exp": time.Now().Add(time.Hour),
"exp": time.Now().Add(time.Hour).Unix(),
"iss": "bar",
"jti": "12345",
"aud": "not-token-url",
Expand Down Expand Up @@ -385,3 +385,53 @@ func TestAuthenticateClient(t *testing.T) {
})
}
}

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)
}
13 changes: 12 additions & 1 deletion client_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,22 @@

package fosite

import "context"
import (
"context"
"time"
)

// ClientManager defines the (persistent) manager interface for clients.
type ClientManager interface {
// GetClient loads the client by its ID or returns an error
// if the client does not exist or another error occurred.
GetClient(ctx context.Context, id string) (Client, error)
// 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
}
6 changes: 6 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ var (
Name: errInvalidRequestObject,
Code: http.StatusBadRequest,
}
ErrJTIKnown = &RFC6749Error{
Description: "The jti was already used.",
Name: errJTIKnownName,
Code: http.StatusBadRequest,
}
)

const (
Expand Down Expand Up @@ -242,6 +247,7 @@ const (
errRequestNotSupportedName = "request_not_supported"
errRequestURINotSupportedName = "request_uri_not_supported"
errRegistrationNotSupportedName = "registration_not_supported"
errJTIKnownName = "jti_known"
)

func ErrorToRFC6749Error(err error) *RFC6749Error {
Expand Down
6 changes: 2 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5 // indirect
github.com/golang/mock v1.1.1
github.com/golang/mock v1.4.3
github.com/gopherjs/gopherjs v0.0.0-20181004151105-1babbf986f6f // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/mux v1.6.2
Expand All @@ -22,10 +22,8 @@ require (
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
github.com/stretchr/testify v1.2.2
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 // indirect
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
google.golang.org/appengine v1.2.0 // indirect
gopkg.in/square/go-jose.v2 v2.1.9
)
Expand Down
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5 h1:LCoguo7Zd0MByKM
github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
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.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA=
Expand Down Expand Up @@ -46,15 +48,26 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc=
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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/oauth2 v0.0.0-20181003184128-c57b0facaced h1:4oqSq7eft7MdPKBGQK11X9WYUxmj6ZLgGTqYIbY1kyw=
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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/text v0.3.0/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=
google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
gopkg.in/square/go-jose.v2 v2.1.9 h1:YCFbL5T2gbmC2sMG12s1x2PAlTK5TZNte3hjZEIcCAg=
gopkg.in/square/go-jose.v2 v2.1.9/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
3 changes: 2 additions & 1 deletion handler/oauth2/flow_refresh.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ import (
"strings"
"time"

"github.com/pkg/errors"

"github.com/ory/fosite"
"github.com/ory/fosite/storage"
"github.com/pkg/errors"
)

type RefreshTokenGrantHandler struct {
Expand Down
Loading

0 comments on commit 0c9e0f6

Please sign in to comment.