Issues (37)

src/Jwt/JWA.php (2 issues)

1
<?php
2
3
namespace Parroauth2\Client\Jwt;
4
5
use InvalidArgumentException;
6
use Jose\Component\Core\Algorithm;
7
use Jose\Component\Core\AlgorithmManager;
8
use Jose\Component\Signature\Algorithm\ES256;
9
use Jose\Component\Signature\Algorithm\ES384;
10
use Jose\Component\Signature\Algorithm\ES512;
11
use Jose\Component\Signature\Algorithm\HS256;
12
use Jose\Component\Signature\Algorithm\HS384;
13
use Jose\Component\Signature\Algorithm\HS512;
14
use Jose\Component\Signature\Algorithm\None;
15
use Jose\Component\Signature\Algorithm\PS256;
16
use Jose\Component\Signature\Algorithm\PS384;
17
use Jose\Component\Signature\Algorithm\PS512;
18
use Jose\Component\Signature\Algorithm\RS256;
19
use Jose\Component\Signature\Algorithm\RS384;
20
use Jose\Component\Signature\Algorithm\RS512;
21
22
/**
23
 * Handle JSON Web Algorithms
24
 *
25
 * @see https://tools.ietf.org/html/rfc7518
26
 */
27
final class JWA
28
{
29
    public const TYPE_HMAC = 'hmac';
30
    public const TYPE_RSA = 'rsa';
31
    public const TYPE_ELLIPTIC_CURVE = 'ec';
32
    public const TYPE_RSASSA_PSS = 'rsassa-pss';
33
    public const TYPE_NONE = 'none';
34
35
    /**
36
     * Maps the alg header value to algorithm information
37
     *
38
     * Keys :
39
     * - class : The class name of the algorithm
40
     * - hash  : The hash function used by the algorithm
41
     * - type  : The algorithm type
42
     *
43
     * @var array<string, array{class: class-string<Algorithm>, hash?: string, type: JWA::TYPE_*}>
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, array{clas...ng, type: JWA::TYPE_*}> at position 8 could not be parsed: Unknown type name 'class-string' at position 8 in array<string, array{class: class-string<Algorithm>, hash?: string, type: JWA::TYPE_*}>.
Loading history...
44
     */
45
    private $algMap = [
46
        // HMAC : https://tools.ietf.org/html/rfc7518#section-3.2
47
        'HS256' => ['class' => HS256::class, 'hash' => 'sha256', 'type' => self::TYPE_HMAC],
48
        'HS384' => ['class' => HS384::class, 'hash' => 'sha384', 'type' => self::TYPE_HMAC],
49
        'HS512' => ['class' => HS512::class, 'hash' => 'sha512', 'type' => self::TYPE_HMAC],
50
51
        // RSA : https://tools.ietf.org/html/rfc7518#section-3.3
52
        'RS256' => ['class' => RS256::class, 'hash' => 'sha256', 'type' => self::TYPE_RSA],
53
        'RS384' => ['class' => RS384::class, 'hash' => 'sha384', 'type' => self::TYPE_RSA],
54
        'RS512' => ['class' => RS512::class, 'hash' => 'sha512', 'type' => self::TYPE_RSA],
55
56
        // ECDSA : https://tools.ietf.org/html/rfc7518#section-3.4
57
        'ES256' => ['class' => ES256::class, 'hash' => 'sha256', 'type' => self::TYPE_ELLIPTIC_CURVE],
58
        'ES384' => ['class' => ES384::class, 'hash' => 'sha384', 'type' => self::TYPE_ELLIPTIC_CURVE],
59
        'ES512' => ['class' => ES512::class, 'hash' => 'sha512', 'type' => self::TYPE_ELLIPTIC_CURVE],
60
61
        // RSASSA-PSS : https://tools.ietf.org/html/rfc7518#section-3.5
62
        'PS256' => ['class' => PS256::class, 'hash' => 'sha256', 'type' => self::TYPE_RSASSA_PSS],
63
        'PS384' => ['class' => PS384::class, 'hash' => 'sha384', 'type' => self::TYPE_RSASSA_PSS],
64
        'PS512' => ['class' => PS512::class, 'hash' => 'sha512', 'type' => self::TYPE_RSASSA_PSS],
65
66
        // Unsecure : https://tools.ietf.org/html/rfc7518#section-3.6
67
        'none' => ['class' => None::class, 'type' => self::TYPE_NONE],
68
    ];
69
70
    /**
71
     * Map of enabled algorithms
72
     *
73
     * @var array<string, bool>
74
     */
75
    private $enabled = [
76
        'HS256' => true,
77
        'HS384' => true,
78
        'HS512' => true,
79
        'RS256' => true,
80
        'RS384' => true,
81
        'RS512' => true,
82
        'ES256' => true,
83
        'ES384' => true,
84
        'ES512' => true,
85
        'PS256' => true,
86
        'PS384' => true,
87
        'PS512' => true,
88
    ];
89
90
    /**
91
     * @var AlgorithmManager|null
92
     */
93
    private $manager;
94
95
    /**
96
     * Get list of algorithms identifiers (alg header parameter) for a given type
97
     *
98
     * @param string $type One of the JWA::TYPE_ constant
99
     *
100
     * @return string[]
101
     */
102 21
    public function algorithmsByType(string $type): array
103
    {
104 21
        $algorithms = [];
105
106 21
        foreach ($this->enabled as $id => $enabled) {
107 21
            if ($enabled && $this->algMap[$id]['type'] === $type) {
108 21
                $algorithms[] = $id;
109
            }
110
        }
111
112 21
        return $algorithms;
113
    }
114
115
    /**
116
     * Get the hash algorithm used by the given alg
117
     *
118
     * @param string $alg The "alg" header parameter
119
     *
120
     * @return string The hash algorithm
121
     *
122
     * @throws InvalidArgumentException When an unsupported alg is given
123
     */
124 20
    public function hashAlgorithm(string $alg): string
125
    {
126 20
        if (empty($this->enabled[$alg]) || empty($this->algMap[$alg]['hash'])) {
127 3
            throw new InvalidArgumentException('Unsupported alg "' . $alg . '"');
128
        }
129
130 17
        return $this->algMap[$alg]['hash'];
131
    }
132
133
    /**
134
     * Enable (or disable) an algorithm
135
     *
136
     * @param string $alg The alg id
137
     * @param bool $value Enable ?
138
     *
139
     * @return $this
140
     */
141 7
    public function enable(string $alg, bool $value = true): self
142
    {
143 7
        if (!isset($this->algMap[$alg]) || !class_exists($this->algMap[$alg]['class'])) {
144 2
            throw new InvalidArgumentException('Unsupported alg "' . $alg . '"');
145
        }
146
147 5
        $this->enabled[$alg] = $value;
148 5
        $this->manager = null;
149
150 5
        return $this;
151
    }
152
153
    /**
154
     * Filter the algorithms, and returns a new instance of JWA representing the subset of algorithms
155
     * This method will not modify the current instance
156
     *
157
     * @param string[] $algorithms List of algorithms to keep
158
     *
159
     * @return self The new instance
160
     */
161 5
    public function filter(array $algorithms): self
162
    {
163 5
        $jwa = clone $this;
164
165 5
        $algorithms = array_flip($algorithms);
166
167
        // Enable intersection of already enabled algorithms with $algorithms
168 5
        foreach ($jwa->enabled as $alg => $enabled) {
169 5
            $jwa->enabled[$alg] = $enabled && isset($algorithms[$alg]);
170
        }
171
172
        // A manager is already instantiated : filters enabled algorithms and recreates a new manager
173 5
        if ($jwa->manager) {
174 2
            $algorithms = [];
175
176 2
            foreach ($jwa->enabled as $alg => $enabled) {
177 2
                if ($enabled && $jwa->manager->has($alg)) {
178 2
                    $algorithms[] = $jwa->manager->get($alg);
179
                }
180
            }
181
182 2
            $jwa->manager = new AlgorithmManager($algorithms);
183
        }
184
185 5
        return $jwa;
186
    }
187
188
    /**
189
     * Get the algorithm manager
190
     *
191
     * @return AlgorithmManager
192
     */
193 43
    public function manager(): AlgorithmManager
194
    {
195 43
        if ($this->manager !== null) {
196 3
            return $this->manager;
197
        }
198
199 43
        $algorithms = [];
200
201 43
        foreach ($this->enabled as $id => $enabled) {
202 43
            if ($enabled) {
203 43
                $className = $this->algMap[$id]['class'];
204
205 43
                if (class_exists($className)) {
206 43
                    $algorithms[] = new $className();
207
                }
208
            }
209
        }
210
211 43
        return $this->manager = new AlgorithmManager($algorithms);
212
    }
213
214
    /**
215
     * Register a new algorithm
216
     *
217
     * @param string $alg The "alg" header parameter
218
     * @param class-string<Algorithm> $class The algorithm implementation class
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<Algorithm> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<Algorithm>.
Loading history...
219
     * @param JWA::TYPE_* $type The algorithm type
220
     * @param string|null $hash The hash function
221
     */
222 4
    public function register(string $alg, string $class, string $type, ?string $hash = null): void
223
    {
224 4
        $this->algMap[$alg] = ['class' => $class, 'type' => $type];
225
226 4
        if ($hash) {
227 4
            $this->algMap[$alg]['hash'] = $hash;
228
        }
229 4
    }
230
}
231