...

Source file src/github.com/ory/fosite/client_authentication_test.go

Documentation: github.com/ory/fosite

     1  /*
     2   * Copyright © 2017-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
    17   * @Copyright 	2017-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
    18   * @license 	Apache-2.0
    19   *
    20   */
    21  
    22  package fosite_test
    23  
    24  import (
    25  	"context"
    26  	"crypto/ecdsa"
    27  	"crypto/rsa"
    28  	"encoding/base64"
    29  	"encoding/json"
    30  	"fmt"
    31  	"net/http"
    32  	"net/http/httptest"
    33  	"net/url"
    34  	"testing"
    35  	"time"
    36  
    37  	"github.com/ory/fosite/token/jwt"
    38  	"github.com/pkg/errors"
    39  	"github.com/stretchr/testify/assert"
    40  	"github.com/stretchr/testify/require"
    41  	jose "gopkg.in/square/go-jose.v2"
    42  
    43  	. "github.com/ory/fosite"
    44  	"github.com/ory/fosite/internal"
    45  	"github.com/ory/fosite/storage"
    46  )
    47  
    48  func mustGenerateRSAAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.PrivateKey, kid string) string {
    49  	token := jwt.NewWithClaims(jose.RS256, claims)
    50  	token.Header["kid"] = kid
    51  	tokenString, err := token.SignedString(key)
    52  	require.NoError(t, err)
    53  	return tokenString
    54  }
    55  
    56  func mustGenerateECDSAAssertion(t *testing.T, claims jwt.MapClaims, key *ecdsa.PrivateKey, kid string) string {
    57  	token := jwt.NewWithClaims(jose.ES256, claims)
    58  	token.Header["kid"] = kid
    59  	tokenString, err := token.SignedString(key)
    60  	require.NoError(t, err)
    61  	return tokenString
    62  }
    63  
    64  func mustGenerateHSAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.PrivateKey, kid string) string {
    65  	token := jwt.NewWithClaims(jose.HS256, claims)
    66  	tokenString, err := token.SignedString([]byte("aaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccddddddddddddddddddddddd"))
    67  	require.NoError(t, err)
    68  	return tokenString
    69  }
    70  
    71  func mustGenerateNoneAssertion(t *testing.T, claims jwt.MapClaims, key *rsa.PrivateKey, kid string) string {
    72  	token := jwt.NewWithClaims(jwt.SigningMethodNone, claims)
    73  	tokenString, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
    74  	require.NoError(t, err)
    75  	return tokenString
    76  }
    77  
    78  // returns an http basic authorization header, encoded using application/x-www-form-urlencoded
    79  func clientBasicAuthHeader(clientID, clientSecret string) http.Header {
    80  	creds := url.QueryEscape(clientID) + ":" + url.QueryEscape(clientSecret)
    81  	return http.Header{
    82  		"Authorization": {
    83  			"Basic " + base64.StdEncoding.EncodeToString([]byte(creds)),
    84  		},
    85  	}
    86  }
    87  
    88  func TestAuthenticateClient(t *testing.T) {
    89  	const at = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
    90  
    91  	hasher := &BCrypt{WorkFactor: 6}
    92  	f := &Fosite{
    93  		JWKSFetcherStrategy: NewDefaultJWKSFetcherStrategy(),
    94  		Store:               storage.NewMemoryStore(),
    95  		Hasher:              hasher,
    96  		TokenURL:            "token-url",
    97  	}
    98  
    99  	barSecret, err := hasher.Hash(context.TODO(), []byte("bar"))
   100  	require.NoError(t, err)
   101  
   102  	// a secret containing various special characters
   103  	complexSecretRaw := "foo %66%6F%6F@$<§!✓"
   104  	complexSecret, err := hasher.Hash(context.TODO(), []byte(complexSecretRaw))
   105  	require.NoError(t, err)
   106  
   107  	rsaKey := internal.MustRSAKey()
   108  	rsaJwks := &jose.JSONWebKeySet{
   109  		Keys: []jose.JSONWebKey{
   110  			{
   111  				KeyID: "kid-foo",
   112  				Use:   "sig",
   113  				Key:   &rsaKey.PublicKey,
   114  			},
   115  		},
   116  	}
   117  
   118  	ecdsaKey := internal.MustECDSAKey()
   119  	ecdsaJwks := &jose.JSONWebKeySet{
   120  		Keys: []jose.JSONWebKey{
   121  			{
   122  				KeyID: "kid-foo",
   123  				Use:   "sig",
   124  				Key:   &ecdsaKey.PublicKey,
   125  			},
   126  		},
   127  	}
   128  
   129  	var h http.HandlerFunc
   130  	h = func(w http.ResponseWriter, r *http.Request) {
   131  		require.NoError(t, json.NewEncoder(w).Encode(rsaJwks))
   132  	}
   133  	ts := httptest.NewServer(h)
   134  	defer ts.Close()
   135  
   136  	for k, tc := range []struct {
   137  		d             string
   138  		client        *DefaultOpenIDConnectClient
   139  		assertionType string
   140  		assertion     string
   141  		r             *http.Request
   142  		form          url.Values
   143  		expectErr     error
   144  	}{
   145  		{
   146  			d:         "should fail because authentication can not be determined",
   147  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo"}, TokenEndpointAuthMethod: "client_secret_basic"},
   148  			form:      url.Values{},
   149  			r:         new(http.Request),
   150  			expectErr: ErrInvalidRequest,
   151  		},
   152  		{
   153  			d:         "should fail because client does not exist",
   154  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "none"},
   155  			form:      url.Values{"client_id": []string{"bar"}},
   156  			r:         new(http.Request),
   157  			expectErr: ErrInvalidClient,
   158  		},
   159  		{
   160  			d:      "should pass because client is public and authentication requirements are met",
   161  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "none"},
   162  			form:   url.Values{"client_id": []string{"foo"}},
   163  			r:      new(http.Request),
   164  		},
   165  		{
   166  			d:      "should pass because client is public and client secret is empty in query param",
   167  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "none"},
   168  			form:   url.Values{"client_id": []string{"foo"}, "client_secret": []string{""}},
   169  			r:      new(http.Request),
   170  		},
   171  		{
   172  			d:      "should pass because client is public and client secret is empty in basic auth header",
   173  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "none"},
   174  			form:   url.Values{},
   175  			r:      &http.Request{Header: clientBasicAuthHeader("foo", "")},
   176  		},
   177  		{
   178  			d:         "should fail because client requires basic auth and client secret is empty in basic auth header",
   179  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "client_secret_basic"},
   180  			form:      url.Values{},
   181  			r:         &http.Request{Header: clientBasicAuthHeader("foo", "")},
   182  			expectErr: ErrInvalidClient,
   183  		},
   184  		{
   185  			d:      "should pass with client credentials containing special characters",
   186  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "!foo%20bar", Secret: complexSecret}, TokenEndpointAuthMethod: "client_secret_post"},
   187  			form:   url.Values{"client_id": []string{"!foo%20bar"}, "client_secret": []string{complexSecretRaw}},
   188  			r:      new(http.Request),
   189  		},
   190  		{
   191  			d:      "should pass with client credentials containing special characters via basic auth",
   192  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo — bar! +<&>*", Secret: complexSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
   193  			form:   url.Values{},
   194  			r:      &http.Request{Header: clientBasicAuthHeader("foo — bar! +<&>*", complexSecretRaw)},
   195  		},
   196  		{
   197  			d:         "should fail because auth method is not none",
   198  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Public: true}, TokenEndpointAuthMethod: "client_secret_basic"},
   199  			form:      url.Values{"client_id": []string{"foo"}},
   200  			r:         new(http.Request),
   201  			expectErr: ErrInvalidClient,
   202  		},
   203  		{
   204  			d:      "should pass because client is confidential and id and secret match in post body",
   205  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: []byte("invalid_hash"), RotatedSecrets: [][]byte{barSecret}}, TokenEndpointAuthMethod: "client_secret_post"},
   206  			form:   url.Values{"client_id": []string{"foo"}, "client_secret": []string{"bar"}},
   207  			r:      new(http.Request),
   208  		},
   209  		{
   210  			d:      "should pass because client is confidential and id and rotated secret match in post body",
   211  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_post"},
   212  			form:   url.Values{"client_id": []string{"foo"}, "client_secret": []string{"bar"}},
   213  			r:      new(http.Request),
   214  		},
   215  		{
   216  			d:         "should fail because client is confidential and secret does not match in post body",
   217  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_post"},
   218  			form:      url.Values{"client_id": []string{"foo"}, "client_secret": []string{"baz"}},
   219  			r:         new(http.Request),
   220  			expectErr: ErrInvalidClient,
   221  		},
   222  		{
   223  			d:         "should fail because client is confidential and id does not exist in post body",
   224  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_post"},
   225  			form:      url.Values{"client_id": []string{"foo"}, "client_secret": []string{"bar"}},
   226  			r:         new(http.Request),
   227  			expectErr: ErrInvalidClient,
   228  		},
   229  		{
   230  			d:      "should pass because client is confidential and id and secret match in header",
   231  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
   232  			form:   url.Values{},
   233  			r:      &http.Request{Header: clientBasicAuthHeader("foo", "bar")},
   234  		},
   235  		{
   236  			d:      "should pass because client is confidential and id and rotated secret match in header",
   237  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: []byte("invalid_hash"), RotatedSecrets: [][]byte{barSecret}}, TokenEndpointAuthMethod: "client_secret_basic"},
   238  			form:   url.Values{},
   239  			r:      &http.Request{Header: clientBasicAuthHeader("foo", "bar")},
   240  		},
   241  		{
   242  			d:      "should pass because client is confidential and id and rotated secret match in header",
   243  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: []byte("invalid_hash"), RotatedSecrets: [][]byte{[]byte("invalid"), barSecret}}, TokenEndpointAuthMethod: "client_secret_basic"},
   244  			form:   url.Values{},
   245  			r:      &http.Request{Header: clientBasicAuthHeader("foo", "bar")},
   246  		},
   247  		{
   248  			d:         "should fail because auth method is not client_secret_basic",
   249  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_post"},
   250  			form:      url.Values{},
   251  			r:         &http.Request{Header: clientBasicAuthHeader("foo", "bar")},
   252  			expectErr: ErrInvalidClient,
   253  		},
   254  		{
   255  			d:         "should fail because client is confidential and secret does not match in header",
   256  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
   257  			form:      url.Values{},
   258  			r:         &http.Request{Header: clientBasicAuthHeader("foo", "baz")},
   259  			expectErr: ErrInvalidClient,
   260  		},
   261  		{
   262  			d:         "should fail because client is confidential and neither secret nor rotated does match in header",
   263  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret, RotatedSecrets: [][]byte{barSecret}}, TokenEndpointAuthMethod: "client_secret_basic"},
   264  			form:      url.Values{},
   265  			r:         &http.Request{Header: clientBasicAuthHeader("foo", "baz")},
   266  			expectErr: ErrInvalidClient,
   267  		},
   268  		{
   269  			d:         "should fail because client id is not encoded using application/x-www-form-urlencoded",
   270  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
   271  			form:      url.Values{},
   272  			r:         &http.Request{Header: http.Header{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("%%%%%%:foo"))}}},
   273  			expectErr: ErrInvalidRequest,
   274  		},
   275  		{
   276  			d:         "should fail because client secret is not encoded using application/x-www-form-urlencoded",
   277  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
   278  			form:      url.Values{},
   279  			r:         &http.Request{Header: http.Header{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo:%%%%%%%"))}}},
   280  			expectErr: ErrInvalidRequest,
   281  		},
   282  		{
   283  			d:         "should fail because client is confidential and id does not exist in header",
   284  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, TokenEndpointAuthMethod: "client_secret_basic"},
   285  			form:      url.Values{},
   286  			r:         &http.Request{Header: http.Header{"Authorization": {"Basic " + base64.StdEncoding.EncodeToString([]byte("foo:bar"))}}},
   287  			expectErr: ErrInvalidClient,
   288  		},
   289  		{
   290  			d:         "should fail because client_assertion but client_assertion is missing",
   291  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "private_key_jwt"},
   292  			form:      url.Values{"client_id": []string{"foo"}, "client_assertion_type": []string{at}},
   293  			r:         new(http.Request),
   294  			expectErr: ErrInvalidRequest,
   295  		},
   296  		{
   297  			d:         "should fail because client_assertion_type is unknown",
   298  			client:    &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "foo", Secret: barSecret}, TokenEndpointAuthMethod: "private_key_jwt"},
   299  			form:      url.Values{"client_id": []string{"foo"}, "client_assertion_type": []string{"foobar"}},
   300  			r:         new(http.Request),
   301  			expectErr: ErrInvalidRequest,
   302  		},
   303  		{
   304  			d:      "should pass with proper RSA assertion when JWKs are set within the client and client_id is not set in the request",
   305  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   306  			form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   307  				"sub": "bar",
   308  				"exp": time.Now().Add(time.Hour).Unix(),
   309  				"iss": "bar",
   310  				"jti": "12345",
   311  				"aud": "token-url",
   312  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   313  			r: new(http.Request),
   314  		},
   315  		{
   316  			d:      "should pass with proper ECDSA assertion when JWKs are set within the client and client_id is not set in the request",
   317  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: ecdsaJwks, TokenEndpointAuthMethod: "private_key_jwt", TokenEndpointAuthSigningAlgorithm: "ES256"},
   318  			form: url.Values{"client_assertion": {mustGenerateECDSAAssertion(t, jwt.MapClaims{
   319  				"sub": "bar",
   320  				"exp": time.Now().Add(time.Hour).Unix(),
   321  				"iss": "bar",
   322  				"jti": "12345",
   323  				"aud": "token-url",
   324  			}, ecdsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   325  			r: new(http.Request),
   326  		},
   327  		{
   328  			d:      "should fail because RSA assertion is used, but ECDSA assertion is required",
   329  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: ecdsaJwks, TokenEndpointAuthMethod: "private_key_jwt", TokenEndpointAuthSigningAlgorithm: "ES256"},
   330  			form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   331  				"sub": "bar",
   332  				"exp": time.Now().Add(time.Hour).Unix(),
   333  				"iss": "bar",
   334  				"jti": "12345",
   335  				"aud": "token-url",
   336  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   337  			r:         new(http.Request),
   338  			expectErr: ErrInvalidClient,
   339  		},
   340  		{
   341  			d:      "should fail because token auth method is not private_key_jwt, but client_secret_jwt",
   342  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "client_secret_jwt"},
   343  			form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   344  				"sub": "bar",
   345  				"exp": time.Now().Add(time.Hour).Unix(),
   346  				"iss": "bar",
   347  				"jti": "12345",
   348  				"aud": "token-url",
   349  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   350  			r:         new(http.Request),
   351  			expectErr: ErrInvalidClient,
   352  		},
   353  		{
   354  			d:      "should fail because token auth method is not private_key_jwt, but none",
   355  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "none"},
   356  			form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   357  				"sub": "bar",
   358  				"exp": time.Now().Add(time.Hour).Unix(),
   359  				"iss": "bar",
   360  				"jti": "12345",
   361  				"aud": "token-url",
   362  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   363  			r:         new(http.Request),
   364  			expectErr: ErrInvalidClient,
   365  		},
   366  		{
   367  			d:      "should fail because token auth method is not private_key_jwt, but client_secret_post",
   368  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "client_secret_post"},
   369  			form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   370  				"sub": "bar",
   371  				"exp": time.Now().Add(time.Hour).Unix(),
   372  				"iss": "bar",
   373  				"jti": "12345",
   374  				"aud": "token-url",
   375  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   376  			r:         new(http.Request),
   377  			expectErr: ErrInvalidClient,
   378  		},
   379  		{
   380  			d:      "should fail because token auth method is not private_key_jwt, but client_secret_basic",
   381  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "client_secret_basic"},
   382  			form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   383  				"sub": "bar",
   384  				"exp": time.Now().Add(time.Hour).Unix(),
   385  				"iss": "bar",
   386  				"jti": "12345",
   387  				"aud": "token-url",
   388  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   389  			r:         new(http.Request),
   390  			expectErr: ErrInvalidClient,
   391  		},
   392  		{
   393  			d:      "should fail because token auth method is not private_key_jwt, but foobar",
   394  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "foobar"},
   395  			form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   396  				"sub": "bar",
   397  				"exp": time.Now().Add(time.Hour).Unix(),
   398  				"iss": "bar",
   399  				"jti": "12345",
   400  				"aud": "token-url",
   401  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   402  			r:         new(http.Request),
   403  			expectErr: ErrInvalidClient,
   404  		},
   405  		{
   406  			d:      "should pass with proper assertion when JWKs are set within the client and client_id is not set in the request (aud is array)",
   407  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   408  			form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   409  				"sub": "bar",
   410  				"exp": time.Now().Add(time.Hour).Unix(),
   411  				"iss": "bar",
   412  				"jti": "12345",
   413  				"aud": []string{"token-url-2", "token-url"},
   414  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   415  			r: new(http.Request),
   416  		},
   417  		{
   418  			d:      "should fail because audience (array) does not match token url",
   419  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   420  			form: url.Values{"client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   421  				"sub": "bar",
   422  				"exp": time.Now().Add(time.Hour).Unix(),
   423  				"iss": "bar",
   424  				"jti": "12345",
   425  				"aud": []string{"token-url-1", "token-url-2"},
   426  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   427  			r:         new(http.Request),
   428  			expectErr: ErrInvalidClient,
   429  		},
   430  		{
   431  			d:      "should pass with proper assertion when JWKs are set within the client",
   432  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   433  			form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   434  				"sub": "bar",
   435  				"exp": time.Now().Add(time.Hour).Unix(),
   436  				"iss": "bar",
   437  				"jti": "12345",
   438  				"aud": "token-url",
   439  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   440  			r: new(http.Request),
   441  		},
   442  		{
   443  			d:      "should fail because JWT algorithm is HS256",
   444  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   445  			form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateHSAssertion(t, jwt.MapClaims{
   446  				"sub": "bar",
   447  				"exp": time.Now().Add(time.Hour).Unix(),
   448  				"iss": "bar",
   449  				"jti": "12345",
   450  				"aud": "token-url",
   451  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   452  			r:         new(http.Request),
   453  			expectErr: ErrInvalidClient,
   454  		},
   455  		{
   456  			d:      "should fail because JWT algorithm is none",
   457  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   458  			form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateNoneAssertion(t, jwt.MapClaims{
   459  				"sub": "bar",
   460  				"exp": time.Now().Add(time.Hour).Unix(),
   461  				"iss": "bar",
   462  				"jti": "12345",
   463  				"aud": "token-url",
   464  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   465  			r:         new(http.Request),
   466  			expectErr: ErrInvalidClient,
   467  		},
   468  		{
   469  			d:      "should pass with proper assertion when JWKs URI is set",
   470  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeysURI: ts.URL, TokenEndpointAuthMethod: "private_key_jwt"},
   471  			form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   472  				"sub": "bar",
   473  				"exp": time.Now().Add(time.Hour).Unix(),
   474  				"iss": "bar",
   475  				"jti": "12345",
   476  				"aud": "token-url",
   477  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   478  			r: new(http.Request),
   479  		},
   480  		{
   481  			d:      "should fail because client_assertion sub does not match client",
   482  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   483  			form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   484  				"sub": "not-bar",
   485  				"exp": time.Now().Add(time.Hour).Unix(),
   486  				"iss": "bar",
   487  				"jti": "12345",
   488  				"aud": "token-url",
   489  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   490  			r:         new(http.Request),
   491  			expectErr: ErrInvalidClient,
   492  		},
   493  		{
   494  			d:      "should fail because client_assertion iss does not match client",
   495  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   496  			form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   497  				"sub": "bar",
   498  				"exp": time.Now().Add(time.Hour).Unix(),
   499  				"iss": "not-bar",
   500  				"jti": "12345",
   501  				"aud": "token-url",
   502  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   503  			r:         new(http.Request),
   504  			expectErr: ErrInvalidClient,
   505  		},
   506  		{
   507  			d:      "should fail because client_assertion jti is not set",
   508  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   509  			form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   510  				"sub": "bar",
   511  				"exp": time.Now().Add(time.Hour).Unix(),
   512  				"iss": "bar",
   513  				"aud": "token-url",
   514  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   515  			r:         new(http.Request),
   516  			expectErr: ErrInvalidClient,
   517  		},
   518  		{
   519  			d:      "should fail because client_assertion aud is not set",
   520  			client: &DefaultOpenIDConnectClient{DefaultClient: &DefaultClient{ID: "bar", Secret: barSecret}, JSONWebKeys: rsaJwks, TokenEndpointAuthMethod: "private_key_jwt"},
   521  			form: url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   522  				"sub": "bar",
   523  				"exp": time.Now().Add(time.Hour).Unix(),
   524  				"iss": "bar",
   525  				"jti": "12345",
   526  				"aud": "not-token-url",
   527  			}, rsaKey, "kid-foo")}, "client_assertion_type": []string{at}},
   528  			r:         new(http.Request),
   529  			expectErr: ErrInvalidClient,
   530  		},
   531  	} {
   532  		t.Run(fmt.Sprintf("case=%d/description=%s", k, tc.d), func(t *testing.T) {
   533  			store := storage.NewMemoryStore()
   534  			store.Clients[tc.client.ID] = tc.client
   535  			f.Store = store
   536  
   537  			c, err := f.AuthenticateClient(nil, tc.r, tc.form)
   538  			if tc.expectErr != nil {
   539  				require.EqualError(t, err, tc.expectErr.Error())
   540  				return
   541  			}
   542  
   543  			if err != nil {
   544  				var validationError *jwt.ValidationError
   545  				var rfcError *RFC6749Error
   546  				if errors.As(err, &validationError) {
   547  					t.Logf("Error is: %s", validationError.Inner)
   548  				} else if errors.As(err, &rfcError) {
   549  					t.Logf("DebugField is: %s", rfcError.DebugField)
   550  					t.Logf("HintField is: %s", rfcError.HintField)
   551  				}
   552  			}
   553  			require.NoError(t, err)
   554  			assert.EqualValues(t, tc.client, c)
   555  		})
   556  	}
   557  }
   558  
   559  func TestAuthenticateClientTwice(t *testing.T) {
   560  	const at = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
   561  
   562  	key := internal.MustRSAKey()
   563  	client := &DefaultOpenIDConnectClient{
   564  		DefaultClient: &DefaultClient{
   565  			ID:     "bar",
   566  			Secret: []byte("secret"),
   567  		},
   568  		JSONWebKeys: &jose.JSONWebKeySet{
   569  			Keys: []jose.JSONWebKey{
   570  				{
   571  					KeyID: "kid-foo",
   572  					Use:   "sig",
   573  					Key:   &key.PublicKey,
   574  				},
   575  			},
   576  		},
   577  		TokenEndpointAuthMethod: "private_key_jwt",
   578  	}
   579  	store := storage.NewMemoryStore()
   580  	store.Clients[client.ID] = client
   581  
   582  	hasher := &BCrypt{WorkFactor: 6}
   583  	f := &Fosite{
   584  		JWKSFetcherStrategy: NewDefaultJWKSFetcherStrategy(),
   585  		Store:               store,
   586  		Hasher:              hasher,
   587  		TokenURL:            "token-url",
   588  	}
   589  
   590  	formValues := url.Values{"client_id": []string{"bar"}, "client_assertion": {mustGenerateRSAAssertion(t, jwt.MapClaims{
   591  		"sub": "bar",
   592  		"exp": time.Now().Add(time.Hour).Unix(),
   593  		"iss": "bar",
   594  		"jti": "12345",
   595  		"aud": "token-url",
   596  	}, key, "kid-foo")}, "client_assertion_type": []string{at}}
   597  
   598  	c, err := f.AuthenticateClient(nil, new(http.Request), formValues)
   599  	require.NoError(t, err, "%#v", err)
   600  	assert.Equal(t, client, c)
   601  
   602  	// replay the request and expect it to fail
   603  	c, err = f.AuthenticateClient(nil, new(http.Request), formValues)
   604  	require.Error(t, err)
   605  	assert.EqualError(t, err, ErrJTIKnown.Error())
   606  	assert.Nil(t, c)
   607  }
   608  

View as plain text