Failed Conditions
Push — v7 ( e9e51b...d9c0af )
by Florent
03:20
created

Verifier::checkPayload()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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