Failed Conditions
Push — ng ( eeabaf...fe50aa )
by Florent
05:34
created

theClientWithClientSecretIsAuthenticated()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 28
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 8.8571
c 0
b 0
f 0
cc 1
eloc 20
nc 1
nop 0
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\JWS;
36
use Jose\Component\Signature\JWSBuilder;
37
use Jose\Component\Signature\JWSTokenSupport;
38
use Jose\Component\Signature\JWSVerifier;
39
use Jose\Component\Signature\Serializer\CompactSerializer;
40
use OAuth2Framework\Component\Core\Client\Client;
41
use OAuth2Framework\Component\Core\Client\ClientId;
42
use OAuth2Framework\Component\Core\DataBag\DataBag;
43
use OAuth2Framework\Component\Core\Exception\OAuth2Exception;
44
use OAuth2Framework\Component\Core\UserAccount\UserAccountId;
45
use OAuth2Framework\Component\ClientAuthentication\AuthenticationMethodManager;
46
use OAuth2Framework\Component\ClientAuthentication\ClientAssertionJwt;
47
use OAuth2Framework\Component\TrustedIssuer\TrustedIssuer;
48
use OAuth2Framework\Component\TrustedIssuer\TrustedIssuerRepository;
49
use PHPUnit\Framework\TestCase;
50
use Psr\Http\Message\ServerRequestInterface;
51
use Zend\Diactoros\Response;
52
53
/**
54
 * @group TokenEndpoint
55
 * @group ClientAuthentication
56
 */
