Passed
Push — master ( df4379...356a95 )
by Thomas Mauro
02:53
created

RequestObjectFactory::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 6
ccs 0
cts 5
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace TMV\OpenIdClient\RequestObject;
6
7
use Jose\Component\Core\AlgorithmManager;
8
use Jose\Component\Encryption\Compression\CompressionMethodManager;
9
use Jose\Component\Encryption\Compression\Deflate;
10
use Jose\Component\Encryption\JWEBuilder;
11
use Jose\Component\Encryption\Serializer\CompactSerializer as EncryptionCompactSerializer;
12
use Jose\Component\Encryption\Serializer\JWESerializer as JWESerializer;
13
use Jose\Component\Signature\Algorithm\None;
14
use Jose\Component\Signature\Algorithm\RS256;
15
use Jose\Component\Signature\JWSBuilder;
16
use Jose\Component\Signature\Serializer\CompactSerializer as SignatureCompactSerializer;
17
use Jose\Component\Signature\Serializer\Serializer as JWSSerializer;
18
use function TMV\OpenIdClient\base64url_encode;
19
use TMV\OpenIdClient\ClientInterface;
20
use TMV\OpenIdClient\Exception\RuntimeException;
21
use function TMV\OpenIdClient\jose_secret_key;
22
23
class RequestObjectFactory
24
{
25
    /** @var JWSBuilder */
26
    private $jwsBuilder;
27
28
    /** @var JWEBuilder */
29
    private $jweBuilder;
30
31
    /** @var JWSSerializer */
32
    private $signatureSerializer;
33
34
    /** @var JWESerializer */
35
    private $encryptionSerializer;
36
37
    public function __construct(
38
        ?AlgorithmManager $algorithmManager = null,
39
        ?JWSBuilder $jwsBuilder = null,
40
        ?JWEBuilder $jweBuilder = null,
41
        ?JWSSerializer $signatureSerializer = null,
42
        ?JWESerializer $encryptionSerializer = null
43
    ) {
44
        $algorithmManager = $algorithmManager ?: new AlgorithmManager([new None(), new RS256()]);
45
        $this->jwsBuilder = $jwsBuilder ?: new JWSBuilder($algorithmManager);
46
        $this->jweBuilder = $jweBuilder ?: new JWEBuilder(
47
            $algorithmManager,
48
            $algorithmManager,
49
            new CompressionMethodManager([new Deflate()])
50
        );
51
        $this->signatureSerializer = $signatureSerializer ?: new SignatureCompactSerializer();
52
        $this->encryptionSerializer = $encryptionSerializer ?: new EncryptionCompactSerializer();
53
    }
54
55
    public function create(ClientInterface $client, array $params = []): string
56
    {
57
        $payload = $this->createPayload($client, $params);
58
        $signedToken = $this->createSignedToken($client, $payload);
59
60
        return $this->createEncryptedToken($client, $signedToken);
61
    }
62
63
    private function createPayload(ClientInterface $client, array $params = []): string
64
    {
65
        $metadata = $client->getMetadata();
66
        $issuer = $client->getIssuer();
67
68
        $payload = \json_encode(\array_merge($params, [
69
            'iss' => $metadata->getClientId(),
70
            'aud' => $issuer->getMetadata()->getIssuer(),
71
            'client_id' => $metadata->getClientId(),
72
            'jti' => \bin2hex(\random_bytes(32)),
73
            'iat' => \time(),
74
            'exp' => \time() + 300,
75
        ]));
76
77
        if (! $payload) {
78
            throw new RuntimeException('Unable to encode payload');
79
        }
80
81
        return $payload;
82
    }
83
84
    private function createSignedToken(ClientInterface $client, string $payload): string
85
    {
86
        $metadata = $client->getMetadata();
87
88
        /** @var string $alg */
89
        $alg = $metadata->get('request_object_signing_alg') ?: 'none';
90
91
        if ('none' === $alg) {
92
            return \implode('.', [
93
                base64url_encode((string) \json_encode(['alg' => $alg])),
94
                base64url_encode($payload),
95
                '',
96
            ]);
97
        }
98
99
        if (0 === \strpos($alg, 'HS')) {
100
            $jwk = jose_secret_key($metadata->getClientSecret() ?: '');
101
        } else {
102
            $jwk = $client->getJWKS()->selectKey('sig', null, ['alg' => $alg]);
103
        }
104
105
        if (! $jwk) {
106
            throw new RuntimeException('No key to sign with alg ' . $alg);
107
        }
108
109
        $header = \array_filter([
110
            'alg' => $alg,
111
            'kid' => $jwk->has('kid') ? $jwk->get('kid') : null,
112
        ]);
113
114
        $jws = $this->jwsBuilder->create()
115
            ->withPayload($payload)
116
            ->addSignature($jwk, $header)
117
            ->build();
118
119
        return $this->signatureSerializer->serialize($jws, 0);
120
    }
121
122
    private function createEncryptedToken(ClientInterface $client, string $payload): string
123
    {
124
        $metadata = $client->getMetadata();
125
        $issuer = $client->getIssuer();
126
127
        /** @var null|string $alg */
128
        $alg = $metadata->get('request_object_encryption_alg');
129
130
        if (! $alg) {
131
            return $payload;
132
        }
133
134
        /** @var null|string $enc */
135
        $enc = $metadata->get('request_object_encryption_enc');
136
137
        if (\preg_match('/^(RSA|ECDH)/', $alg)) {
138
            $jwk = $issuer->getJwks()->selectKey('enc', null, ['alg' => $alg, 'enc' => $enc]);
139
        } else {
140
            $jwk = jose_secret_key(
141
                $metadata->getClientSecret() ?: '',
142
                'dir' === $alg ? $enc : $alg
143
            );
144
        }
145
146
        if (! $jwk) {
147
            throw new RuntimeException('No key to sign with alg ' . $alg);
148
        }
149
150
        $header = \array_filter([
151
            'alg' => $alg,
152
            'enc' => $enc,
153
            'kid' => $jwk->has('kid') ? $jwk->get('kid') : null,
154
        ]);
155
156
        $jwe = $this->jweBuilder->create()
157
            ->withPayload($payload)
158
            ->withSharedProtectedHeader($header)
159
            ->addRecipient($jwk)
160
            ->build();
161
162
        return $this->encryptionSerializer->serialize($jwe, 0);
163
    }
164
}
165