Completed
Push — master ( 0428a3...157f6a )
by Florent
02:29
created

Verifier::verifySignatures()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 19
rs 9.4285
cc 3
eloc 11
nc 3
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 Jose\Algorithm\JWAManagerInterface;
15
use Jose\Algorithm\SignatureAlgorithmInterface;
16
use Jose\Behaviour\HasJWAManager;
17
use Jose\Behaviour\HasKeyChecker;
18
use Jose\Behaviour\HasLogger;
19
use Jose\Object\JWKInterface;
20
use Jose\Object\JWKSet;
21
use Jose\Object\JWKSetInterface;
22
use Jose\Object\JWSInterface;
23
use Jose\Object\SignatureInterface;
24
use Psr\Log\LoggerInterface;
25
use Psr\Log\LogLevel;
26
27
/**
28
 */
29
final class Verifier implements VerifierInterface
30
{
31
    use HasKeyChecker;
32
    use HasJWAManager;
33
    use HasLogger;
34
35
    /**
36
     * Verifier constructor.
37
     *
38
     * @param \Jose\Algorithm\JWAManagerInterface $jwa_manager
39
     * @param \Psr\Log\LoggerInterface|null       $logger
40
     */
41
    public function __construct(JWAManagerInterface $jwa_manager,
42
                                LoggerInterface $logger = null
43
    ) {
44
        $this->setJWAManager($jwa_manager);
45
46
        if (null !== $logger) {
47
            $this->setLogger($logger);
48
        }
49
    }
50
51
    /**
52
     * {@inheritdoc}
53
     *
54
     * @throws \InvalidArgumentException
55
     */
56
    public function verifyWithKey(JWSInterface $jws, JWKInterface $jwk, $detached_payload = null)
57
    {
58
        $this->log(LogLevel::DEBUG, 'Trying to verify the JWS with the key', ['jws' => $jws, 'jwk' => $jwk, 'detached_payload' => $detached_payload]);
59
        $jwk_set = new JWKSet();
60
        $jwk_set = $jwk_set->addKey($jwk);
61
62
        return $this->verifySignatures($jws, $jwk_set, $detached_payload);
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     *
68
     * @throws \InvalidArgumentException
69
     */
70
    public function verifyWithKeySet(JWSInterface $jws, JWKSetInterface $jwk_set, $detached_payload = null)
71
    {
72
        $this->log(LogLevel::DEBUG, 'Trying to verify the JWS with the key set', ['jwk' => $jws, 'jwk_set' => $jwk_set, 'detached_payload' => $detached_payload]);
73
        return $this->verifySignatures($jws, $jwk_set, $detached_payload);
74
    }
75
76
    /**
77
     * @param \Jose\Object\JWSInterface       $jws
78
     * @param \Jose\Object\JWKSetInterface    $jwk_set
79
     * @param \Jose\Object\SignatureInterface $signature
80
     * @param string|null                     $detached_payload
81
     *
82
     * @return bool
83
     */
84
    private function verifySignature(JWSInterface $jws, JWKSetInterface $jwk_set, SignatureInterface $signature, $detached_payload = null)
85
    {
86
        $input = $signature->getEncodedProtectedHeaders().'.'.(null === $detached_payload ? $jws->getEncodedPayload() : $detached_payload);
87
88
        foreach ($jwk_set->getKeys() as $jwk) {
89
            $algorithm = $this->getAlgorithm($signature);
90
            try {
91
                $this->checkKeyUsage($jwk, 'verification');
92
                $this->checkKeyAlgorithm($jwk, $algorithm->getAlgorithmName());
93
                if (true === $algorithm->verify($jwk, $input, $signature->getSignature())) {
94
                    return true;
95
                }
96
            } catch (\Exception $e) {
97
                //We do nothing, we continue with other keys
98
                continue;
99
            }
100
        }
101
102
        return false;
103
    }
104
105
    /**
106
     * @param \Jose\Object\JWSInterface    $jws
107
     * @param \Jose\Object\JWKSetInterface $jwk_set
108
     * @param string|null                  $detached_payload
109
     *
110
     * @return int
111
     */
112
    private function verifySignatures(JWSInterface $jws, JWKSetInterface $jwk_set, $detached_payload = null)
113
    {
114
        $this->checkPayload($jws, $detached_payload);
115
        $this->checkJWKSet($jwk_set);
116
        $this->checkSignatures($jws);
117
118
        $nb_signatures = $jws->countSignatures();
119
120
        for ($i = 0; $i < $nb_signatures; $i++) {
121
            $signature = $jws->getSignature($i);
122
            $result = $this->verifySignature($jws, $jwk_set, $signature, $detached_payload);
123
124
            if (true == $result) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
125
                return $i;
126
            }
127
        }
128
129
        throw new \InvalidArgumentException('Unable to verify the JWS. Please verify the key or keyset used is correct.');
130
    }
131
132
    /**
133
     * @param \Jose\Object\JWSInterface $jws
134
     */
135
    private function checkSignatures(JWSInterface $jws)
136
    {
137
        if (0 === $jws->countSignatures()) {
138
            $this->log(LogLevel::ERROR, 'There is no signature in the JWS', ['jws' => $jws]);
139
            throw new \InvalidArgumentException('The JWS does not contain any signature.');
140
        }
141
        $this->log(LogLevel::INFO, 'The JWS contains {nb} signature(s)', ['nb' => $jws->countSignatures()]);
142
143
    }
144
145
    /**
146
     * @param \Jose\Object\JWKSetInterface $jwk_set
147
     */
148
    private function checkJWKSet(JWKSetInterface $jwk_set)
149
    {
150
        if (0 === count($jwk_set)) {
151
            $this->log(LogLevel::ERROR, 'There is no key in the key set', ['jwk_set' => $jwk_set]);
152
            throw new \InvalidArgumentException('No key in the key set.');
153
        }
154
        $this->log(LogLevel::INFO, 'The JWK Set contains {nb} key(s)', ['nb' => count($jwk_set)]);
155
    }
156
157
    /**
158
     * @param \Jose\Object\JWSInterface $jws
159
     * @param null|string               $detached_payload
160
     */
161
    private function checkPayload(JWSInterface $jws, $detached_payload = null)
162
    {
163
        if (null !== $detached_payload && !empty($jws->getEncodedPayload())) {
164
            $this->log(LogLevel::ERROR, 'A detached payload is set, but the JWS already has a payload');
165
            throw new \InvalidArgumentException('A detached payload is set, but the JWS already has a payload.');
166
        }
167
    }
168
169
    /**
170
     * @param \Jose\Object\SignatureInterface $signature
171
     *
172
     * @return \Jose\Algorithm\SignatureAlgorithmInterface|null
173
     */
174
    private function getAlgorithm(SignatureInterface $signature)
175
    {
176
        $complete_headers = array_merge(
177
            $signature->getProtectedHeaders(),
178
            $signature->getHeaders()
179
        );
180
        if (!array_key_exists('alg', $complete_headers)) {
181
            $this->log(LogLevel::ERROR, 'No "alg" parameter set in the header.');
182
            throw new \InvalidArgumentException('No "alg" parameter set in the header.');
183
        }
184
185
        $algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
186
        if (!$algorithm instanceof SignatureAlgorithmInterface) {
187
            throw new \RuntimeException(sprintf('The algorithm "%s" is not supported or does not implement SignatureInterface.', $complete_headers['alg']));
188
        }
189
190
        return $algorithm;
191
    }
192
}
193