RequestObjectFactory::createSignedToken()   B
last analyzed

Complexity

Conditions 7
Paths 7

Size

Total Lines 40
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 7.0035

Importance

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