Passed
Push — master ( 3cc4a7...df4379 )
by Thomas Mauro
03:03
created

RequestTokenFactory::createEncryptedToken()   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 38
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 22
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 38
ccs 0
cts 29
cp 0
crap 42
rs 8.9457
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 RequestTokenFactory
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
        $header = ['alg' => $alg];
92
93
        if ('none' === $alg) {
94
            return \implode('.', [
95
                base64url_encode((string) \json_encode($header)),
96
                base64url_encode($payload),
97
                '',
98
            ]);
99
        }
100
101
        if (0 === \strpos($alg, 'HS')) {
102
            $jwk = jose_secret_key($metadata->getClientSecret() ?: '');
103
        } else {
104
            $jwk = $client->getJWKS()->selectKey('sig', null, ['alg' => $alg]);
105
106
            if (! $jwk) {
107
                throw new RuntimeException('No key to sign with alg ' . $alg);
108
            }
109
        }
110
111
        if ($kid = $jwk->get('kid')) {
112
            $header['kid'] = $kid;
113
        }
114
115
        $jws = $this->jwsBuilder->create()
116
            ->withPayload($payload)
117
            ->addSignature($jwk, $header)
118
            ->build();
119
120
        return $this->signatureSerializer->serialize($jws, 0);
121
    }
122
123
    private function createEncryptedToken(ClientInterface $client, string $payload): string
124
    {
125
        $metadata = $client->getMetadata();
126
        $issuer = $client->getIssuer();
127
128
        /** @var null|string $alg */
129
        $alg = $metadata->get('request_object_encryption_alg');
130
131
        if (! $alg) {
132
            return $payload;
133
        }
134
135
        /** @var null|string $enc */
136
        $enc = $metadata->get('request_object_encryption_enc');
137
138
        if (\preg_match('/^(RSA|ECDH)/', $alg)) {
139
            $jwk = $issuer->getJwks()->selectKey('enc', null, ['alg' => $alg, 'enc' => $enc]);
140
141
            if (! $jwk) {
142
                throw new RuntimeException('No key to sign with alg ' . $alg);
143
            }
144
        } else {
145
            $jwk = jose_secret_key(
146
                $metadata->getClientSecret() ?: '',
147
                'dir' === $alg ? $enc : $alg
148
            );
149
        }
150
151
        $jwe = $this->jweBuilder->create()
152
            ->withPayload($payload)
153
            ->withSharedProtectedHeader([
154
                'alg' => $alg,
155
                'enc' => $enc,
156
            ])
157
            ->addRecipient($jwk)
158
            ->build();
159
160
        return $this->encryptionSerializer->serialize($jwe, 0);
161
    }
162
}
163