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\IJWSPayloadRawSpec; |
32
|
|
|
use jws\IJWSPayloadSpec; |
33
|
|
|
use jws\payloads\JWSPayloadFactory; |
34
|
|
|
use jwt\IBasicJWT; |
35
|
|
|
use jwt\IJOSEHeader; |
36
|
|
|
use jwt\impl\JWT; |
37
|
|
|
use jwt\impl\JWTSerializer; |
38
|
|
|
use jwt\JOSEHeaderParam; |
39
|
|
|
use jwt\RegisteredJOSEHeaderNames; |
40
|
|
|
use jwt\utils\JOSEHeaderSerializer; |
41
|
|
|
use jwt\utils\JWTClaimSetSerializer; |
42
|
|
|
use jwt\utils\JWTRawSerializer; |
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 = ''; |
178
|
|
|
|
179
|
|
|
if($this->payload instanceof IJWSPayloadClaimSetSpec) |
180
|
|
|
{ |
181
|
|
|
$enc_payload = JWTClaimSetSerializer::serialize($this->payload->getClaimSet()); |
182
|
|
|
} |
183
|
|
|
else if($this->payload instanceof IJWSPayloadRawSpec) |
184
|
|
|
{ |
185
|
|
|
$enc_payload = JWTRawSerializer::serialize($this->payload->getRaw()); |
186
|
|
|
} |
187
|
|
|
return $enc_payload; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* @param IJWK $key |
192
|
|
|
* @return $this |
193
|
|
|
*/ |
194
|
|
|
public function setKey(IJWK $key) |
195
|
|
|
{ |
196
|
|
|
$this->jwk = $key; |
197
|
|
|
return $this; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @param string $compact_serialization |
202
|
|
|
* @return IJWS |
203
|
|
|
* @access private |
204
|
|
|
*/ |
205
|
|
|
static public function fromCompactSerialization($compact_serialization) |
|
|
|
|
206
|
|
|
{ |
207
|
|
|
list($header, $payload, $signature) = JWTSerializer::deserialize($compact_serialization); |
208
|
|
|
return new JWS($header, JWSPayloadFactory::build($payload), $signature); |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @return StringOrURI |
213
|
|
|
*/ |
214
|
|
|
public function getSigningAlgorithm() |
215
|
|
|
{ |
216
|
|
|
return $this->header->getAlgorithm(); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* @return StringOrURI |
221
|
|
|
*/ |
222
|
|
|
public function getType() |
223
|
|
|
{ |
224
|
|
|
return $this->header->getType(); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @param string $original_alg |
229
|
|
|
* @return bool |
230
|
|
|
* @throws InvalidJWKAlgorithm |
231
|
|
|
* @throws JWSInvalidJWKException |
232
|
|
|
* @throws JWSInvalidPayloadException |
233
|
|
|
* @throws JWSNotSupportedAlgorithm |
234
|
|
|
*/ |
235
|
|
|
public function verify($original_alg) |
236
|
|
|
{ |
237
|
|
|
if(is_null($this->jwk)) |
238
|
|
|
throw new JWSInvalidJWKException; |
239
|
|
|
|
240
|
|
View Code Duplication |
if($this->jwk->getKeyUse()->getString() !== JSONWebKeyPublicKeyUseValues::Signature) |
|
|
|
|
241
|
|
|
throw new JWSInvalidJWKException |
242
|
|
|
( |
243
|
|
|
sprintf |
244
|
|
|
( |
245
|
|
|
'use %s not supported ', |
246
|
|
|
$this->jwk->getKeyUse()->getString() |
247
|
|
|
) |
248
|
|
|
); |
249
|
|
|
|
250
|
|
|
if(is_null($this->jwk->getAlgorithm())) |
251
|
|
|
throw new InvalidJWKAlgorithm('algorithm intended for use with the key is not set! '); |
252
|
|
|
|
253
|
|
|
if(!is_null($this->jwk->getId()) && !is_null($this->header->getKeyID()) && $this->header->getKeyID()->getValue() != $this->jwk->getId()->getValue()) |
254
|
|
|
throw new JWSInvalidJWKException |
255
|
|
|
( |
256
|
|
|
sprintf |
257
|
|
|
( |
258
|
|
|
'original kid %s - current kid %s', |
259
|
|
|
$this->header->getKeyID()->getValue(), |
260
|
|
|
$this->jwk->getId()->getValue() |
261
|
|
|
) |
262
|
|
|
); |
263
|
|
|
|
264
|
|
|
$alg = DigitalSignatures_MACs_Registry::getInstance()->get($original_alg); |
265
|
|
|
|
266
|
|
|
if(is_null($alg)) |
267
|
|
|
throw new JWSNotSupportedAlgorithm(sprintf('algo %s', $original_alg)); |
268
|
|
|
|
269
|
|
|
$former_alg = $this->header->getAlgorithm()->getString(); |
270
|
|
|
|
271
|
|
|
if($former_alg != $original_alg) |
272
|
|
|
throw new JWSNotSupportedAlgorithm |
273
|
|
|
( |
274
|
|
|
sprintf |
275
|
|
|
( |
276
|
|
|
'former alg %s - original alg %s', |
277
|
|
|
$former_alg, |
278
|
|
|
$original_alg |
279
|
|
|
) |
280
|
|
|
); |
281
|
|
|
|
282
|
|
|
if($this->jwk->getAlgorithm()->getValue() !== $original_alg) |
283
|
|
|
throw new InvalidJWKAlgorithm |
284
|
|
|
( |
285
|
|
|
sprintf |
286
|
|
|
( |
287
|
|
|
'mismatch between algorithm intended for use with the key %s and the cryptographic algorithm used to secure the JWS %s', |
288
|
|
|
$this->jwk->getAlgorithm()->getValue(), |
289
|
|
|
$original_alg |
290
|
|
|
) |
291
|
|
|
); |
292
|
|
|
|
293
|
|
|
$secured_input_bytes = JOSEHeaderSerializer::serialize($this->header) . IBasicJWT::SegmentSeparator .$this->getEncodedPayload(); |
294
|
|
|
|
295
|
|
|
// use public key / secret |
296
|
|
|
$key = $this->jwk->getKey(JSONWebKeyKeyOperationsValues::VerifyDigitalSignatureOrMAC); |
297
|
|
|
return $alg->verify($key, $secured_input_bytes, $this->signature); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
/** |
301
|
|
|
* @return IJWSPayloadSpec |
302
|
|
|
*/ |
303
|
|
|
public function getPayload() |
304
|
|
|
{ |
305
|
|
|
return $this->payload; |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* @param IJOSEHeader $header |
310
|
|
|
* @param IJWSPayloadSpec $payload |
311
|
|
|
* @param string $signature |
312
|
|
|
* @return IJWS |
313
|
|
|
*/ |
314
|
|
|
static public function fromHeaderClaimsAndSignature(IJOSEHeader $header, IJWSPayloadSpec $payload = null , $signature = '') |
|
|
|
|
315
|
|
|
{ |
316
|
|
|
return new JWS($header, $payload, $signature ); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
/** |
320
|
|
|
* @return array |
321
|
|
|
*/ |
322
|
|
|
public function take() |
323
|
|
|
{ |
324
|
|
|
$payload = ($this->payload instanceof IJWSPayloadRawSpec) ? $this->payload->getRaw() : $this->claim_set; |
325
|
|
|
return array |
326
|
|
|
( |
327
|
|
|
$this->header, |
328
|
|
|
$payload, |
329
|
|
|
$this->signature |
330
|
|
|
); |
331
|
|
|
} |
332
|
|
|
} |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.