Completed
Push — master ( 356550...41f07f )
by sebastian
03:01
created

src/jws/impl/JWS.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Copyright 2015 OpenStack Foundation
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 * http://www.apache.org/licenses/LICENSE-2.0
8
 * Unless required by applicable law or agreed to in writing, software
9
 * distributed under the License is distributed on an "AS IS" BASIS,
10
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
 * See the License for the specific language governing permissions and
12
 * limitations under the License.
13
 **/
14
15
namespace jws\impl;
16
17
use jwa\cryptographic_algorithms\digital_signatures\DigitalSignatureAlgorithm;
18
use jwa\cryptographic_algorithms\DigitalSignatures_MACs_Registry;
19
use jwa\cryptographic_algorithms\macs\MAC_Algorithm;
20
use jwk\exceptions\InvalidJWKAlgorithm;
21
use jwk\IAsymmetricJWK;
22
use jwk\IJWK;
23
use jwk\JSONWebKeyKeyOperationsValues;
24
use jwk\JSONWebKeyPublicKeyUseValues;
25
use jwk\JSONWebKeyVisibility;
26
use jws\exceptions\JWSInvalidJWKException;
27
use jws\exceptions\JWSInvalidPayloadException;
28
use jws\exceptions\JWSNotSupportedAlgorithm;
29
use jws\IJWS;
30
use jws\IJWSPayloadClaimSetSpec;
31
use jws\IJWSPayloadSpec;
32
use jws\payloads\JWSPayloadFactory;
33
use jwt\IBasicJWT;
34
use jwt\IJOSEHeader;
35
use jwt\impl\JWT;
36
use jwt\impl\JWTSerializer;
37
use jwt\JOSEHeaderParam;
38
use jwt\RegisteredJOSEHeaderNames;
39
use jwt\utils\JOSEHeaderSerializer;
40
use jwt\utils\JWTClaimSetSerializer;
41
use jwt\utils\JWTRawSerializer;
42
use utils\json_types\JsonArray;
43
use utils\json_types\JsonValue;
44
use utils\json_types\StringOrURI;
45
46
/**
47
 * Class JWS
48
 * @package jws\impl
49
 * @access private
50
 */
