Failed Conditions
Push — master ( 1325ac...02feb2 )
by Florent
19:27
created

createInvalidClientAssertionSignedByATrustedIssuer()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 30
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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