Completed
Push — master ( cd6056...0428a3 )
by Florent
02:54
created

Verifier::checkJWKSet()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 7
rs 9.4285
cc 2
eloc 4
nc 2
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 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 string|null                  $detached_payload
80
     *
81
     * @return int
82
     */
83
    private function verifySignatures(JWSInterface $jws, JWKSetInterface $jwk_set, $detached_payload = null)
84
    {
85
        $this->checkPayload($jws, $detached_payload);
86
        $this->checkJWKSet($jwk_set);
87
        $this->checkSignatures($jws);
88
89
        $nb_signatures = $jws->countSignatures();
90
91
        for ($i = 0; $i < $nb_signatures; $i++) {
92
            $signature = $jws->getSignature($i);
93
            $input = $signature->getEncodedProtectedHeaders().'.'.(null === $detached_payload ? $jws->getEncodedPayload() : $detached_payload);
94
95
            foreach ($jwk_set->getKeys() as $jwk) {
96
                $algorithm = $this->getAlgorithm($signature);
97
                try {
98
                    $this->checkKeyUsage($jwk, 'verification');
99
                    $this->checkKeyAlgorithm($jwk, $algorithm->getAlgorithmName());
100
                    if (true === $algorithm->verify($jwk, $input, $signature->getSignature())) {
101
                        return $i;
102
                    }
103
                } catch (\Exception $e) {
104
                    //We do nothing, we continue with other keys
105
                    continue;
106
                }
107
            }
108
        }
109
110
        throw new \InvalidArgumentException('Unable to verify the JWS. Please verify the key or keyset used is correct');
111
    }
112
113
    /**
114
     * @param \Jose\Object\JWSInterface $jws
115
     */
116
    private function checkSignatures(JWSInterface $jws)
117
    {
118
        if (0 === $jws->countSignatures()) {
119
            $this->log(LogLevel::ERROR, 'There is no signature in the JWS', ['jws' => $jws]);
120
            throw new \InvalidArgumentException('The JWS does not contain any signature.');
121
        }
122
    }
123
124
    /**
125
     * @param \Jose\Object\JWKSetInterface $jwk_set
126
     */
127
    private function checkJWKSet(JWKSetInterface $jwk_set)
128
    {
129
        if (0 === count($jwk_set)) {
130
            $this->log(LogLevel::ERROR, 'There is no key in the key set', ['jwk_set' => $jwk_set]);
131
            throw new \InvalidArgumentException('No key in the key set.');
132
        }
133
    }
134
135
    /**
136
     * @param \Jose\Object\JWSInterface $jws
137
     * @param null|string               $detached_payload
138
     */
139
    private function checkPayload(JWSInterface $jws, $detached_payload = null)
140
    {
141
        if (null !== $detached_payload && !empty($jws->getEncodedPayload())) {
142
            throw new \InvalidArgumentException('A detached payload is set, but the JWS already has a payload.');
143
        }
144
    }
145
146
    /**
147
     * @param \Jose\Object\SignatureInterface $signature
148
     *
149
     * @return \Jose\Algorithm\SignatureAlgorithmInterface|null
150
     */
151
    private function getAlgorithm(SignatureInterface $signature)
152
    {
153
        $complete_headers = array_merge(
154
            $signature->getProtectedHeaders(),
155
            $signature->getHeaders()
156
        );
157
        if (!array_key_exists('alg', $complete_headers)) {
158
            throw new \InvalidArgumentException('No "alg" parameter set in the header.');
159
        }
160
161
        $algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
162
        if (!$algorithm instanceof SignatureAlgorithmInterface) {
163
            throw new \RuntimeException(sprintf('The algorithm "%s" is not supported or does not implement SignatureInterface.', $complete_headers['alg']));
164
        }
165
166
        return $algorithm;
167
    }
168
}
169