57
class ClientAssertionJwtAuthenticationMethodTest extends TestCase
58
{
59
    /**
60
     * @test
61
     */
62
    public function genericCalls()
63
    {
64
        $method = $this->getMethod();
65
66
        self::assertEquals([], $method->getSchemesParameters());
67
        self::assertEquals(['client_secret_jwt', 'private_key_jwt'], $method->getSupportedMethods());
68
        self::assertEquals(['HS256', 'RS256'], $method->getSupportedSignatureAlgorithms());
69
        self::assertEquals([], $method->getSupportedKeyEncryptionAlgorithms());
70
        self::assertEquals([], $method->getSupportedContentEncryptionAlgorithms());
71
    }
72
73
    /**
74
     * @test
75
     */
76
    public function genericCallsWithEncryptionSupport()
77
    {
78
        $method = $this->getMethodWithEncryptionSupport(false);
79
80
        self::assertEquals([], $method->getSchemesParameters());
81
        self::assertEquals(['client_secret_jwt', 'private_key_jwt'], $method->getSupportedMethods());
82
        self::assertEquals(['HS256', 'RS256'], $method->getSupportedSignatureAlgorithms());
83
        self::assertEquals(['RSA-OAEP-256'], $method->getSupportedKeyEncryptionAlgorithms());
84
        self::assertEquals(['A256CBC-HS512'], $method->getSupportedContentEncryptionAlgorithms());
85
    }
86
87
    /**
88
     * @test
89
     */
90
    public function theClientIdCannotBeFoundInTheRequest()
91
    {
92
        $method = $this->getMethod();
93
        $request = $this->prophesize(ServerRequestInterface::class);
94
        $request->getHeader('Authorization')->willReturn([]);
95
        $request->getParsedBody()->willReturn([]);
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 theClientAssertionTypeIsNotSupported()
106
    {
107
        $method = $this->getMethod();
108
        $request = $this->prophesize(ServerRequestInterface::class);
109
        $request->getHeader('Authorization')->willReturn([]);
110
        $request->getParsedBody()->willReturn([
111
            'client_assertion_type' => 'foo',
112
        ]);
113
114
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
115
        self::assertNull($clientId);
116
        self::assertNull($credentials);
117
    }
118
119
    /**
120
     * @test
121
     */
122
    public function theClientAssertionIsMissing()
123
    {
124
        $method = $this->getMethod();
125
        $request = $this->prophesize(ServerRequestInterface::class);
126
        $request->getHeader('Authorization')->willReturn([]);
127
        $request->getParsedBody()->willReturn([
128
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
129
        ]);
130
131
        try {
132
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
133
            $this->fail('An OAuth2 exception should be thrown.');
134
        } catch (OAuth2Exception $e) {
135
            self::assertEquals('invalid_request', $e->getMessage());
136
            self::assertEquals('Parameter "client_assertion" is missing.', $e->getErrorDescription());
137
        }
138
    }
139
140
    /**
141
     * @test
142
     */
143
    public function theClientAssertionIsInvalid()
144
    {
145
        $method = $this->getMethod();
146
        $request = $this->prophesize(ServerRequestInterface::class);
147
        $request->getHeader('Authorization')->willReturn([]);
148
        $request->getParsedBody()->willReturn([
149
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
150
            'client_assertion' => 'foo',
151
        ]);
152
153
        try {
154
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
155
            $this->fail('An OAuth2 exception should be thrown.');
156
        } catch (OAuth2Exception $e) {
157
            self::assertEquals('invalid_request', $e->getMessage());
158
            self::assertEquals('Unable to load, decrypt or verify the client assertion.', $e->getErrorDescription());
159
        }
160
    }
161
162
    /**
163
     * @test
164
     */
165
    public function theClientAssertionSignedByTheClientIsInvalidBecauseOfMissingClaims()
166
    {
167
        $assertion = $this->serializeJWS(
168
            $this->createInvalidClientAssertionSignedByTheClient()
169
        );
170
        $method = $this->getMethod();
171
        $request = $this->prophesize(ServerRequestInterface::class);
172
        $request->getHeader('Authorization')->willReturn([]);
173
        $request->getParsedBody()->willReturn([
174
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
175
            'client_assertion' => $assertion,
176
        ]);
177
178
        try {
179
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
180
            $this->fail('An OAuth2 exception should be thrown.');
181
        } catch (OAuth2Exception $e) {
182
            self::assertEquals('invalid_request', $e->getMessage());
183
            self::assertEquals('The following claim(s) is/are mandatory: "iss, sub, aud, exp".', $e->getErrorDescription());
184
        }
185
    }
186
187
    /**
188
     * @test
189
     */
190
    public function theClientAssertionSignedByTheClientIsRejectedBecauseEncryptionIsMandatory()
191
    {
192
        $assertion = $this->serializeJWS(
193
            $this->createInvalidClientAssertionSignedByTheClient()
194
        );
195
        $method = $this->getMethodWithEncryptionSupport(true);
196
        $request = $this->prophesize(ServerRequestInterface::class);
197
        $request->getHeader('Authorization')->willReturn([]);
198
        $request->getParsedBody()->willReturn([
199
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
200
            'client_assertion' => $assertion,
201
        ]);
202
203
        try {
204
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
205
            $this->fail('An OAuth2 exception should be thrown.');
206
        } catch (OAuth2Exception $e) {
207
            self::assertEquals('invalid_request', $e->getMessage());
208
            self::assertEquals('The encryption of the assertion is mandatory but the decryption of the assertion failed.', $e->getErrorDescription());
209
        }
210
    }
211
212
    /**
213
     * @test
214
     */
215
    public function theEncryptedClientAssertionSignedAndEncryptedByTheClientIsInvalidBecauseOfMissingClaims()
216
    {
217
        $assertion = $this->encryptAssertion(
218
            $this->serializeJWS(
219
                $this->createInvalidClientAssertionSignedByTheClient()
220
            )
221
        );
222
        $method = $this->getMethodWithEncryptionSupport(false);
223
        $request = $this->prophesize(ServerRequestInterface::class);
224
        $request->getHeader('Authorization')->willReturn([]);
225
        $request->getParsedBody()->willReturn([
226
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
227
            'client_assertion' => $assertion,
228
        ]);
229
230
        try {
231
            $method->findClientIdAndCredentials($request->reveal(), $credentials);
232
            $this->fail('An OAuth2 exception should be thrown.');
233
        } catch (OAuth2Exception $e) {
234
            self::assertEquals('invalid_request', $e->getMessage());
235
            self::assertEquals('The following claim(s) is/are mandatory: "iss, sub, aud, exp".', $e->getErrorDescription());
236
        }
237
    }
238
239
    /**
240
     * @test
241
     */
242
    public function theClientAssertionIsValidAndTheClientIdIsRetrieved()
243
    {
244
        $assertion = $this->serializeJWS(
245
            $this->createValidClientAssertionSignedByTheClient()
246
        );
247
        $method = $this->getMethod();
248
        $request = $this->prophesize(ServerRequestInterface::class);
249
        $request->getHeader('Authorization')->willReturn([]);
250
        $request->getParsedBody()->willReturn([
251
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
252
            'client_assertion' => $assertion,
253
        ]);
254
255
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
256
        self::assertEquals('ClientId', $clientId->getValue());
257
    }
258
259
    /**
260
     * @test
261
     */
262
    public function theEncryptedClientAssertionIsValidAndTheClientIdIsRetrieved()
263
    {
264
        $assertion = $this->encryptAssertion(
265
            $this->serializeJWS(
266
                $this->createValidClientAssertionSignedByTheClient()
267
            )
268
        );
269
        $method = $this->getMethodWithEncryptionSupport(false);
270
        $request = $this->prophesize(ServerRequestInterface::class);
271
        $request->getHeader('Authorization')->willReturn([]);
272
        $request->getParsedBody()->willReturn([
273
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
274
            'client_assertion' => $assertion,
275
        ]);
276
277
        $clientId = $method->findClientIdAndCredentials($request->reveal(), $credentials);
278
        self::assertEquals('ClientId', $clientId->getValue());
279
    }
280
281
    /**
282
     * @test
283
     */
284
    public function theClientUsesAnotherAuthenticationMethod()
285
    {
286
        $jws = $this->createInvalidClientAssertionSignedByTheClient();
287
        $assertion = $this->encryptAssertion(
288
            $this->serializeJWS(
289
                $jws
290
            )
291
        );
292
        $method = $this->getMethodWithEncryptionSupport(false);
293
        $manager = new AuthenticationMethodManager();
294
        $manager->add($method);
295
        $client = Client::createEmpty();
296
        $client = $client->create(
297
            ClientId::create('CLIENT_ID'),
298
            DataBag::create([
299
                'client_secret' => 'CLIENT_SECRET',
300
                'token_endpoint_auth_method' => 'client_secret_post',
301
            ]),
302
            UserAccountId::create('USER_ACCOUNT_ID')
303
        );
304
        $request = $this->prophesize(ServerRequestInterface::class);
305
        $request->getParsedBody()->willReturn([
306
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
307
            'client_assertion'      => $assertion,
308
        ]);
309
310
        self::assertFalse($manager->isClientAuthenticated($request->reveal(), $client, $method, $jws));
311
    }
312
313
    /**
314
     * @test
315
     */
316
    public function theClientWithPrivateKeyIsAuthenticated()
317
    {
318
        $jws = $this->createValidClientAssertionSignedByTheClient();
319
        $assertion = $this->encryptAssertion(
320
            $this->serializeJWS(
321
                $jws
322
            )
323
        );
324
        $method = $this->getMethodWithEncryptionSupport(false);
325
        $manager = new AuthenticationMethodManager();
326
        $manager->add($method);
327
        $client = Client::createEmpty();
328
        $client = $client->create(
329
            ClientId::create('CLIENT_ID'),
330
            DataBag::create([
331
                'token_endpoint_auth_method' => 'private_key_jwt',
332
                'jwks' => '{"keys":[{"kty":"oct","k":"U0VDUkVU"}]}',
333
            ]),
334
            UserAccountId::create('USER_ACCOUNT_ID')
335
        );
336
        $request = $this->prophesize(ServerRequestInterface::class);
337
        $request->getParsedBody()->willReturn([
338
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
339
            'client_assertion'      => $assertion,
340
        ]);
341
342
        self::assertTrue($manager->isClientAuthenticated($request->reveal(), $client, $method, $jws));
343
    }
344
345
    /**
346
     * @test
347
     */
348
    public function theClientWithClientSecretIsAuthenticated()
349
    {
350
        $jws = $this->createValidClientAssertionSignedByTheClient();
351
        $assertion = $this->encryptAssertion(
352
            $this->serializeJWS(
353
                $jws
354
            )
355
        );
356
        $method = $this->getMethodWithEncryptionSupport(false);
357
        $manager = new AuthenticationMethodManager();
358
        $manager->add($method);
359
        $client = Client::createEmpty();
360
        $client = $client->create(
361
            ClientId::create('CLIENT_ID'),
362
            DataBag::create([
363
                'token_endpoint_auth_method' => 'client_secret_jwt',
364
                'client_secret' => 'SECRET',
365
            ]),
366
            UserAccountId::create('USER_ACCOUNT_ID')
367
        );
368
        $request = $this->prophesize(ServerRequestInterface::class);
369
        $request->getParsedBody()->willReturn([
370
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
371
            'client_assertion'      => $assertion,
372
        ]);
373
374
        self::assertTrue($manager->isClientAuthenticated($request->reveal(), $client, $method, $jws));
375
    }
376
377
    /**
378
     * @test
379
     */
380
    public function theClientWithTrustedIssuerAssertionIsAuthenticated()
381
    {
382
        $jws = $this->createValidClientAssertionSignedByATrustedIssuer();
383
        $assertion = $this->serializeJWS(
384
            $jws
385
        );
386
        $method = $this->getMethodWithTrustedIssuerSupport();
387
        $manager = new AuthenticationMethodManager();
388
        $manager->add($method);
389
        $client = Client::createEmpty();
390
        $client = $client->create(
391
            ClientId::create('CLIENT_ID'),
392
            DataBag::create([
393
                'token_endpoint_auth_method' => 'client_secret_jwt',
394
            ]),
395
            UserAccountId::create('USER_ACCOUNT_ID')
396
        );
397
        $request = $this->prophesize(ServerRequestInterface::class);
398
        $request->getParsedBody()->willReturn([
399
            'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
400
            'client_assertion'      => $assertion,
401
        ]);
402
403
        self::assertTrue($manager->isClientAuthenticated($request->reveal(), $client, $method, $jws));
404
    }
405
406
    /**
407
     * @test
408
     */
409
    public function theClientConfigurationCanBeCheckedWithClientSecretJwt()
410
    {
411
        $method = $this->getMethod();
412
        $commandParameters = DataBag::create([
413
            'token_endpoint_auth_method' => 'client_secret_jwt',
414
        ]);
415
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
416
417
        self::assertTrue($validatedParameters->has('client_secret'));
418
        self::assertTrue($validatedParameters->has('client_secret_expires_at'));
419
    }
420
421
    /**
422
     * @test
423
     * @expectedException \InvalidArgumentException
424
     * @expectedExceptionMessage Either the parameter "jwks" or "jwks_uri" must be set.
425
     */
426
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfBothJwksAndJwksUriAreSet()
427
    {
428
        $method = $this->getMethod();
429
        $commandParameters = DataBag::create([
430
            'token_endpoint_auth_method' => 'private_key_jwt',
431
            'jwks' => 'foo',
432
            'jwks_uri' => 'bar',
433
        ]);
434
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
435
436
        self::assertTrue($validatedParameters->has('token_endpoint_auth_method'));
437
        self::assertEquals('private_key_jwt', $validatedParameters->get('token_endpoint_auth_method'));
438
    }
439
440
    /**
441
     * @test
442
     * @expectedException \InvalidArgumentException
443
     * @expectedExceptionMessage Either the parameter "jwks" or "jwks_uri" must be set.
444
     */
445
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfBothJwksAndJwksUriAreNotSetBecauseTrustedIssuerSupportIsDisabled()
446
    {
447
        $method = $this->getMethod();
448
        $commandParameters = DataBag::create([
449
            'token_endpoint_auth_method' => 'private_key_jwt',
450
        ]);
451
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
452
453
        self::assertTrue($validatedParameters->has('token_endpoint_auth_method'));
454
        self::assertEquals('private_key_jwt', $validatedParameters->get('token_endpoint_auth_method'));
455
    }
456
457
    /**
458
     * @test
459
     */
460
    public function theClientConfigurationCanBeCheckedWithPrivateKeyJwtIfBothJwksAndJwksUriAreNotSetBecauseTrustedIssuerSupportIsEnabled()
461
    {
462
        $method = $this->getMethodWithTrustedIssuerSupport();
463
        $commandParameters = DataBag::create([
464
            'token_endpoint_auth_method' => 'private_key_jwt',
465
        ]);
466
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
467
468
        self::assertTrue($validatedParameters->has('token_endpoint_auth_method'));
469
        self::assertEquals('private_key_jwt', $validatedParameters->get('token_endpoint_auth_method'));
470
    }
471
472
    /**
473
     * @test
474
     * @expectedException \InvalidArgumentException
475
     * @expectedExceptionMessage The parameter "jwks" must be a valid JWKSet object.
476
     */
477
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksIsNotAValidKeySet()
478
    {
479
        $method = $this->getMethod();
480
        $commandParameters = DataBag::create([
481
            'token_endpoint_auth_method' => 'private_key_jwt',
482
            'jwks' => 'foo',
483
        ]);
484
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
485
    }
486
487
    /**
488
     * @test
489
     */
490
    public function theClientConfigurationCanBeCheckedWithPrivateKeyJwtIfJwksIsValid()
491
    {
492
        $method = $this->getMethod();
493
        $commandParameters = DataBag::create([
494
            'token_endpoint_auth_method' => 'private_key_jwt',
495
            'jwks' => '{"keys":[{"kty":"oct","k":"U0VDUkVU"}]}',
496
        ]);
497
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
498
499
        self::assertTrue($validatedParameters->has('jwks'));
500
        self::assertEquals('{"keys":[{"kty":"oct","k":"U0VDUkVU"}]}', $validatedParameters->get('jwks'));
501
    }
502
503
    /**
504
     * @test
505
     * @expectedException \InvalidArgumentException
506
     * @expectedExceptionMessage Distant key sets cannot be used. Please use "jwks" instead of "jwks_uri".
507
     */
508
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriFactoryIsNotAvailable()
509
    {
510
        $method = $this->getMethod();
511
        $commandParameters = DataBag::create([
512
            'token_endpoint_auth_method' => 'private_key_jwt',
513
            'jwks_uri' => 'foo',
514
        ]);
515
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
516
    }
517
518
    /**
519
     * @test
520
     * @expectedException \InvalidArgumentException
521
     * @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet.
522
     */
523
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriIsNotValid()
524
    {
525
        $method = clone $this->getMethod();
526
        $httpClient = $this->getHttpClient();
527
        $method->enableJkuSupport(
528
            $this->getJkuFactory($httpClient)
529
        );
530
        $commandParameters = DataBag::create([
531
            'token_endpoint_auth_method' => 'private_key_jwt',
532
            'jwks_uri' => 'foo',
533
        ]);
534
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
535
    }
536
537
    /**
538
     * @test
539
     * @expectedException \InvalidArgumentException
540
     * @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet.
541
     */
542
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriCannotBeReached()
543
    {
544
        $method = clone $this->getMethod();
545
        $httpClient = $this->getHttpClient();
546
        $httpClient->addResponse(new Response('php://memory', 404));
547
        $method->enableJkuSupport(
548
            $this->getJkuFactory($httpClient)
549
        );
550
        $commandParameters = DataBag::create([
551
            'token_endpoint_auth_method' => 'private_key_jwt',
552
            'jwks_uri' => 'https://www.foo.com/bad-url.jwkset',
553
        ]);
554
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
555
    }
556
557
    /**
558
     * @test
559
     * @expectedException \InvalidArgumentException
560
     * @expectedExceptionMessage The parameter "jwks_uri" must be a valid uri to a JWKSet.
561
     */
562
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesNotContainAValidKeySet()
563
    {
564
        $method = clone $this->getMethod();
565
        $httpClient = $this->getHttpClient();
566
        $stream = fopen('php://memory', 'w+');
567
        fwrite($stream, 'Hello World!');
568
        rewind($stream);
569
        $httpClient->addResponse(new Response($stream, 200));
570
        $method->enableJkuSupport(
571
            $this->getJkuFactory($httpClient)
572
        );
573
        $commandParameters = DataBag::create([
574
            'token_endpoint_auth_method' => 'private_key_jwt',
575
            'jwks_uri' => 'https://www.foo.com/index.html',
576
        ]);
577
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
578
    }
579
580
    /**
581
     * @test
582
     * @expectedException \InvalidArgumentException
583
     * @expectedExceptionMessage The distant key set is empty.
584
     */
585
    public function theClientConfigurationCannotBeCheckedWithPrivateKeyJwtIfJwksUriDoesContainAnEmptyKeySet()
586
    {
587
        $method = clone $this->getMethod();
588
        $httpClient = $this->getHttpClient();
589
        $stream = fopen('php://memory', 'w+');
590
        fwrite($stream, '{"keys":[]}');
591
        rewind($stream);
592
        $httpClient->addResponse(new Response($stream, 200));
593
        $method->enableJkuSupport(
594
            $this->getJkuFactory($httpClient)
595
        );
596
        $commandParameters = DataBag::create([
597
            'token_endpoint_auth_method' => 'private_key_jwt',
598
            'jwks_uri' => 'https://www.foo.com/index.html',
599
        ]);
600
        $method->checkClientConfiguration($commandParameters, DataBag::create([]));
601
    }
602
603
    /**
604
     * @test
605
     */
606
    public function theClientConfigurationCanBeCheckedWithPrivateKeyJwt()
607
    {
608
        $method = clone $this->getMethod();
609
        $httpClient = $this->getHttpClient();
610
        $stream = fopen('php://memory', 'w+');
611
        fwrite($stream, '{"keys":[{"kty":"oct","k":"U0VDUkVU"}]}');
612
        rewind($stream);
613
        $httpClient->addResponse(new Response($stream, 200));
614
        $method->enableJkuSupport(
615
            $this->getJkuFactory($httpClient)
616
        );
617
        $commandParameters = DataBag::create([
618
            'token_endpoint_auth_method' => 'private_key_jwt',
619
            'jwks_uri' => 'https://www.foo.com/keyset',
620
        ]);
621
        $validatedParameters = $method->checkClientConfiguration($commandParameters, DataBag::create([]));
622
623
        self::assertTrue($validatedParameters->has('jwks_uri'));
624
        self::assertEquals('https://www.foo.com/keyset', $validatedParameters->get('jwks_uri'));
625
    }
626
627
    /**
628
     * @var null|ClientAssertionJwt
629
     */
630
    private $method = null;
631
632
    /**
633
     * @return ClientAssertionJwt
634
     */
635
    private function getMethod(): ClientAssertionJwt
636
    {
637
        if (null === $this->method) {
638
            $this->method = new ClientAssertionJwt(
639
                new StandardConverter(),
640
                new JWSVerifier(AlgorithmManager::create([new HS256(), new RS256()])),
641
                HeaderCheckerManager::create([], [new JWSTokenSupport()]),
642
                ClaimCheckerManager::create([]),
643
                3600
644
            );
645
        }
646
647
        return $this->method;
648
    }
649
650
    /**
651
     * @param bool $isRequired
652
     *
653
     * @return ClientAssertionJwt
654
     */
655
    private function getMethodWithEncryptionSupport(bool $isRequired): ClientAssertionJwt
656
    {
657
        $method = clone $this->getMethod();
658
659
        $method->enableEncryptedAssertions(
660
            new JWELoader(
661
                JWESerializerManager::create([new \Jose\Component\Encryption\Serializer\CompactSerializer(new StandardConverter())]),
662
                new JWEDecrypter(
663
                    AlgorithmManager::create([new RSAOAEP256()]),
664
                    AlgorithmManager::create([new A256CBCHS512()]),
665
                    CompressionMethodManager::create([new Deflate()])
666
                ),
667
                HeaderCheckerManager::create([], [new JWETokenSupport()])
668
            ),
669
            JWKSet::createFromKeys([JWK::create([
670
                'kty' => 'RSA',
671
                'kid' => '[email protected]',
672
                'use' => 'enc',
673
                'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE',
674
                'e' => 'AQAB',
675
                'alg' => 'RSA-OAEP-256',
676
                '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',
677
                'p' => '7_2v3OQZzlPFcHyYfLABQ3XP85Es4hCdwCkbDeltaUXgVy9l9etKghvM4hRkOvbb01kYVuLFmxIkCDtpi-zLCYAdXKrAK3PtSbtzld_XZ9nlsYa_QZWpXB_IrtFjVfdKUdMz94pHUhFGFj7nr6NNxfpiHSHWFE1zD_AC3mY46J961Y2LRnreVwAGNw53p07Db8yD_92pDa97vqcZOdgtybH9q6uma-RFNhO1AoiJhYZj69hjmMRXx-x56HO9cnXNbmzNSCFCKnQmn4GQLmRj9sfbZRqL94bbtE4_e0Zrpo8RNo8vxRLqQNwIy85fc6BRgBJomt8QdQvIgPgWCv5HoQ',
678
                'q' => 'zqOHk1P6WN_rHuM7ZF1cXH0x6RuOHq67WuHiSknqQeefGBA9PWs6ZyKQCO-O6mKXtcgE8_Q_hA2kMRcKOcvHil1hqMCNSXlflM7WPRPZu2qCDcqssd_uMbP-DqYthH_EzwL9KnYoH7JQFxxmcv5An8oXUtTwk4knKjkIYGRuUwfQTus0w1NfjFAyxOOiAQ37ussIcE6C6ZSsM3n41UlbJ7TCqewzVJaPJN5cxjySPZPD3Vp01a9YgAD6a3IIaKJdIxJS1ImnfPevSJQBE79-EXe2kSwVgOzvt-gsmM29QQ8veHy4uAqca5dZzMs7hkkHtw1z0jHV90epQJJlXXnH8Q',
679
                'dp' => '19oDkBh1AXelMIxQFm2zZTqUhAzCIr4xNIGEPNoDt1jK83_FJA-xnx5kA7-1erdHdms_Ef67HsONNv5A60JaR7w8LHnDiBGnjdaUmmuO8XAxQJ_ia5mxjxNjS6E2yD44USo2JmHvzeeNczq25elqbTPLhUpGo1IZuG72FZQ5gTjXoTXC2-xtCDEUZfaUNh4IeAipfLugbpe0JAFlFfrTDAMUFpC3iXjxqzbEanflwPvj6V9iDSgjj8SozSM0dLtxvu0LIeIQAeEgT_yXcrKGmpKdSO08kLBx8VUjkbv_3Pn20Gyu2YEuwpFlM_H1NikuxJNKFGmnAq9LcnwwT0jvoQ',
680
                'dq' => 'S6p59KrlmzGzaQYQM3o0XfHCGvfqHLYjCO557HYQf72O9kLMCfd_1VBEqeD-1jjwELKDjck8kOBl5UvohK1oDfSP1DleAy-cnmL29DqWmhgwM1ip0CCNmkmsmDSlqkUXDi6sAaZuntyukyflI-qSQ3C_BafPyFaKrt1fgdyEwYa08pESKwwWisy7KnmoUvaJ3SaHmohFS78TJ25cfc10wZ9hQNOrIChZlkiOdFCtxDqdmCqNacnhgE3bZQjGp3n83ODSz9zwJcSUvODlXBPc2AycH6Ci5yjbxt4Ppox_5pjm6xnQkiPgj01GpsUssMmBN7iHVsrE7N2iznBNCeOUIQ',
681
                'qi' => 'FZhClBMywVVjnuUud-05qd5CYU0dK79akAgy9oX6RX6I3IIIPckCciRrokxglZn-omAY5CnCe4KdrnjFOT5YUZE7G_Pg44XgCXaarLQf4hl80oPEf6-jJ5Iy6wPRx7G2e8qLxnh9cOdf-kRqgOS3F48Ucvw3ma5V6KGMwQqWFeV31XtZ8l5cVI-I3NzBS7qltpUVgz2Ju021eyc7IlqgzR98qKONl27DuEES0aK0WE97jnsyO27Yp88Wa2RiBrEocM89QZI1seJiGDizHRUP4UZxw9zsXww46wy0P6f9grnYp7t8LkyDDk8eoI4KX6SNMNVcyVS9IWjlq8EzqZEKIA',
682
            ])]),
683
            $isRequired
684
        );
685
686
        return $method;
687
    }
688
689
    /**
690
     * @return ClientAssertionJwt
691
     */
692
    private function getMethodWithTrustedIssuerSupport(): ClientAssertionJwt
693
    {
694
        $method = clone $this->getMethod();
695
696
        $trustedIssuer = $this->prophesize(TrustedIssuer::class);
697
        $trustedIssuer->name()->willReturn('TRUSTED_ISSUER');
698
        $trustedIssuer->getAllowedAssertionTypes()->willReturn(['urn:ietf:params:oauth:client-assertion-type:jwt-bearer']);
699
        $trustedIssuer->getAllowedSignatureAlgorithms()->willReturn(['RS256']);
700
        $trustedIssuer->getJWKSet()->willReturn(JWKSet::createFromKeys([JWK::create([
701
            'kty' => 'RSA',
702
            'n'   => '33WRDEG5rN7daMgI2N5H8cPwTeQPOnz34uG2fe0yKyHjJDGE2XoESRpu5LelSPdYM_r4AWMFWoDWPd-7xaq7uFEkM8c6zaQIgj4uEiq-pBMvH-e805SFbYOKYqfQe4eeXAk4OrQwcUkSrlGskf6YUaw_3IwbPgzEDTgTZFVtQlE',
703
            'e'   => 'AQAB',
704
            'alg' => 'RS256',
705
            'use' => 'sig',
706
        ])]));
707
708
        $trustedIssuerRepository = $this->prophesize(TrustedIssuerRepository::class);
709
        $trustedIssuerRepository->find('TRUSTED_ISSUER')->willReturn($trustedIssuer->reveal());
710
711
        $method->enableTrustedIssuerSupport($trustedIssuerRepository->reveal());
712
713
        return $method;
714
    }
715
716
    /**
717
     * @param \Http\Mock\Client $client
718
     *
719
     * @return JKUFactory
720
     */
721
    private function getJkuFactory(\Http\Mock\Client $client): JKUFactory
722
    {
723
        return new JKUFactory(
724
            new StandardConverter(),
725
            $client,
726
            new DiactorosMessageFactory()
727
        );
728
    }
729
730
    /**
731
     * @return \Http\Mock\Client
732
     */
733
    private function getHttpClient(): \Http\Mock\Client
734
    {
735
        return new \Http\Mock\Client(
736
            new DiactorosMessageFactory()
737
        );
738
    }
739
740
    /**
741
     * @param JWS $jws
742
     *
743
     * @return string
744
     *
745
     * @throws \Exception
746
     */
747
    private function serializeJWS(JWS $jws): string
748
    {
749
        $jsonConverter = new StandardConverter();
750
        $serializer = new CompactSerializer($jsonConverter);
751
752
        return $serializer->serialize($jws, 0);
753
    }
754
755
    /**
756
     * @return JWS
757
     */
758
    private function createValidClientAssertionSignedByTheClient(): JWS
759
    {
760
        $jsonConverter = new StandardConverter();
761
        $jwsBuilder = new JWSBuilder(
762
            $jsonConverter,
763
            AlgorithmManager::create([
764
                new HS256(),
765
            ])
766
        );
767
768
        return $jwsBuilder
769
            ->create()
770
            ->withPayload($jsonConverter->encode([
771
                'iss' => 'ClientId',
772
                'sub' => 'ClientId',
773
                'aud' => 'My Server',
774
                'exp' => time() + 3600,
775
            ]))
776
            ->addSignature(
777
                JWK::createFromJson('{"kty":"oct","k":"U0VDUkVU"}'),
778
                ['alg' => 'HS256']
779
            )
780
            ->build();
781
    }
782
783
    /**
784
     * @return JWS
785
     */
786
    private function createValidClientAssertionSignedByATrustedIssuer(): JWS
787
    {
788
        $jsonConverter = new StandardConverter();
789
        $jwsBuilder = new JWSBuilder(
790
            $jsonConverter,
791
            AlgorithmManager::create([
792
                new RS256(),
793
            ])
794
        );
795
796
        return $jwsBuilder
797
            ->create()
798
            ->withPayload($jsonConverter->encode([
799
                'iss' => 'TRUSTED_ISSUER',
800
                'sub' => 'ClientId',
801
                'aud' => 'My Server',
802
                'exp' => time() + 3600,
803
            ]))
804
            ->addSignature(
805
                JWK::create([
806
                    'kty' => 'RSA',
807
                    'n'   => '33WRDEG5rN7daMgI2N5H8cPwTeQPOnz34uG2fe0yKyHjJDGE2XoESRpu5LelSPdYM_r4AWMFWoDWPd-7xaq7uFEkM8c6zaQIgj4uEiq-pBMvH-e805SFbYOKYqfQe4eeXAk4OrQwcUkSrlGskf6YUaw_3IwbPgzEDTgTZFVtQlE',
808
                    'e'   => 'AQAB',
809
                    'p'   => '9Vovb8pySyOZUoTrNMD6JmTsDa12u9y4_HImQuKD0rerVo2y5y7D_r00i1MhGHkBrI3W2PsubIiZgKp1f0oQfQ',
810
                    'd'   => 'jrDrO3Fo2GvD5Jn_lER0mnxtIb_kvYt5WyaYutbRN1u_SKhaVeklfWzkrSZb5DkV2LOE1JXfoEgvBnms1O9OSJXwqDrFF7NDebw95g6JzI-SbkIHw0Cb-_E9K92FjvW3Bi8j9PKIa8c_dpwIAIirc_q8uhSTf4WoIOHSFbSaQPE',
811
                    'q'   => '6Sgna9gQw4dXN0jBSjOZSjl4S2_H3wHatclrvlYfbJVU6GlIlqWGaUkdFvCuEr9iXJAY4zpEQ4P370EZtsyVZQ',
812
                    'dp'  => '5m79fpE1Jz0YE1ijT7ivOMAws-fnTCnR08eiB8-W36GBWplbHaXejrJFV1WMD-AWomnVD5VZ1LW29hEiqZp2QQ',
813
                    'dq'  => 'JV2pC7CB50QeZx7C02h3jZyuObC9YHEEoxOXr9ZPjPBVvjV5S6NVajQsdEu4Kgr_8YOqaWgiHovcxTwyqcgZvQ',
814
                    'qi'  => 'VZykPj-ugKQxuWTSE-hA-nJqkl7FzjfzHte4QYUSHLHFq6oLlHhgUoJ_4oFLaBmCvgZLAFRDDD6pnd5Fgzt9ow',
815
                ]),
816
                ['alg' => 'RS256']
817
            )
818
            ->build();
819
    }
820
821
    /**
822
     * @return JWS
823
     *
824
     * @throws \Exception
825
     */
826
    private function createInvalidClientAssertionSignedByTheClient(): JWS
827
    {
828
        $jsonConverter = new StandardConverter();
829
        $jwsBuilder = new JWSBuilder(
830
            $jsonConverter,
831
            AlgorithmManager::create([
832
                new HS256(),
833
            ])
834
        );
835
836
        return $jwsBuilder
837
            ->create()
838
            ->withPayload($jsonConverter->encode([]))
839
            ->addSignature(
840
                JWK::createFromJson('{"kty":"oct","k":"U0VDUkVU"}'),
841
                ['alg' => 'HS256']
842
            )
843
            ->build();
844
    }
845
846
    /**
847
     * @return JWS
848
     */
849
    private function createInvalidClientAssertionSignedByATrustedIssuer(): JWS
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
850
    {
851
        $jsonConverter = new StandardConverter();
852
        $jwsBuilder = new JWSBuilder(
853
            $jsonConverter,
854
            AlgorithmManager::create([
855
                new RS256(),
856
            ])
857
        );
858
859
        return $jwsBuilder
860
            ->create()
861
            ->withPayload($jsonConverter->encode([
862
            ]))
863
            ->addSignature(
864
                JWK::create([
865
                    'kty' => 'RSA',
866
                    'n'   => '33WRDEG5rN7daMgI2N5H8cPwTeQPOnz34uG2fe0yKyHjJDGE2XoESRpu5LelSPdYM_r4AWMFWoDWPd-7xaq7uFEkM8c6zaQIgj4uEiq-pBMvH-e805SFbYOKYqfQe4eeXAk4OrQwcUkSrlGskf6YUaw_3IwbPgzEDTgTZFVtQlE',
867
                    'e'   => 'AQAB',
868
                    'p'   => '9Vovb8pySyOZUoTrNMD6JmTsDa12u9y4_HImQuKD0rerVo2y5y7D_r00i1MhGHkBrI3W2PsubIiZgKp1f0oQfQ',
869
                    'd'   => 'jrDrO3Fo2GvD5Jn_lER0mnxtIb_kvYt5WyaYutbRN1u_SKhaVeklfWzkrSZb5DkV2LOE1JXfoEgvBnms1O9OSJXwqDrFF7NDebw95g6JzI-SbkIHw0Cb-_E9K92FjvW3Bi8j9PKIa8c_dpwIAIirc_q8uhSTf4WoIOHSFbSaQPE',
870
                    'q'   => '6Sgna9gQw4dXN0jBSjOZSjl4S2_H3wHatclrvlYfbJVU6GlIlqWGaUkdFvCuEr9iXJAY4zpEQ4P370EZtsyVZQ',
871
                    'dp'  => '5m79fpE1Jz0YE1ijT7ivOMAws-fnTCnR08eiB8-W36GBWplbHaXejrJFV1WMD-AWomnVD5VZ1LW29hEiqZp2QQ',
872
                    'dq'  => 'JV2pC7CB50QeZx7C02h3jZyuObC9YHEEoxOXr9ZPjPBVvjV5S6NVajQsdEu4Kgr_8YOqaWgiHovcxTwyqcgZvQ',
873
                    'qi'  => 'VZykPj-ugKQxuWTSE-hA-nJqkl7FzjfzHte4QYUSHLHFq6oLlHhgUoJ_4oFLaBmCvgZLAFRDDD6pnd5Fgzt9ow',
874
                ]),
875
                ['alg' => 'RS256']
876
            )
877
            ->build();
878
    }
879
880
    /**
881
     * @param string $assertion
882
     *
883
     * @return string
884
     *
885
     * @throws \Exception
886
     */
887
    private function encryptAssertion(string $assertion): string
888
    {
889
        $jsonConverter = new StandardConverter();
890
        $jweBuilder = new JWEBuilder(
891
            $jsonConverter,
892
            AlgorithmManager::create([new RSAOAEP256()]),
893
            AlgorithmManager::create([new A256CBCHS512()]),
894
            CompressionMethodManager::create([new Deflate()])
895
        );
896
        $jwe = $jweBuilder->create()
897
            ->withPayload($assertion)
898
            ->withSharedProtectedHeader(['alg' => 'RSA-OAEP-256', 'enc' => 'A256CBC-HS512'])
899
            ->addRecipient(JWK::create([
900
                'kty' => 'RSA',
901
                'kid' => '[email protected]',
902
                'use' => 'enc',
903
                'n' => 'wbdxI55VaanZXPY29Lg5hdmv2XhvqAhoxUkanfzf2-5zVUxa6prHRrI4pP1AhoqJRlZfYtWWd5mmHRG2pAHIlh0ySJ9wi0BioZBl1XP2e-C-FyXJGcTy0HdKQWlrfhTm42EW7Vv04r4gfao6uxjLGwfpGrZLarohiWCPnkNrg71S2CuNZSQBIPGjXfkmIy2tl_VWgGnL22GplyXj5YlBLdxXp3XeStsqo571utNfoUTU8E4qdzJ3U1DItoVkPGsMwlmmnJiwA7sXRItBCivR4M5qnZtdw-7v4WuR4779ubDuJ5nalMv2S66-RPcnFAzWSKxtBDnFJJDGIUe7Tzizjg1nms0Xq_yPub_UOlWn0ec85FCft1hACpWG8schrOBeNqHBODFskYpUc2LC5JA2TaPF2dA67dg1TTsC_FupfQ2kNGcE1LgprxKHcVWYQb86B-HozjHZcqtauBzFNV5tbTuB-TpkcvJfNcFLlH3b8mb-H_ox35FjqBSAjLKyoeqfKTpVjvXhd09knwgJf6VKq6UC418_TOljMVfFTWXUxlnfhOOnzW6HSSzD1c9WrCuVzsUMv54szidQ9wf1cYWf3g5qFDxDQKis99gcDaiCAwM3yEBIzuNeeCa5dartHDb1xEB_HcHSeYbghbMjGfasvKn0aZRsnTyC0xhWBlsolZE',
904
                'e' => 'AQAB',
905
                'alg' => 'RSA-OAEP-256',
906
            ]))
907
            ->build();
908
909
        $serializer = new \Jose\Component\Encryption\Serializer\CompactSerializer($jsonConverter);
910
911
        return $serializer->serialize($jwe, 0);
912
    }
913
}
914