Skip to content

Commit 7708daa

Browse files
author
ogorkun
committed
MC-38539: Introduce JWT wrapper
1 parent 1376ca4 commit 7708daa

File tree

5 files changed

+505
-85
lines changed

5 files changed

+505
-85
lines changed

app/code/Magento/JwtFrameworkAdapter/Model/JweManager.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ public function build(JweInterface $jwe, EncryptionSettingsInterface $encryption
7777
$builder = $builder->withPayload($payload->getContent());
7878

7979
$sharedProtected = $this->extractHeaderData($jwe->getProtectedHeader());
80-
if (!$jwe->getPerRecipientUnprotectedHeaders()) {
81-
$sharedProtected['enc'] = $encryptionSettings->getContentEncryptionAlgorithm();
82-
}
80+
$sharedProtected['enc'] = $encryptionSettings->getContentEncryptionAlgorithm();
8381
if (!$jwe->getPerRecipientUnprotectedHeaders()) {
8482
$sharedProtected['alg'] = $encryptionSettings->getAlgorithmName();
8583
}
@@ -117,7 +115,10 @@ public function build(JweInterface $jwe, EncryptionSettingsInterface $encryption
117115
}
118116

119117
$built = $builder->build();
120-
if ($jwe->getPerRecipientUnprotectedHeaders() && count($jwe->getPerRecipientUnprotectedHeaders()) === 1) {
118+
if ($jwe->getPerRecipientUnprotectedHeaders()
119+
&& count($jwe->getPerRecipientUnprotectedHeaders()) === 1
120+
|| (!$jwe->getPerRecipientUnprotectedHeaders() && $jwe->getSharedUnprotectedHeader())
121+
) {
121122
return $this->serializer->serialize('jwe_json_flattened', $built);
122123
}
123124
if ($jwe->getPerRecipientUnprotectedHeaders()) {

app/code/Magento/JwtFrameworkAdapter/Model/JwtManager.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Magento\Framework\Jwt\EncryptionSettingsInterface;
1212
use Magento\Framework\Jwt\Exception\JwtException;
1313
use Magento\Framework\Jwt\Exception\MalformedTokenException;
14+
use Magento\Framework\Jwt\Jwe\JweEncryptionSettingsInterface;
1415
use Magento\Framework\Jwt\Jwe\JweInterface;
1516
use Magento\Framework\Jwt\Jwk;
1617
use Magento\Framework\Jwt\Jws\JwsInterface;
@@ -145,6 +146,9 @@ private function detectJwtType(EncryptionSettingsInterface $encryptionSettings):
145146
if ($encryptionSettings instanceof JwsSignatureSettingsInterface) {
146147
return self::JWT_TYPE_JWS;
147148
}
149+
if ($encryptionSettings instanceof JweEncryptionSettingsInterface) {
150+
return self::JWT_TYPE_JWE;
151+
}
148152

149153
if ($encryptionSettings->getAlgorithmName() === Jwk::ALGORITHM_NONE) {
150154
return self::JWT_TYPE_UNSECURED;

dev/tests/integration/testsuite/Magento/Framework/Jwt/JwtManagerTest.php

Lines changed: 214 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Magento\Framework\Jwt\Claim\PrivateClaim;
1616
use Magento\Framework\Jwt\Claim\Subject;
1717
use Magento\Framework\Jwt\Header\Critical;
18+
use Magento\Framework\Jwt\Header\KeyId;
1819
use Magento\Framework\Jwt\Header\PrivateHeaderParameter;
1920
use Magento\Framework\Jwt\Header\PublicHeaderParameter;
2021
use Magento\Framework\Jwt\Jwe\Jwe;
@@ -226,11 +227,106 @@ public function getTokenVariants(): array
226227
]
227228
)
228229
);
230+
$jsonFlatSharedHeaderJwe = new Jwe(
231+
new JweHeader(
232+
[
233+
new PrivateHeaderParameter('test', true),
234+
new PublicHeaderParameter('test2', 'magento', 'value')
235+
]
236+
),
237+
new JweHeader(
238+
[
239+
new PrivateHeaderParameter('mage', 'test')
240+
]
241+
),
242+
null,
243+
new ClaimsPayload(
244+
[
245+
new PrivateClaim('custom-claim', 'value'),
246+
new PrivateClaim('custom-claim2', 'value2', true),
247+
new PrivateClaim('custom-claim3', 'value3'),
248+
new IssuedAt(new \DateTimeImmutable()),
249+
new Issuer('magento.com')
250+
]
251+
)
252+
);
253+
$jsonFlatJwe = new Jwe(
254+
new JweHeader(
255+
[
256+
new PrivateHeaderParameter('test', true),
257+
new PublicHeaderParameter('test2', 'magento', 'value')
258+
]
259+
),
260+
null,
261+
[
262+
new JweHeader(
263+
[
264+
new PrivateHeaderParameter('mage', 'test')
265+
]
266+
)
267+
],
268+
new ClaimsPayload(
269+
[
270+
new PrivateClaim('custom-claim', 'value'),
271+
new PrivateClaim('custom-claim2', 'value2', true),
272+
new PrivateClaim('custom-claim3', 'value3'),
273+
new IssuedAt(new \DateTimeImmutable()),
274+
new Issuer('magento.com')
275+
]
276+
)
277+
);
278+
$jsonJwe = new Jwe(
279+
new JweHeader(
280+
[
281+
new PrivateHeaderParameter('test', true),
282+
new PublicHeaderParameter('test2', 'magento', 'value')
283+
]
284+
),
285+
new JweHeader(
286+
[
287+
new PrivateHeaderParameter('mage', 'test')
288+
]
289+
),
290+
[
291+
new JweHeader([new PrivateHeaderParameter('tst', 2)]),
292+
new JweHeader([new PrivateHeaderParameter('test2', 3)])
293+
],
294+
new ClaimsPayload(
295+
[
296+
new PrivateClaim('custom-claim', 'value'),
297+
new PrivateClaim('custom-claim2', 'value2', true),
298+
new PrivateClaim('custom-claim3', 'value3'),
299+
new IssuedAt(new \DateTimeImmutable()),
300+
new Issuer('magento.com')
301+
]
302+
)
303+
);
304+
$jsonJweKids = new Jwe(
305+
new JweHeader(
306+
[
307+
new PrivateHeaderParameter('test', true),
308+
]
309+
),
310+
null,
311+
[
312+
new JweHeader([new PrivateHeaderParameter('tst', 2), new KeyId('1')]),
313+
new JweHeader([new PrivateHeaderParameter('test2', 3), new KeyId('2')])
314+
],
315+
new ClaimsPayload(
316+
[
317+
new PrivateClaim('custom-claim', 'value'),
318+
new PrivateClaim('custom-claim2', 'value2', true),
319+
new PrivateClaim('custom-claim3', 'value3'),
320+
new IssuedAt(new \DateTimeImmutable()),
321+
new Issuer('magento.com')
322+
]
323+
)
324+
);
229325

230326
//Keys
231327
[$rsaPrivate, $rsaPublic] = $this->createRsaKeys();
232328
$ecKeys = $this->createEcKeys();
233-
$sharedSecret = random_bytes(128);
329+
$sharedSecret = random_bytes(2048);
234330

235331
return [
236332
'jws-HS256' => [
@@ -263,7 +359,7 @@ public function getTokenVariants(): array
263359
new JwsSignatureJwks($jwkFactory->createSignRs512($rsaPrivate, 'pass')),
264360
[new JwsSignatureJwks($jwkFactory->createVerifyRs512($rsaPublic))]
265361
],
266-
'jws-compact-multiple-signatures' => [
362+
'jws-json-multiple-signatures' => [
267363
$compactJws,
268364
new JwsSignatureJwks(
269365
new JwkSet(
@@ -281,7 +377,7 @@ public function getTokenVariants(): array
281377
)
282378
]
283379
],
284-
'jws-compact-multiple-signatures-one-read' => [
380+
'jws-json-multiple-signatures-one-read' => [
285381
$compactJws,
286382
new JwsSignatureJwks(
287383
new JwkSet(
@@ -335,6 +431,121 @@ public function getTokenVariants(): array
335431
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
336432
)
337433
]
434+
],
435+
'jwe-A192KW' => [
436+
$jsonFlatSharedHeaderJwe,
437+
new JweEncryptionJwks(
438+
$jwkFactory->createA192KW($sharedSecret),
439+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
440+
),
441+
[
442+
new JweEncryptionJwks(
443+
$jwkFactory->createA192KW($sharedSecret),
444+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
445+
)
446+
]
447+
],
448+
'jwe-A256KW' => [
449+
$jsonFlatJwe,
450+
new JweEncryptionJwks(
451+
$jwkFactory->createA256KW($sharedSecret),
452+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
453+
),
454+
[
455+
new JweEncryptionJwks(
456+
$jwkFactory->createA256KW($sharedSecret),
457+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
458+
)
459+
]
460+
],
461+
'jwe-multiple-recipients' => [
462+
$jsonJwe,
463+
new JweEncryptionJwks(
464+
new JwkSet(
465+
[
466+
$jwkFactory->createA256KW($sharedSecret),
467+
$jwkFactory->createA128KW($sharedSecret)
468+
]
469+
),
470+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
471+
),
472+
[
473+
new JweEncryptionJwks(
474+
new JwkSet(
475+
[
476+
$jwkFactory->createA256KW($sharedSecret),
477+
]
478+
),
479+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
480+
)
481+
]
482+
],
483+
'jwe-rsa-oaep' => [
484+
$flatJwe,
485+
new JweEncryptionJwks(
486+
$jwkFactory->createEncryptRsaOaep($rsaPublic),
487+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
488+
),
489+
[
490+
new JweEncryptionJwks(
491+
$jwkFactory->createDecryptRsaOaep($rsaPrivate, 'pass'),
492+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
493+
)
494+
]
495+
],
496+
'jwe-rsa-oaep-256' => [
497+
$flatJwe,
498+
new JweEncryptionJwks(
499+
$jwkFactory->createEncryptRsaOaep256($rsaPublic),
500+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A192GCM
501+
),
502+
[
503+
new JweEncryptionJwks(
504+
$jwkFactory->createDecryptRsaOaep256($rsaPrivate, 'pass'),
505+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A192GCM
506+
)
507+
]
508+
],
509+
'jwe-dir' => [
510+
$flatJwe,
511+
new JweEncryptionJwks(
512+
$jwkFactory->createDir(
513+
$sharedSecret,
514+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A192_HS384
515+
),
516+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A192_HS384
517+
),
518+
[
519+
new JweEncryptionJwks(
520+
$jwkFactory->createDir(
521+
$sharedSecret,
522+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A192_HS384
523+
),
524+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A192_HS384
525+
)
526+
]
527+
],
528+
'jwe-multiple-recipients-kids' => [
529+
$jsonJweKids,
530+
new JweEncryptionJwks(
531+
new JwkSet(
532+
[
533+
$jwkFactory->createEncryptRsaOaep256($rsaPublic, '2'),
534+
$jwkFactory->createA256KW($sharedSecret, '1')
535+
]
536+
),
537+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
538+
),
539+
[
540+
new JweEncryptionJwks(
541+
new JwkSet(
542+
[
543+
$jwkFactory->createDecryptRsaOaep256($rsaPrivate, 'pass', '2')
544+
]
545+
),
546+
JweEncryptionSettingsInterface::CONTENT_ENCRYPTION_ALGO_A128_HS256
547+
)
548+
]
338549
]
339550
];
340551
}

lib/internal/Magento/Framework/Jwt/Jwk.php

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,17 @@ class Jwk
144144
*/
145145
private $x5ts256;
146146

147+
/**
148+
* @var string|null
149+
*/
150+
private $contentEncryption;
151+
147152
/**
148153
* @var array
149154
*/
150155
private $data;
151156

152157
/**
153-
* Jwk constructor.
154158
* @param string $kty
155159
* @param array $data
156160
* @param string|null $use
@@ -161,6 +165,7 @@ class Jwk
161165
* @param string|null $x5t
162166
* @param string|null $x5ts256
163167
* @param string|null $kid
168+
* @param string|null $contentEncryption
164169
*/
165170
public function __construct(
166171
string $kty,
@@ -172,7 +177,8 @@ public function __construct(
172177
?array $x5c = null,
173178
?string $x5t = null,
174179
?string $x5ts256 = null,
175-
?string $kid = null
180+
?string $kid = null,
181+
?string $contentEncryption = null
176182
) {
177183
$this->kty = $kty;
178184
$this->data = $data;
@@ -184,6 +190,12 @@ public function __construct(
184190
$this->x5t = $x5t;
185191
$this->x5ts256 = $x5ts256;
186192
$this->kid = $kid;
193+
if ($contentEncryption && $alg !== self::ALGORITHM_DIR) {
194+
throw new \InvalidArgumentException(
195+
'Can only specify content encryption algorithm as "alg" for JWEs with "dir" algorithm'
196+
);
197+
}
198+
$this->contentEncryption = $contentEncryption;
187199
}
188200

189201
/**
@@ -276,6 +288,16 @@ public function getKeyId(): ?string
276288
return $this->kid;
277289
}
278290

291+
/**
292+
* Content encryption algorithm for JWEs with "alg" == "dir".
293+
*
294+
* @return string|null
295+
*/
296+
public function getContentEncryption(): ?string
297+
{
298+
return $this->contentEncryption;
299+
}
300+
279301
/**
280302
* Map with algorithm (type) specific data.
281303
*
@@ -297,14 +319,14 @@ public function getJsonData(): array
297319
'kty' => $this->getKeyType(),
298320
'use' => $this->getPublicKeyUse(),
299321
'key_ops' => $this->getKeyOperations(),
300-
'alg' => $this->getAlgorithm(),
322+
'alg' => $this->getContentEncryption() ?? $this->getAlgorithm(),
301323
'x5u' => $this->getX509Url(),
302324
'x5c' => $this->getX509CertificateChain(),
303325
'x5t' => $this->getX509Sha1Thumbprint(),
304326
'x5t#S256' => $this->getX509Sha256Thumbprint(),
305327
'kid' => $this->getKeyId()
306328
];
307-
$data = array_merge($data, $this->getAlgoData());
329+
$data = array_merge($this->getAlgoData(), $data);
308330

309331
return array_filter($data, function ($value) {
310332
return $value !== null;

0 commit comments

Comments
 (0)