Completed
Push — master ( 05e2ef...22b3b9 )
by Florent
02:34
created

Verifier::getInputToVerify()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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