1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* |
6
|
|
|
* The MIT License (MIT) |
7
|
|
|
* |
8
|
|
|
* Copyright (c) 2014-2018 Spomky-Labs |
9
|
|
|
* |
10
|
|
|
* This software may be modified and distributed under the terms |
11
|
|
|
* of the MIT license. See the LICENSE file for details. |
12
|
|
|
*/ |
13
|
|
|
|
14
|
|
|
namespace OAuth2Framework\Component\ClientAuthentication\Tests; |
15
|
|
|
|
16
|
|
|
use Http\Message\MessageFactory\DiactorosMessageFactory; |
17
|
|
|
use Jose\Component\Checker\ClaimCheckerManager; |
18
|
|
|
use Jose\Component\Checker\HeaderCheckerManager; |
19
|
|
|
use Jose\Component\Core\AlgorithmManager; |
20
|
|
|
use Jose\Component\Core\Converter\StandardConverter; |
21
|
|
|
use Jose\Component\Core\JWK; |
22
|
|
|
use Jose\Component\Core\JWKSet; |
23
|
|
|
use Jose\Component\Encryption\Algorithm\ContentEncryption\A256CBCHS512; |
24
|
|
|
use Jose\Component\Encryption\Algorithm\KeyEncryption\RSAOAEP256; |
25
|
|
|
use Jose\Component\Encryption\Compression\CompressionMethodManager; |
26
|
|
|
use Jose\Component\Encryption\Compression\Deflate; |
27
|
|
|
use Jose\Component\Encryption\JWEBuilder; |
28
|
|
|
use Jose\Component\Encryption\JWEDecrypter; |
29
|
|
|
use Jose\Component\Encryption\JWELoader; |
30
|
|
|
use Jose\Component\Encryption\JWETokenSupport; |
31
|
|
|
use Jose\Component\Encryption\Serializer\JWESerializerManager; |
32
|
|
|
use Jose\Component\KeyManagement\JKUFactory; |
33
|
|
|
use Jose\Component\Signature\Algorithm\HS256; |
34
|
|
|
use Jose\Component\Signature\Algorithm\RS256; |
35
|
|
|
use Jose\Component\Signature\JWSBuilder; |
36
|
|
|
use Jose\Component\Signature\JWSTokenSupport; |
37
|
|
|
use Jose\Component\Signature\JWSVerifier; |
38
|
|
|
use Jose\Component\Signature\Serializer\CompactSerializer; |
39
|
|
|
use OAuth2Framework\Component\Core\Client\Client; |
40
|
|
|
use OAuth2Framework\Component\Core\Client\ClientId; |
41
|
|
|
use OAuth2Framework\Component\Core\DataBag\DataBag; |
42
|
|
|
use OAuth2Framework\Component\Core\Exception\OAuth2Exception; |
43
|
|
|
use OAuth2Framework\Component\Core\UserAccount\UserAccountId; |
44
|
|
|
use OAuth2Framework\Component\ClientAuthentication\AuthenticationMethodManager; |
45
|
|
|
use OAuth2Framework\Component\ClientAuthentication\ClientAssertionJwt; |
46
|
|
|
use PHPUnit\Framework\TestCase; |
47
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
48
|
|
|
use Zend\Diactoros\Response; |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* @group TokenEndpoint |
52
|
|
|
* @group ClientAuthentication |
53
|
|
|
*/ |
54
|
|
|
class ClientAssertionJwtAuthenticationMethodTest extends TestCase |
55
|
|
|
{ |
56
|
|
|
/** |
57
|
|
|
* @test |
58
|
|
|
*/ |
59
|
|
|
public function genericCalls() |
60
|
|
|
{ |
61
|
|
|
$method = $this->getMethod(); |
62
|
|
|
|
63
|
|
|
self::assertEquals([], $method->getSchemesParameters()); |
64
|
|
|
self::assertEquals(['client_secret_jwt', 'private_key_jwt'], $method->getSupportedMethods()); |
65
|
|
|
self::assertEquals(['HS256', 'RS256'], $method->getSupportedSignatureAlgorithms()); |
66
|
|
|
self::assertEquals(['RSA-OAEP-256'], $method->getSupportedKeyEncryptionAlgorithms()); |
67
|
|
|
self::assertEquals(['A256CBC-HS512'], $method->getSupportedContentEncryptionAlgorithms()); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* @test |
72
|
|
|
*/ |
73
|
|
|
public function theClientIdCannotBeFoundInTheRequest() |
74
|
|
|
{ |
75
|
|
|
$method = $this->getMethod(); |
76
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
77
|
|
|
$request->getHeader('Authorization')->willReturn([]); |
78
|
|
|
$request->getParsedBody()->willReturn([]); |
79
|
|
|
|
80
|
|
|
$clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials); |
81
|
|
|
self::assertNull($clientId); |
82
|
|
|
self::assertNull($credentials); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @test |
87
|
|
|
*/ |
88
|
|
|
public function theClientAssertionTypeIsNotSupported() |
89
|
|
|
{ |
90
|
|
|
$method = $this->getMethod(); |
91
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
92
|
|
|
$request->getHeader('Authorization')->willReturn([]); |
93
|
|
|
$request->getParsedBody()->willReturn([ |
94
|
|
|
'client_assertion_type' => 'foo', |
95
|
|
|
]); |
96
|
|
|
|
97
|
|
|
$clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials); |
98
|
|
|
self::assertNull($clientId); |
99
|
|
|
self::assertNull($credentials); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @test |
104
|
|
|
*/ |
105
|
|
|
public function theClientAssertionIsMissing() |
106
|
|
|
{ |
107
|
|
|
$method = $this->getMethod(); |
108
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
109
|
|
|
$request->getHeader('Authorization')->willReturn([]); |
110
|
|
|
$request->getParsedBody()->willReturn([ |
111
|
|
|
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', |
112
|
|
|
]); |
113
|
|
|
|
114
|
|
|
try { |
115
|
|
|
$method->findClientIdAndCredentials($request->reveal(), $credentials); |
116
|
|
|
$this->fail('An OAuth2 exception should be thrown.'); |
117
|
|
|
} catch (OAuth2Exception $e) { |
118
|
|
|
self::assertEquals('invalid_request', $e->getMessage()); |
119
|
|
|
self::assertEquals('Parameter "client_assertion" is missing.', $e->getErrorDescription()); |
120
|
|
|
} |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* @test |
125
|
|
|
*/ |
126
|
|
|
public function theClientAssertionIsInvalid() |
127
|
|
|
{ |
128
|
|
|
$method = $this->getMethod(); |
129
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
130
|
|
|
$request->getHeader('Authorization')->willReturn([]); |
131
|
|
|
$request->getParsedBody()->willReturn([ |
132
|
|
|
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', |
133
|
|
|
'client_assertion' => 'foo', |
134
|
|
|
]); |
135
|
|
|
|
136
|
|
|
try { |
137
|
|
|
$method->findClientIdAndCredentials($request->reveal(), $credentials); |
138
|
|
|
$this->fail('An OAuth2 exception should be thrown.'); |
139
|
|
|
} catch (OAuth2Exception $e) { |
140
|
|
|
self::assertEquals('invalid_request', $e->getMessage()); |
141
|
|
|
self::assertEquals('Unable to load, decrypt or verify the client assertion.', $e->getErrorDescription()); |
142
|
|
|
} |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @test |
147
|
|
|
*/ |
148
|
|
|
public function theClientAssertionSignedByTheClientIsInvalidBecauseOfMissingClaims() |
149
|
|
|
{ |
150
|
|
|
$method = $this->getMethod(); |
151
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
152
|
|
|
$request->getHeader('Authorization')->willReturn([]); |
153
|
|
|
$request->getParsedBody()->willReturn([ |
154
|
|
|
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', |
155
|
|
|
'client_assertion' => $this->createInvalidClientAssertionSignedByTheClient(), |
156
|
|
|
]); |
157
|
|
|
|
158
|
|
|
try { |
159
|
|
|
$method->findClientIdAndCredentials($request->reveal(), $credentials); |
160
|
|
|
$this->fail('An OAuth2 exception should be thrown.'); |
161
|
|
|
} catch (OAuth2Exception $e) { |
162
|
|
|
self::assertEquals('invalid_request', $e->getMessage()); |
163
|
|
|
self::assertEquals('The following claim(s) is/are mandatory: "iss, sub, aud, exp".', $e->getErrorDescription()); |
164
|
|
|
} |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
/** |
168
|
|
|
* @test |
169
|
|
|
*/ |
170
|
|
|
public function theEncryptedClientAssertionSignedAndEncryptedByTheClientIsInvalidBecauseOfMissingClaims() |
171
|
|
|
{ |
172
|
|
|
$method = $this->getMethod(); |
173
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
174
|
|
|
$request->getHeader('Authorization')->willReturn([]); |
175
|
|
|
$request->getParsedBody()->willReturn([ |
176
|
|
|
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', |
177
|
|
|
'client_assertion' => $this->createInvalidClientAssertionSignedAndEncryptedByTheClient(), |
178
|
|
|
]); |
179
|
|
|
|
180
|
|
|
try { |
181
|
|
|
$method->findClientIdAndCredentials($request->reveal(), $credentials); |
182
|
|
|
$this->fail('An OAuth2 exception should be thrown.'); |
183
|
|
|
} catch (OAuth2Exception $e) { |
184
|
|
|
self::assertEquals('invalid_request', $e->getMessage()); |
185
|
|
|
self::assertEquals('The following claim(s) is/are mandatory: "iss, sub, aud, exp".', $e->getErrorDescription()); |
186
|
|
|
} |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* @test |
191
|
|
|
*/ |
192
|
|
|
public function theClientAssertionIsValidAndTheClientIdIsRetrieved() |
193
|
|
|
{ |
194
|
|
|
$method = $this->getMethod(); |
195
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
196
|
|
|
$request->getHeader('Authorization')->willReturn([]); |
197
|
|
|
$request->getParsedBody()->willReturn([ |
198
|
|
|
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', |
199
|
|
|
'client_assertion' => $this->createValidClientAssertionSignedByTheClient(), |
200
|
|
|
]); |
201
|
|
|
|
202
|
|
|
$clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials); |
203
|
|
|
self::assertEquals('ClientId', $clientId->getValue()); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* @test |
208
|
|
|
*/ |
209
|
|
|
public function theEncryptedClientAssertionIsValidAndTheClientIdIsRetrieved() |
210
|
|
|
{ |
211
|
|
|
$method = $this->getMethod(); |
212
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
213
|
|
|
$request->getHeader('Authorization')->willReturn([]); |
214
|
|
|
$request->getParsedBody()->willReturn([ |
215
|
|
|
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', |
216
|
|
|
'client_assertion' => $this->createValidClientAssertionSignedAndEncryptedByTheClient(), |
217
|
|
|
]); |
218
|
|
|
|
219
|
|
|
$clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials); |
220
|
|
|
self::assertEquals('ClientId', $clientId->getValue()); |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* @test |
225
|
|
|
*/ |
226
|
|
|
/*public function theClientUsesAnotherAuthenticationMethod() |
|
|
|
|
227
|
|
|
{ |
228
|
|
|
$method = $this->getMethod(); |
229
|
|
|
$manager = new AuthenticationMethodManager(); |
230
|
|
|
$method->add($method); |
231
|
|
|
$client = Client::createEmpty(); |
232
|
|
|
$client = $client->create( |
233
|
|
|
ClientId::create('CLIENT_ID'), |
234
|
|
|
DataBag::create([ |
235
|
|
|
'client_secret' => 'CLIENT_SECRET', |
236
|
|
|
'token_endpoint_auth_method' => 'client_secret_post', |
237
|
|
|
]), |
238
|
|
|
UserAccountId::create('USER_ACCOUNT_ID') |
239
|
|
|
); |
240
|
|
|
$request = $this->prophesize(ServerRequestInterface::class); |
241
|
|
|
$request->getParsedBody()->willReturn([ |
242
|
|
|
'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', |
243
|
|
|
'client_assertion' => 'CLIENT_SECRET', |
244
|
|
|
]); |
245
|
|
|
|
246
|
|
|
self::assertFalse($method->isClientAuthenticated($request->reveal(), $client, $method, 'CLIENT_SECRET')); |
247
|
|
|
}*/ |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* @test |
251
|
|
|
*/ |
252
|
|
|
public function theClientConfigurationCanBeCheckedWithClientSecretJwt() |
253
|
|
|
{ |
254
|
|
|
$method = $this->getMethod(); |
255
|
|
|
$commandParameters = DataBag::create([ |
256
|
|
|
'token_endpoint_auth_method' => 'client_secret_jwt', |
257
|
|
|
]); |
258
|
|
|
$validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([])); |
259
|
|
|
|
260
|
|
|
self::assertTrue($validatedParameters->has('client_secret')); |
261
|
|
|
self::assertTrue($validatedParameters->has('client_secret_expires_at')); |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
/** |
265
|
|
|
* @test |
266
|
|
|
* @expectedException \InvalidArgumentException |
267
|
|
|
* @expectedExceptionMessage Either the parameter "jwks" or "jwks_uri" must be set. |
268
|
|
|
*/ |
269
|
|
|
public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfBothJwksAndJwksUriAreSet() |
270
|
|
|
{ |
271
|
|
|
$method = $this->getMethod(); |
272
|
|
|
$commandParameters = DataBag::create([ |
273
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
274
|
|
|
'jwks' => 'foo', |
275
|
|
|
'jwks_uri' => 'bar', |
276
|
|
|
]); |
277
|
|
|
$method->checkClientConfiguration($commandParameters, DataBag::create([])); |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
/** |
281
|
|
|
* @test |
282
|
|
|
* @expectedException \InvalidArgumentException |
283
|
|
|
* @expectedExceptionMessage Either the parameter "jwks" or "jwks_uri" must be set. |
284
|
|
|
*/ |
285
|
|
|
public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfNoneOfTheJwksAndJwksUriAreSet() |
286
|
|
|
{ |
287
|
|
|
$method = $this->getMethod(); |
288
|
|
|
$commandParameters = DataBag::create([ |
289
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
290
|
|
|
]); |
291
|
|
|
$method->checkClientConfiguration($commandParameters, DataBag::create([])); |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* @test |
296
|
|
|
* @expectedException \InvalidArgumentException |
297
|
|
|
* @expectedExceptionMessage The parameter "jwks" must be a valid JWKSet object. |
298
|
|
|
*/ |
299
|
|
|
public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksIsNotAValidKeySet() |
300
|
|
|
{ |
301
|
|
|
$method = $this->getMethod(); |
302
|
|
|
$commandParameters = DataBag::create([ |
303
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
304
|
|
|
'jwks' => 'foo', |
305
|
|
|
]); |
306
|
|
|
$method->checkClientConfiguration($commandParameters, DataBag::create([])); |
307
|
|
|
} |
308
|
|
|
|
309
|
|
|
/** |
310
|
|
|
* @test |
311
|
|
|
*/ |
312
|
|
|
public function theClientConfigurationCanBeCheckedWithPrivateKeyJwtIfJwksIsValid() |
313
|
|
|
{ |
314
|
|
|
$method = $this->getMethod(); |
315
|
|
|
$commandParameters = DataBag::create([ |
316
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
317
|
|
|
'jwks' => '{"keys":[{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"},{"kty":"oct","k":"dIx5cdLn-dAgNkvfZSiroJuy5oykHO4hDnYpmwlMq6A"}]}', |
318
|
|
|
]); |
319
|
|
|
$validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([])); |
320
|
|
|
|
321
|
|
|
self::assertTrue($validatedParameters->has('jwks')); |
322
|
|
|
self::assertEquals('{"keys":[{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"},{"kty":"oct","k":"dIx5cdLn-dAgNkvfZSiroJuy5oykHO4hDnYpmwlMq6A"}]}', $validatedParameters->get('jwks')); |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* @test |
327
|
|
|
* @expectedException \InvalidArgumentException |
328
|
|
|
* @expectedExceptionMessage Distant key sets cannot be used. Please use "jwks" instead of "jwks_uri". |
329
|
|
|
*/ |
330
|
|
|
public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriFactoryIsNotAvailable() |
331
|
|
|
{ |
332
|
|
|
$method = $this->getMethod(); |
333
|
|
|
$commandParameters = DataBag::create([ |
334
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
335
|
|
|
'jwks_uri' => 'foo', |
336
|
|
|
]); |
337
|
|
|
$method->checkClientConfiguration($commandParameters, DataBag::create([])); |
338
|
|
|
} |
339
|
|
|
|
340
|
|
|
/** |
341
|
|
|
* @test |
342
|
|
|
* @expectedException \InvalidArgumentException |
343
|
|
|
* @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet. |
344
|
|
|
*/ |
345
|
|
|
public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriIsNotValid() |
346
|
|
|
{ |
347
|
|
|
$method = clone $this->getMethod(); |
348
|
|
|
$httpClient = $this->getHttpClient(); |
349
|
|
|
$method->enableJkuSupport( |
350
|
|
|
$this->getJkuFactory($httpClient) |
351
|
|
|
); |
352
|
|
|
$commandParameters = DataBag::create([ |
353
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
354
|
|
|
'jwks_uri' => 'foo', |
355
|
|
|
]); |
356
|
|
|
$method->checkClientConfiguration($commandParameters, DataBag::create([])); |
357
|
|
|
} |
358
|
|
|
|
359
|
|
|
/** |
360
|
|
|
* @test |
361
|
|
|
* @expectedException \InvalidArgumentException |
362
|
|
|
* @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet. |
363
|
|
|
*/ |
364
|
|
|
public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriCannotBeReached() |
365
|
|
|
{ |
366
|
|
|
$method = clone $this->getMethod(); |
367
|
|
|
$httpClient = $this->getHttpClient(); |
368
|
|
|
$httpClient->addResponse(new Response('php://memory', 404)); |
369
|
|
|
$method->enableJkuSupport( |
370
|
|
|
$this->getJkuFactory($httpClient) |
371
|
|
|
); |
372
|
|
|
$commandParameters = DataBag::create([ |
373
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
374
|
|
|
'jwks_uri' => 'https://www.foo.com/bad-url.jwkset', |
375
|
|
|
]); |
376
|
|
|
$method->checkClientConfiguration($commandParameters, DataBag::create([])); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
/** |
380
|
|
|
* @test |
381
|
|
|
* @expectedException \InvalidArgumentException |
382
|
|
|
* @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet. |
383
|
|
|
*/ |
384
|
|
|
public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesNotContainAValidKeySet() |
385
|
|
|
{ |
386
|
|
|
$method = clone $this->getMethod(); |
387
|
|
|
$httpClient = $this->getHttpClient(); |
388
|
|
|
$stream = fopen('php://memory', 'w+'); |
389
|
|
|
fwrite($stream, 'Hello World!'); |
390
|
|
|
rewind($stream); |
391
|
|
|
$httpClient->addResponse(new Response($stream, 200)); |
392
|
|
|
$method->enableJkuSupport( |
393
|
|
|
$this->getJkuFactory($httpClient) |
394
|
|
|
); |
395
|
|
|
$commandParameters = DataBag::create([ |
396
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
397
|
|
|
'jwks_uri' => 'https://www.foo.com/index.html', |
398
|
|
|
]); |
399
|
|
|
$method->checkClientConfiguration($commandParameters, DataBag::create([])); |
400
|
|
|
} |
401
|
|
|
|
402
|
|
|
/** |
403
|
|
|
* @test |
404
|
|
|
* @expectedException \InvalidArgumentException |
405
|
|
|
* @expectedExceptionMessage The distant key set is empty. |
406
|
|
|
*/ |
407
|
|
|
public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesContainAnEmptyKeySet() |
408
|
|
|
{ |
409
|
|
|
$method = clone $this->getMethod(); |
410
|
|
|
$httpClient = $this->getHttpClient(); |
411
|
|
|
$stream = fopen('php://memory', 'w+'); |
412
|
|
|
fwrite($stream, '{"keys":[]}'); |
413
|
|
|
rewind($stream); |
414
|
|
|
$httpClient->addResponse(new Response($stream, 200)); |
415
|
|
|
$method->enableJkuSupport( |
416
|
|
|
$this->getJkuFactory($httpClient) |
417
|
|
|
); |
418
|
|
|
$commandParameters = DataBag::create([ |
419
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
420
|
|
|
'jwks_uri' => 'https://www.foo.com/index.html', |
421
|
|
|
]); |
422
|
|
|
$method->checkClientConfiguration($commandParameters, DataBag::create([])); |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* @test |
427
|
|
|
*/ |
428
|
|
|
public function theClientConfigurationCanBeCheckedWithPrivateKeyJwt() |
429
|
|
|
{ |
430
|
|
|
$method = clone $this->getMethod(); |
431
|
|
|
$httpClient = $this->getHttpClient(); |
432
|
|
|
$stream = fopen('php://memory', 'w+'); |
433
|
|
|
fwrite($stream, '{"keys":[{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"},{"kty":"oct","k":"dIx5cdLn-dAgNkvfZSiroJuy5oykHO4hDnYpmwlMq6A"}]}'); |
434
|
|
|
rewind($stream); |
435
|
|
|
$httpClient->addResponse(new Response($stream, 200)); |
436
|
|
|
$method->enableJkuSupport( |
437
|
|
|
$this->getJkuFactory($httpClient) |
438
|
|
|
); |
439
|
|
|
$commandParameters = DataBag::create([ |
440
|
|
|
'token_endpoint_auth_method' => 'private_key_jwt', |
441
|
|
|
'jwks_uri' => 'https://www.foo.com/keyset', |
442
|
|
|
]); |
443
|
|
|
$validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([])); |
444
|
|
|
|
445
|
|
|
self::assertTrue($validatedParameters->has('jwks_uri')); |
446
|
|
|
self::assertEquals('https://www.foo.com/keyset', $validatedParameters->get('jwks_uri')); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* @var null|ClientAssertionJwt |
451
|
|
|
*/ |
452
|
|
|
private $method = null; |
453
|
|
|
|
454
|
|
|
/** |
455
|
|
|
* @return ClientAssertionJwt |
456
|
|
|
*/ |
457
|
|
|
private function getMethod(): ClientAssertionJwt |
458
|
|
|
{ |
459
|
|
|
if (null === $this->method) { |
460
|
|
|
$this->method = new ClientAssertionJwt( |
461
|
|
|
new StandardConverter(), |
462
|
|
|
new JWSVerifier(AlgorithmManager::create([new HS256(), new RS256()])), |
463
|
|
|
HeaderCheckerManager::create([], [new JWSTokenSupport()]), |
464
|
|
|
ClaimCheckerManager::create([]), |
465
|
|
|
3600 |
466
|
|
|
); |
467
|
|
|
|
468
|
|
|
$this->method->enableEncryptedAssertions( |
469
|
|
|
new JWELoader( |
470
|
|
|
JWESerializerManager::create([new \Jose\Component\Encryption\Serializer\CompactSerializer(new StandardConverter())]), |
471
|
|
|
new JWEDecrypter( |
472
|
|
|
AlgorithmManager::create([new RSAOAEP256()]), |
473
|
|
|
AlgorithmManager::create([new A256CBCHS512()]), |
474
|
|
|
CompressionMethodManager::create([new Deflate()]) |
475
|
|
|
), |
476
|
|
|
HeaderCheckerManager::create([], [new JWETokenSupport()]) |
477
|
|
|
), |
478
|
|
|
JWKSet::createFromKeys([JWK::create([ |
479
|
|
|
'kty' => 'RSA', |
480
|
|
|
'kid' => '[email protected]', |
481
|
|
|
'use' => 'enc', |
482
|
|
|
'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE', |
483
|
|
|
'e' => 'AQAB', |
484
|
|
|
'alg' => 'RSA-OAEP-256', |
485
|
|
|
'd' => 'n7fzJc3_WG59VEOBTkayzuSMM780OJQuZjN_KbH8lOZG25ZoA7T4Bxcc0xQn5oZE5uSCIwg91oCt0JvxPcpmqzaJZg1nirjcWZ-oBtVk7gCAWq-B3qhfF3izlbkosrzjHajIcY33HBhsy4_WerrXg4MDNE4HYojy68TcxT2LYQRxUOCf5TtJXvM8olexlSGtVnQnDRutxEUCwiewfmmrfveEogLx9EA-KMgAjTiISXxqIXQhWUQX1G7v_mV_Hr2YuImYcNcHkRvp9E7ook0876DhkO8v4UOZLwA1OlUX98mkoqwc58A_Y2lBYbVx1_s5lpPsEqbbH-nqIjh1fL0gdNfihLxnclWtW7pCztLnImZAyeCWAG7ZIfv-Rn9fLIv9jZ6r7r-MSH9sqbuziHN2grGjD_jfRluMHa0l84fFKl6bcqN1JWxPVhzNZo01yDF-1LiQnqUYSepPf6X3a2SOdkqBRiquE6EvLuSYIDpJq3jDIsgoL8Mo1LoomgiJxUwL_GWEOGu28gplyzm-9Q0U0nyhEf1uhSR8aJAQWAiFImWH5W_IQT9I7-yrindr_2fWQ_i1UgMsGzA7aOGzZfPljRy6z-tY_KuBG00-28S_aWvjyUc-Alp8AUyKjBZ-7CWH32fGWK48j1t-zomrwjL_mnhsPbGs0c9WsWgRzI-K8gE', |
486
|
|
|
'p' => '7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ', |
487
|
|
|
'q' => 'zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q', |
488
|
|
|
'dp' => '19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ', |
489
|
|
|
'dq' => 'S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ', |
490
|
|
|
'qi' => 'FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA', |
491
|
|
|
])]), |
492
|
|
|
false |
493
|
|
|
); |
494
|
|
|
} |
495
|
|
|
|
496
|
|
|
return $this->method; |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
/** |
500
|
|
|
* @param \Http\Mock\Client $client |
501
|
|
|
* |
502
|
|
|
* @return JKUFactory |
503
|
|
|
*/ |
504
|
|
|
private function getJkuFactory(\Http\Mock\Client $client): JKUFactory |
505
|
|
|
{ |
506
|
|
|
return new JKUFactory( |
507
|
|
|
new StandardConverter(), |
508
|
|
|
$client, |
509
|
|
|
new DiactorosMessageFactory() |
510
|
|
|
); |
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
/** |
514
|
|
|
* @return \Http\Mock\Client |
515
|
|
|
*/ |
516
|
|
|
private function getHttpClient(): \Http\Mock\Client |
517
|
|
|
{ |
518
|
|
|
return new \Http\Mock\Client( |
519
|
|
|
new DiactorosMessageFactory() |
520
|
|
|
); |
521
|
|
|
} |
522
|
|
|
|
523
|
|
|
/** |
524
|
|
|
* @return string |
525
|
|
|
*/ |
526
|
|
|
private function createValidClientAssertionSignedByTheClient(): string |
527
|
|
|
{ |
528
|
|
|
$jsonConverter = new StandardConverter(); |
529
|
|
|
$jwsBuilder = new JWSBuilder( |
530
|
|
|
$jsonConverter, |
531
|
|
|
AlgorithmManager::create([ |
532
|
|
|
new HS256(), |
533
|
|
|
]) |
534
|
|
|
); |
535
|
|
|
|
536
|
|
|
$jws = $jwsBuilder |
537
|
|
|
->create() |
538
|
|
|
->withPayload($jsonConverter->encode([ |
539
|
|
|
'iss' => 'ClientId', |
540
|
|
|
'sub' => 'ClientId', |
541
|
|
|
'aud' => 'My Server', |
542
|
|
|
'exp' => time() + 3600, |
543
|
|
|
])) |
544
|
|
|
->addSignature( |
545
|
|
|
JWK::createFromJson('{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"}'), |
546
|
|
|
['alg' => 'HS256'] |
547
|
|
|
) |
548
|
|
|
->build(); |
549
|
|
|
|
550
|
|
|
$serializer = new CompactSerializer($jsonConverter); |
551
|
|
|
|
552
|
|
|
return $serializer->serialize($jws, 0); |
553
|
|
|
} |
554
|
|
|
|
555
|
|
|
/** |
556
|
|
|
* @return string |
557
|
|
|
* |
558
|
|
|
* @throws \Exception |
559
|
|
|
*/ |
560
|
|
|
private function createValidClientAssertionSignedAndEncryptedByTheClient(): string |
561
|
|
|
{ |
562
|
|
|
$token = $this->createValidClientAssertionSignedByTheClient(); |
563
|
|
|
|
564
|
|
|
return $this->encryptAssertion($token); |
565
|
|
|
} |
566
|
|
|
|
567
|
|
|
/** |
568
|
|
|
* @return string |
569
|
|
|
* |
570
|
|
|
* @throws \Exception |
571
|
|
|
*/ |
572
|
|
|
private function createInvalidClientAssertionSignedByTheClient(): string |
573
|
|
|
{ |
574
|
|
|
$jsonConverter = new StandardConverter(); |
575
|
|
|
$jwsBuilder = new JWSBuilder( |
576
|
|
|
$jsonConverter, |
577
|
|
|
AlgorithmManager::create([ |
578
|
|
|
new HS256(), |
579
|
|
|
]) |
580
|
|
|
); |
581
|
|
|
|
582
|
|
|
$jws = $jwsBuilder |
583
|
|
|
->create() |
584
|
|
|
->withPayload($jsonConverter->encode([])) |
585
|
|
|
->addSignature( |
586
|
|
|
JWK::createFromJson('{"kty":"oct","k":"bJzb8RaN7TzPz001PeF0lw0ZoUJqbazGxMvBd_xzfms"}'), |
587
|
|
|
['alg' => 'HS256'] |
588
|
|
|
) |
589
|
|
|
->build(); |
590
|
|
|
|
591
|
|
|
$serializer = new CompactSerializer($jsonConverter); |
592
|
|
|
|
593
|
|
|
return $serializer->serialize($jws, 0); |
594
|
|
|
} |
595
|
|
|
|
596
|
|
|
/** |
597
|
|
|
* @return string |
598
|
|
|
* |
599
|
|
|
* @throws \Exception |
600
|
|
|
*/ |
601
|
|
|
private function createInvalidClientAssertionSignedAndEncryptedByTheClient(): string |
602
|
|
|
{ |
603
|
|
|
$token = $this->createInvalidClientAssertionSignedByTheClient(); |
604
|
|
|
|
605
|
|
|
return $this->encryptAssertion($token); |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
/** |
609
|
|
|
* @param string $assertion |
610
|
|
|
* |
611
|
|
|
* @return string |
612
|
|
|
* |
613
|
|
|
* @throws \Exception |
614
|
|
|
*/ |
615
|
|
|
private function encryptAssertion(string $assertion): string |
616
|
|
|
{ |
617
|
|
|
$jsonConverter = new StandardConverter(); |
618
|
|
|
$jweBuilder = new JWEBuilder( |
619
|
|
|
$jsonConverter, |
620
|
|
|
AlgorithmManager::create([new RSAOAEP256()]), |
621
|
|
|
AlgorithmManager::create([new A256CBCHS512()]), |
622
|
|
|
CompressionMethodManager::create([new Deflate()]) |
623
|
|
|
); |
624
|
|
|
$jwe = $jweBuilder->create() |
625
|
|
|
->withPayload($assertion) |
626
|
|
|
->withSharedProtectedHeader(['alg' => 'RSA-OAEP-256', 'enc' =>'A256CBC-HS512']) |
627
|
|
|
->addRecipient(JWK::create([ |
628
|
|
|
'kty' => 'RSA', |
629
|
|
|
'kid' => '[email protected]', |
630
|
|
|
'use' => 'enc', |
631
|
|
|
'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE', |
632
|
|
|
'e' => 'AQAB', |
633
|
|
|
'alg' => 'RSA-OAEP-256', |
634
|
|
|
])) |
635
|
|
|
->build(); |
636
|
|
|
|
637
|
|
|
$serializer = new \Jose\Component\Encryption\Serializer\CompactSerializer($jsonConverter); |
638
|
|
|
|
639
|
|
|
return $serializer->serialize($jwe, 0); |
640
|
|
|
} |
641
|
|
|
} |
642
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.