Passed
Push — master ( 90db0f...396226 )
by Jafar
03:16
created

JWS::load()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 17
nc 12
nop 3
dl 0
loc 33
rs 5.3846
c 0
b 0
f 0
1
<?php
2
/*
3
 * This file is part of the Guarded Authentication package.
4
 *
5
 * (c) Jafar Jabr <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner;
12
13
use InvalidArgumentException;
14
use Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner\Base64\Base64Encoder;
15
use Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner\Base64\Base64UrlSafeEncoder;
16
use Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner\Base64\EncoderInterface;
17
use Jafar\Bundle\GuardedAuthenticationBundle\Api\JWTSigner\Signer\SignerInterface;
18
19
/**
20
 * Class JWS.
21
 *
22
 * @author Jafar Jabr <[email protected]>
23
 */
24
class JWS extends JWT
25
{
26
    protected $signature;
27
28
    protected $isSigned = false;
29
30
    protected $originalToken;
31
32
    protected $encodedSignature;
33
34
    protected $encryptionEngine = 'OpenSSL';
35
36
    /**
37
     * Constructor.
38
     *
39
     * @param array $header An associative array of headers. The value can be any type accepted by json_encode or a JSON serializable object
40
     */
41
    public function __construct($header = [])
42
    {
43
        parent::__construct([], $header);
44
    }
45
46
    /**
47
     * Signs the JWS signininput.
48
     *
49
     * @param resource|string $key
50
     *
51
     * @return string
52
     */
53
    public function sign($key)
54
    {
55
        $this->signature = $this->getSigner()->sign($this->generateSigninInput(), $key);
56
        $this->isSigned  = true;
57
58
        return $this->signature;
59
    }
60
61
    /**
62
     * Returns the signature representation of the JWS.
63
     *
64
     * @return string
65
     */
66
    public function getSignature()
67
    {
68
        if ($this->isSigned()) {
69
            return $this->signature;
70
        }
71
72
        return;
73
    }
74
75
    /**
76
     * Checks whether the JSW has already been signed.
77
     *
78
     * @return bool
79
     */
80
    public function isSigned()
81
    {
82
        return (bool) $this->isSigned;
83
    }
84
85
    /**
86
     * Returns the string representing the JWT.
87
     *
88
     * @return string
89
     */
90
    public function getTokenString()
91
    {
92
        $signinInput = $this->generateSigninInput();
93
94
        return sprintf('%s.%s', $signinInput, $this->encoder->encode($this->getSignature()));
95
    }
96
97
    /**
98
     * Creates an instance of a JWS from a JWT.
99
     *
100
     * @param string           $jwsTokenString
101
     * @param bool             $allowUnsecure
102
     * @param EncoderInterface $encoder
103
     *
104
     * @return JWS
105
     *
106
     * @throws \InvalidArgumentException
107
     */
108
    public static function load(
109
        $jwsTokenString,
110
        $allowUnsecure = false,
111
        EncoderInterface $encoder = null
112
    ) {
113
        if (null === $encoder) {
114
            $encoder = strpbrk($jwsTokenString, '+/=') ? new Base64Encoder() : new Base64UrlSafeEncoder();
115
        }
116
117
        $parts = explode('.', $jwsTokenString);
118
119
        if (3 === count($parts)) {
120
            $header  = json_decode($encoder->decode($parts[0]), true);
121
            $payload = json_decode($encoder->decode($parts[1]), true);
122
123
            if (is_array($header) && is_array($payload)) {
124
                if ('none' === strtolower($header['alg']) && !$allowUnsecure) {
125
                    throw new InvalidArgumentException(sprintf('The token "%s" cannot be validated in a secure context, as it uses the unallowed "none" algorithm', $jwsTokenString));
126
                }
127
128
                $jws = new static($header);
129
130
                $jws->setEncoder($encoder)
131
                    ->setHeader($header)
132
                    ->setPayload($payload)
133
                    ->setOriginalToken($jwsTokenString)
134
                    ->setEncodedSignature($parts[2]);
135
136
                return $jws;
137
            }
138
        }
139
140
        throw new InvalidArgumentException(sprintf('The token "%s" is an invalid JWS', $jwsTokenString));
141
    }
142
143
    /**
144
     * Verifies that the internal signin input corresponds to the encoded
145
     * signature previously stored (@see JWS::load).
146
     *
147
     * @param resource|string $key
148
     * @param string          $algo The algorithms this JWS should be signed with. Use it if you want to restrict which algorithms you want to allow to be validated.
149
     *
150
     * @return bool
151
     */
152
    public function verify($key, $algo = null)
153
    {
154
        if (empty($key) || ($algo && $this->header['alg'] !== $algo)) {
155
            return false;
156
        }
157
158
        $decodedSignature = $this->encoder->decode($this->getEncodedSignature());
159
        $signinInput      = $this->getSigninInput();
160
161
        return $this->getSigner()->verify($key, $decodedSignature, $signinInput);
162
    }
163
164
    /**
165
     * Get the original token signin input if it exists, otherwise generate the
166
     * signin input for the current JWS.
167
     *
168
     * @return string
169
     */
170
    private function getSigninInput()
171
    {
172
        $parts = explode('.', $this->originalToken);
173
174
        if (count($parts) >= 2) {
175
            return sprintf('%s.%s', $parts[0], $parts[1]);
176
        }
177
178
        return $this->generateSigninInput();
179
    }
180
181
    /**
182
     * Sets the original base64 encoded token.
183
     *
184
     * @param string $originalToken
185
     *
186
     * @return JWS
187
     */
188
    private function setOriginalToken($originalToken)
189
    {
190
        $this->originalToken = $originalToken;
191
192
        return $this;
193
    }
194
195
    /**
196
     * Returns the base64 encoded signature.
197
     *
198
     * @return string
199
     */
200
    public function getEncodedSignature()
201
    {
202
        return $this->encodedSignature;
203
    }
204
205
    /**
206
     * Sets the base64 encoded signature.
207
     *
208
     * @param string $encodedSignature
209
     *
210
     * @return JWS
211
     */
212
    public function setEncodedSignature($encodedSignature)
213
    {
214
        $this->encodedSignature = $encodedSignature;
215
216
        return $this;
217
    }
218
219
    /**
220
     * Returns the signer responsible to encrypting / decrypting this JWS.
221
     *
222
     * @return SignerInterface
223
     *
224
     * @throws \InvalidArgumentException
225
     */
226
    protected function getSigner()
227
    {
228
        $signerClass = sprintf('Jafar\\Bundle\\GuardedAuthenticationBundle\\Api\\JWTSigner\\Signer\\%s\\%s', $this->encryptionEngine, $this->header['alg']);
229
230
        if (class_exists($signerClass)) {
231
            return new $signerClass();
232
        }
233
234
        throw new InvalidArgumentException(
235
            sprintf("The algorithm '%s' is not supported for %s", $this->header['alg'], $this->encryptionEngine)
236
        );
237
    }
238
}
239