Completed
Push — master ( 00727d...5aa01a )
by Florent
02:25
created

Verifier::createVerifier()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
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 Jose\Algorithm\SignatureAlgorithmInterface;
16
use Jose\Behaviour\CommonSigningMethods;
17
use Jose\Behaviour\HasJWAManager;
18
use Jose\Behaviour\HasKeyChecker;
19
use Jose\Behaviour\HasLogger;
20
use Jose\Factory\AlgorithmManagerFactory;
21
use Jose\Object\JWKInterface;
22
use Jose\Object\JWKSet;
23
use Jose\Object\JWKSetInterface;
24
use Jose\Object\JWSInterface;
25
use Jose\Object\SignatureInterface;
26
use Psr\Log\LoggerInterface;
27
use Psr\Log\LogLevel;
28
29
final class Verifier implements VerifierInterface
30
{
31
    use HasKeyChecker;
32
    use HasJWAManager;
33
    use HasLogger;
34
    use CommonSigningMethods;
35
36
    /**
37
     * Verifier constructor.
38
     *
39
     * @param string[]|\Jose\Algorithm\SignatureAlgorithmInterface[] $signature_algorithms
40
     * @param \Psr\Log\LoggerInterface|null                          $logger
41
     */
42
    public function __construct(array $signature_algorithms,
43
                                LoggerInterface $logger = null
44
    ) {
45
        $this->setSignatureAlgorithms($signature_algorithms);
46
        $this->setJWAManager(AlgorithmManagerFactory::createAlgorithmManager($signature_algorithms));
47
48
        if (null !== $logger) {
49
            $this->setLogger($logger);
50
        }
51
    }
52
53
    /**
54
     * {@inheritdoc}
55
     */
56
    public static function createVerifier(array $signature_algorithms, LoggerInterface $logger = null)
57
    {
58
        return new self($signature_algorithms, $logger);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new self($signature_algorithms, $logger); (Jose\Verifier) is incompatible with the return type declared by the interface Jose\VerifierInterface::createVerifier of type Jose\SignerInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
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
     * @throws \InvalidArgumentException
79
     */
80
    public function verifyWithKeySet(JWSInterface $jws, JWKSetInterface $jwk_set, $detached_payload = null, &$recipient_index = null)
81
    {
82
        $this->log(LogLevel::DEBUG, 'Trying to verify the JWS with the key set', ['jwk' => $jws, 'jwk_set' => $jwk_set, 'detached_payload' => $detached_payload]);
83
84
        return $this->verifySignatures($jws, $jwk_set, $detached_payload, $recipient_index);
85
    }
86
87
    /**
88
     * @param \Jose\Object\JWSInterface       $jws
89
     * @param \Jose\Object\JWKSetInterface    $jwk_set
90
     * @param \Jose\Object\SignatureInterface $signature
91
     * @param string|null                     $detached_payload
92
     *
93
     * @return bool
94
     */
95
    private function verifySignature(JWSInterface $jws, JWKSetInterface $jwk_set, SignatureInterface $signature, $detached_payload = null)
96
    {
97
        $input = $signature->getEncodedProtectedHeaders().'.'.(null === $detached_payload ? $jws->getEncodedPayload() : $detached_payload);
98
99
        foreach ($jwk_set->getKeys() as $jwk) {
100
            $algorithm = $this->getAlgorithm($signature);
101
            try {
102
                $this->checkKeyUsage($jwk, 'verification');
103
                $this->checkKeyAlgorithm($jwk, $algorithm->getAlgorithmName());
104
                if (true === $algorithm->verify($jwk, $input, $signature->getSignature())) {
105
                    return true;
106
                }
107
            } catch (\Exception $e) {
108
                //We do nothing, we continue with other keys
109
                continue;
110
            }
111
        }
112
113
        return false;
114
    }
115
116
    /**
117
     * @param \Jose\Object\JWSInterface    $jws
118
     * @param \Jose\Object\JWKSetInterface $jwk_set
119
     * @param string|null                  $detached_payload
120
     * @param int|null                     $recipient_index
121
     *
122
     * @return bool
123
     */
124
    private function verifySignatures(JWSInterface $jws, JWKSetInterface $jwk_set, $detached_payload = null, &$recipient_index = null)
125
    {
126
        $this->checkPayload($jws, $detached_payload);
127
        $this->checkJWKSet($jwk_set);
128
        $this->checkSignatures($jws);
129
130
        $nb_signatures = $jws->countSignatures();
131
132
        for ($i = 0; $i < $nb_signatures; $i++) {
133
            $signature = $jws->getSignature($i);
134
            $result = $this->verifySignature($jws, $jwk_set, $signature, $detached_payload);
135
136
            if (true === $result) {
137
                $recipient_index = $i;
138
139
                return;
140
            }
141
        }
142
143
        throw new \InvalidArgumentException('Unable to verify the JWS.');
144
    }
145
146
    /**
147
     * @param \Jose\Object\JWSInterface $jws
148
     */
149
    private function checkSignatures(JWSInterface $jws)
150
    {
151
        Assertion::greaterThan($jws->countSignatures(), 0, 'The JWS does not contain any signature.');
152
        $this->log(LogLevel::INFO, 'The JWS contains {nb} signature(s)', ['nb' => $jws->countSignatures()]);
153
    }
154
155
    /**
156
     * @param \Jose\Object\JWKSetInterface $jwk_set
157
     */
158
    private function checkJWKSet(JWKSetInterface $jwk_set)
159
    {
160
        Assertion::greaterThan($jwk_set->countKeys(), 0, 'There is no key in the key set.');
161
        $this->log(LogLevel::INFO, 'The JWK Set contains {nb} key(s)', ['nb' => count($jwk_set)]);
162
    }
163
164
    /**
165
     * @param \Jose\Object\JWSInterface $jws
166
     * @param null|string               $detached_payload
167
     */
168
    private function checkPayload(JWSInterface $jws, $detached_payload = null)
169
    {
170
        Assertion::false(
171
            null !== $detached_payload && !empty($jws->getEncodedPayload()),
172
            'A detached payload is set, but the JWS already has a payload.'
173
        );
174
    }
175
176
    /**
177
     * @param \Jose\Object\SignatureInterface $signature
178
     *
179
     * @return \Jose\Algorithm\SignatureAlgorithmInterface
180
     */
181
    private function getAlgorithm(SignatureInterface $signature)
182
    {
183
        $complete_headers = array_merge(
184
            $signature->getProtectedHeaders(),
185
            $signature->getHeaders()
186
        );
187
        Assertion::keyExists($complete_headers, 'alg', 'No "alg" parameter set in the header.');
188
189
        $algorithm = $this->getJWAManager()->getAlgorithm($complete_headers['alg']);
190
        Assertion::isInstanceOf($algorithm, SignatureAlgorithmInterface::class, sprintf('The algorithm "%s" is not supported or does not implement SignatureInterface.', $complete_headers['alg']));
191
192
        return $algorithm;
193
    }
194
}
195