51
final class JWS extends JWT implements IJWS
52
{
53
54
    /**
55
     * @var IJWK
56
     */
57
    private $jwk = null;
58
59
    /**
60
     * @var IJWSPayloadSpec
61
     */
62
    private $payload = null;
63
64
    /**
65
     * @param IJOSEHeader $header
66
     * @param IJWSPayloadSpec $payload
67
     * @param string $signature
68
     * @throws JWSNotSupportedAlgorithm
69
     */
70
    protected function __construct(IJOSEHeader $header, IJWSPayloadSpec $payload = null, $signature = '')
71
    {
72
73
        $claim_set = null;
74
75
        if(!is_null($payload) && $payload->isClaimSet() && $payload instanceof IJWSPayloadClaimSetSpec) {
76
            $header->addHeader(new JOSEHeaderParam(RegisteredJOSEHeaderNames::Type, new StringOrURI('JWT')));
77
            $claim_set = $payload->getClaimSet();
78
        }
79
80
        parent::__construct($header, $claim_set);
81
82
        if(!is_null($payload))
83
            $this->setPayload($payload);
84
85
        $this->signature = $signature;
86
    }
87
88
    /**
89
     * @param IJWSPayloadSpec $payload
90
     * @return IJWS
91
     */
92
    public function setPayload(IJWSPayloadSpec $payload)
93
    {
94
        $this->payload = $payload;
95
        return $this;
96
    }
97
98
    /**
99
     * @return string
100
     */
101
    public function toCompactSerialization()
102
    {
103
        if(!is_null($this->jwk->getId()))
104
            $this->header->addHeader(new JOSEHeaderParam(RegisteredJOSEHeaderNames::KeyID, $this->jwk->getId()));
105
106
        if($this->jwk instanceof IAsymmetricJWK)
107
        {
108
            // we should add the public key on the header
109
            $public_key = clone $this->jwk;
110
111
            $this->header->addHeader
112
            (
113
                new JOSEHeaderParam
114
                (
115
                    RegisteredJOSEHeaderNames::JSONWebKey,
116
                    new JsonValue
117
                    (
118
                        $public_key->setVisibility(JSONWebKeyVisibility::PublicOnly)
119
                    )
120
                )
121
            );
122
        }
123
124
        $this->sign();
125
        return parent::toCompactSerialization();
126
    }
127
128
    /**
129
     * @return $this
130
     * @throws JWSInvalidJWKException
131
     * @throws JWSInvalidPayloadException
132
     * @throws JWSNotSupportedAlgorithm
133
     */
134
    public function sign()
135
    {
136
137
        if(is_null($this->jwk))
138
            throw new JWSInvalidJWKException;
139
140 View Code Duplication
        if($this->jwk->getKeyUse()->getString() !== JSONWebKeyPublicKeyUseValues::Signature)
141
            throw new JWSInvalidJWKException(sprintf('use %s not supported.', $this->jwk->getKeyUse()->getString()));
142
143
        $alg = DigitalSignatures_MACs_Registry::getInstance()->get($this->header->getAlgorithm()->getString());
144
145
        if(is_null($alg))
146
            throw new JWSNotSupportedAlgorithm(sprintf('alg %s.',$this->header->getAlgorithm()->getString()));
147
148
        $secured_input_bytes = JOSEHeaderSerializer::serialize($this->header) . IBasicJWT::SegmentSeparator .$this->getEncodedPayload();
149
150
        $key  = $this->jwk->getKey(JSONWebKeyKeyOperationsValues::ComputeDigitalSignatureOrMAC);
151
152
        if($alg instanceof DigitalSignatureAlgorithm)
153
        {
154
            $this->signature = $alg->sign($key, $secured_input_bytes);
155
        }
156
        else if($alg instanceof MAC_Algorithm )
157
        {
158
            $this->signature = $alg->digest($key, $secured_input_bytes);
159
        }
160
        else
161
        {
162
            throw new JWSNotSupportedAlgorithm(sprintf('alg %s.',$this->header->getAlgorithm()->getString()));
163
        }
164
165
        return $this;
166
    }
167
168
    /**
169
     * @return string
170
     * @throws JWSInvalidPayloadException
171
     */
172
    public function getEncodedPayload()
173
    {
174
        if(is_null($this->payload))
175
            throw new JWSInvalidPayloadException('payload is not set!');
176
177
        $enc_payload = '';
0 ignored issues
show
$enc_payload is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
178
        if($this->payload->isClaimSet() && $this->payload instanceof IJWSPayloadClaimSetSpec)
179
        {
180
            $enc_payload = JWTClaimSetSerializer::serialize($this->payload->getClaimSet());
181
        }
182
        else
183
        {
184
            $enc_payload = JWTRawSerializer::serialize($this->payload->getRaw());
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface jws\IJWSPayloadSpec as the method getRaw() does only exist in the following implementations of said interface: jws\payloads\_JWSPayloadRawSpec.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
185
        }
186
        return $enc_payload;
187
    }
188
189
    /**
190
     * @param IJWK $key
191
     * @return $this
192
     */
193
    public function setKey(IJWK $key)
194
    {
195
        $this->jwk = $key;
196
        return $this;
197
    }
198
199
    /**
200
     * @param string $compact_serialization
201
     * @return IJWS
202
     * @access private
203
     */
204
    static public function fromCompactSerialization($compact_serialization)
205
    {
206
        list($header, $payload, $signature) = JWTSerializer::deserialize($compact_serialization);
207
        return new JWS($header, JWSPayloadFactory::build($payload), $signature);
208
    }
209
210
    /**
211
     * @return StringOrURI
212
     */
213
    public function getSigningAlgorithm()
214
    {
215
        return $this->header->getAlgorithm();
216
    }
217
218
    /**
219
     * @return StringOrURI
220
     */
221
    public function getType()
222
    {
223
        return $this->header->getType();
224
    }
225
226
    /**
227
     * @param string $original_alg
228
     * @return bool
229
     * @throws InvalidJWKAlgorithm
230
     * @throws JWSInvalidJWKException
231
     * @throws JWSInvalidPayloadException
232
     * @throws JWSNotSupportedAlgorithm
233
     */
234
    public function verify($original_alg)
235
    {
236
        if(is_null($this->jwk))
237
            throw new JWSInvalidJWKException;
238
239 View Code Duplication
        if($this->jwk->getKeyUse()->getString() !== JSONWebKeyPublicKeyUseValues::Signature)
240
            throw new JWSInvalidJWKException
241
            (
242
                sprintf
243
                (
244
                    'use %s not supported ',
245
                    $this->jwk->getKeyUse()->getString()
246
                )
247
            );
248
249
        if(is_null($this->jwk->getAlgorithm()))
250
            throw new InvalidJWKAlgorithm('algorithm intended for use with the key is not set! ');
251
252
        if(!is_null($this->jwk->getId()) && !is_null($this->header->getKeyID()) && $this->header->getKeyID()->getValue() != $this->jwk->getId()->getValue())
253
            throw new JWSInvalidJWKException
254
            (
255
                sprintf
256
                (
257
                    'original kid %s - current kid %s',
258
                    $this->header->getKeyID()->getValue(),
259
                    $this->jwk->getId()->getValue()
260
                )
261
            );
262
263
        $alg = DigitalSignatures_MACs_Registry::getInstance()->get($original_alg);
264
265
        if(is_null($alg))
266
            throw new JWSNotSupportedAlgorithm(sprintf('algo %s', $original_alg));
267
268
        $former_alg = $this->header->getAlgorithm()->getString();
269
270
        if($former_alg != $original_alg)
271
            throw new JWSNotSupportedAlgorithm
272
            (
273
                sprintf
274
                (
275
                    'former alg %s - original alg %s',
276
                    $former_alg,
277
                    $original_alg
278
                )
279
            );
280
281
        if($this->jwk->getAlgorithm()->getValue() !==  $original_alg)
282
            throw new InvalidJWKAlgorithm
283
            (
284
                sprintf
285
                (
286
                    'mismatch between algorithm intended for use with the key %s and the cryptographic algorithm used to secure the JWS %s',
287
                    $this->jwk->getAlgorithm()->getValue(),
288
                    $original_alg
289
                )
290
            );
291
292
        $secured_input_bytes = JOSEHeaderSerializer::serialize($this->header) . IBasicJWT::SegmentSeparator .$this->getEncodedPayload();
293
294
        // use public key / secret
295
        $key = $this->jwk->getKey(JSONWebKeyKeyOperationsValues::VerifyDigitalSignatureOrMAC);
296
        return $alg->verify($key, $secured_input_bytes, $this->signature);
297
    }
298
299
    /**
300
     * @return IJWSPayloadSpec
301
     */
302
    public function getPayload()
303
    {
304
        return $this->payload;
305
    }
306
307
     /**
308
     * @param IJOSEHeader $header
309
     * @param IJWSPayloadSpec $payload
310
     * @param string $signature
311
     * @return IJWS
312
     */
313
    static public function fromHeaderClaimsAndSignature(IJOSEHeader $header, IJWSPayloadSpec $payload = null , $signature = '')
314
    {
315
        return new JWS($header, $payload, $signature );
316
    }
317
318
    /**
319
     * @return array
320
     */
321
    public function take()
322
    {
323
        $payload = $this->payload->isClaimSet() ?  $this->claim_set : $this->payload->getRaw();
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface jws\IJWSPayloadSpec as the method getRaw() does only exist in the following implementations of said interface: jws\payloads\_JWSPayloadRawSpec.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
324
325
        return array
326
        (
327
            $this->header,
328
            $payload,
329
            $this->signature
330
        );
331
    }
332
}