Failed Conditions
Push — v7 ( 318c5f...2b71c7 )
by Florent
03:43
created

Verifier::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 1
1
<?php
2
3
/*
4
 * The MIT License (MIT)
5
 *
6
 * Copyright (c) 2014-2016 Spomky-Labs
7
 *
8
 * This software may be modified and distributed under the terms
9
 * of the MIT license.  See the LICENSE file for details.
10
 */
11
12
namespace Jose;
13
14
use Base64Url\Base64Url;
15
use Jose\Algorithm\JWAManager;
16
use Jose\Algorithm\SignatureAlgorithmInterface;
17
use Jose\Object\JWKInterface;
18
use Jose\Object\JWKSet;
19
use Jose\Object\JWKSetInterface;
20
use Jose\Object\JWSInterface;
21
use Jose\Object\SignatureInterface;
22
23
final class Verifier
24
{
25
    /**
26
     * @var JWAManager
27
     */
28
    private $jwaManager;
29
30
    use Behaviour\HasKeyChecker;
31
    use Behaviour\CommonSigningMethods;
32
33
    /**
34
     * Verifier constructor.
35
     *
36
     * @param string[]|SignatureAlgorithmInterface[] $signature_algorithms
37
     */
38
    public function __construct(array $signature_algorithms)
39
    {
40
        $this->setSignatureAlgorithms($signature_algorithms);
41
        $this->jwaManager = Factory\AlgorithmManagerFactory::createAlgorithmManager($signature_algorithms);
42
    }
43
44
    /**
45
     * @param array $signature_algorithms
46
     *
47
     * @return Verifier
48
     */
49
    public static function createVerifier(array $signature_algorithms): Verifier
50
    {
51
        $verifier = new self($signature_algorithms);
52
53
        return $verifier;
54
    }
55
56
    /**
57
     * @param JWSInterface $jws
58
     * @param JWKInterface $jwk
59
     * @param null|string  $detached_payload
60
     * @param int|null     $recipient_index
61
     */
62
    public function verifyWithKey(JWSInterface $jws, JWKInterface $jwk, ?string $detached_payload = null, ?int &$recipient_index = null)
63
    {
64
        $jwk_set = new JWKSet();
65
        $jwk_set->addKey($jwk);
66
67
        $this->verifySignatures($jws, $jwk_set, $detached_payload, $recipient_index);
68
    }
69
70
    /**
71
     * @param JWSInterface $jws
72
     * @param JWKSetInterface $jwk_set
73
     * @param null $detached_payload
74
     * @param null $recipient_index
75
     */
76
    public function verifyWithKeySet(JWSInterface $jws, JWKSetInterface $jwk_set, $detached_payload = null, &$recipient_index = null)
77
    {
78
        $this->verifySignatures($jws, $jwk_set, $detached_payload, $recipient_index);
79
    }
80
81
    /**
82
     * @param JWSInterface       $jws
83
     * @param JWKSetInterface    $jwk_set
84
     * @param SignatureInterface $signature
85
     * @param string|null        $detached_payload
86
     *
87
     * @return bool
88
     */
89
    private function verifySignature(JWSInterface $jws, JWKSetInterface $jwk_set, SignatureInterface $signature, ?string $detached_payload = null): bool
90
    {
91
        $input = $this->getInputToVerify($jws, $signature, $detached_payload);
92
        foreach ($jwk_set->getKeys() as $jwk) {
93
            $algorithm = $this->getAlgorithm($signature);
94
            try {
95
                $this->checkKeyUsage($jwk, 'verification');
96
                $this->checkKeyAlgorithm($jwk, $algorithm->getAlgorithmName());
97
                if (true === $algorithm->verify($jwk, $input, $signature->getSignature())) {
98
                    return true;
99
                }
100
            } catch (\Exception $e) {
101
                //We do nothing, we continue with other keys
102
                continue;
103
            }
104
        }
105
106
        return false;
107
    }
108
109
    /**
110
     * @param JWSInterface       $jws
111
     * @param SignatureInterface $signature
112
     * @param string|null        $detached_payload
113
     *
114
     * @return string
115
     */
116
    private function getInputToVerify(JWSInterface $jws, SignatureInterface $signature, ?string $detached_payload): string
117
    {
118
        $encoded_protected_headers = $signature->getEncodedProtectedHeaders();
119
        if (!$signature->hasProtectedHeader('b64') || true === $signature->getProtectedHeader('b64')) {
120
            if (null !== $jws->getEncodedPayload($signature)) {
121
                return sprintf('%s.%s', $encoded_protected_headers, $jws->getEncodedPayload($signature));
122
            }
123
124
            $payload = empty($jws->getPayload()) ? $detached_payload : $jws->getPayload();
125
            $payload = is_string($payload) ? $payload : json_encode($payload);
126
127
            return sprintf('%s.%s', $encoded_protected_headers, Base64Url::encode($payload));
128
        }
129
130
        $payload = empty($jws->getPayload()) ? $detached_payload : $jws->getPayload();
131
        $payload = is_string($payload) ? $payload : json_encode($payload);
132
133
        return sprintf('%s.%s', $encoded_protected_headers, $payload);
134
    }
135
136
    /**
137
     * @param JWSInterface    $jws
138
     * @param JWKSetInterface $jwk_set
139
     * @param string|null     $detached_payload
140
     * @param int|null        $recipient_index
141
     */
142
    private function verifySignatures(JWSInterface $jws, JWKSetInterface $jwk_set, ?string $detached_payload = null, ?int &$recipient_index = null)
143
    {
144
        $this->checkPayload($jws, $detached_payload);
145
        $this->checkJWKSet($jwk_set);
146
        $this->checkSignatures($jws);
147
148
        $nb_signatures = $jws->countSignatures();
149
150
        for ($i = 0; $i < $nb_signatures; $i++) {
151
            $signature = $jws->getSignature($i);
152
            $result = $this->verifySignature($jws, $jwk_set, $signature, $detached_payload);
153
154
            if (true === $result) {
155
                $recipient_index = $i;
156
157
                return;
158
            }
159
        }
160
161
        throw new \InvalidArgumentException('Unable to verify the JWS.');
162
    }
163
164
    /**
165
     * @param JWSInterface $jws
166
     */
167
    private function checkSignatures(JWSInterface $jws)
168
    {
169
        if (0 === $jws->countSignatures()) {
170
            throw new \InvalidArgumentException('The JWS does not contain any signature.');
171
        }
172
    }
173
174
    /**
175
     * @param JWKSetInterface $jwk_set
176
     */
177
    private function checkJWKSet(JWKSetInterface $jwk_set)
178
    {
179
        if (0 === $jwk_set->countKeys()) {
180
            throw new \InvalidArgumentException('There is no key in the key set.');
181
        }
182
    }
183
184
    /**
185
     * @param JWSInterface $jws
186
     * @param null|string  $detached_payload
187
     */
188
    private function checkPayload(Object\JWSInterface $jws, ?string $detached_payload = null)
189
    {
190
        if (!empty($jws->getPayload()) && null !== $detached_payload) {
191
            throw new \InvalidArgumentException('A detached payload is set, but the JWS already has a payload.');
192
        }
193
        if (empty($jws->getPayload()) && null === $detached_payload) {
194
            throw new \InvalidArgumentException('No payload.');
195
        }
196
    }
197
198
    /**
199
     * @param SignatureInterface $signature
200
     *
201
     * @return SignatureAlgorithmInterface
202
     */
203
    private function getAlgorithm(SignatureInterface $signature): SignatureAlgorithmInterface
204
    {
205
        $complete_headers = $signature->getProtectedHeaders() + $signature->getHeaders();
206
        if (!array_key_exists('alg', $complete_headers)) {
207
            throw new \InvalidArgumentException('No "alg" parameter set in the header.');
208
        }
209
210
        $algorithm = $this->jwaManager->getAlgorithm($complete_headers['alg']);
211
        if (!$algorithm instanceof SignatureAlgorithmInterface) {
212
            throw new \InvalidArgumentException(sprintf('The algorithm "%s" is not supported or is not a signature algorithm.', $complete_headers['alg']));
213
        }
214
215
        return $algorithm;
216
    }
217
}
218