Skip to content

Commit 64c2f94

Browse files
author
ogorkun
committed
MC-38539: Introduce JWT wrapper
1 parent 74d3140 commit 64c2f94

22 files changed

+907
-30
lines changed

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

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -184,22 +184,7 @@ public function read(string $token, array $acceptableEncryption): JwtInterface
184184
*/
185185
private function convertToAdapterJwk(Jwk $jwk): AdapterJwk
186186
{
187-
$data = [
188-
'kty' => $jwk->getKeyType(),
189-
'use' => $jwk->getPublicKeyUse(),
190-
'key_ops' => $jwk->getKeyOperations(),
191-
'alg' => $jwk->getAlgorithm(),
192-
'x5u' => $jwk->getX509Url(),
193-
'x5c' => $jwk->getX509CertificateChain(),
194-
'x5t' => $jwk->getX509Sha1Thumbprint(),
195-
'x5t#S256' => $jwk->getX509Sha256Thumbprint()
196-
];
197-
$data = array_merge($data, $jwk->getAlgoData());
198-
$data = array_filter($data, function ($value) {
199-
return $value !== null;
200-
});
201-
202-
return new AdapterJwk($data);
187+
return new AdapterJwk($jwk->getJsonData());
203188
}
204189

205190
private function convertToAdapterKeySet(JwkSet $jwkSet): AdapterJwkSet
@@ -256,14 +241,16 @@ private function buildJws(JwsInterface $jws, EncryptionSettingsInterface $encryp
256241
throw new EncryptionException('Algorithm is required for JWKs');
257242
}
258243
$protected = [];
244+
if ($jws->getPayload()->getContentType()) {
245+
$protected['cty'] = $jws->getPayload()->getContentType();
246+
}
259247
if ($jws->getProtectedHeaders()) {
260248
$protected = $this->extractHeaderData($jws->getProtectedHeaders()[$i]);
261249
}
262250
$protected['alg'] = $alg;
263251
$unprotected = [];
264252
if ($jws->getUnprotectedHeaders()) {
265253
$unprotected = $this->extractHeaderData($jws->getUnprotectedHeaders()[$i]);
266-
$unprotected['alg'] = $alg;
267254
}
268255
$builder = $builder->addSignature($this->convertToAdapterJwk($jwk), $protected, $unprotected);
269256
}

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

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
namespace Magento\Framework\Jwt;
1010

1111
use Magento\Framework\Jwt\Claim\PrivateClaim;
12+
use Magento\Framework\Jwt\Header\Critical;
1213
use Magento\Framework\Jwt\Header\PrivateHeaderParameter;
14+
use Magento\Framework\Jwt\Header\PublicHeaderParameter;
1315
use Magento\Framework\Jwt\Jwe\JweInterface;
1416
use Magento\Framework\Jwt\Jws\Jws;
1517
use Magento\Framework\Jwt\Jws\JwsHeader;
@@ -42,14 +44,18 @@ protected function setUp(): void
4244
*
4345
* @param JwtInterface $jwt
4446
* @param EncryptionSettingsInterface $encryption
47+
* @param EncryptionSettingsInterface[] $readEncryption
4548
* @return void
4649
*
4750
* @dataProvider getTokenVariants
4851
*/
49-
public function testCreateRead(JwtInterface $jwt, EncryptionSettingsInterface $encryption): void
50-
{
52+
public function testCreateRead(
53+
JwtInterface $jwt,
54+
EncryptionSettingsInterface $encryption,
55+
array $readEncryption
56+
): void {
5157
$token = $this->manager->create($jwt, $encryption);
52-
$recreated = $this->manager->read($token, [$encryption]);
58+
$recreated = $this->manager->read($token, $readEncryption);
5359

5460
//Verifying header
5561
$this->verifyHeader($jwt->getHeader(), $recreated->getHeader());
@@ -88,7 +94,7 @@ public function getTokenVariants(): array
8894
/** @var JwkFactory $jwkFactory */
8995
$jwkFactory = Bootstrap::getObjectManager()->get(JwkFactory::class);
9096

91-
$hmacFlatJws = new Jws(
97+
$flatJws = new Jws(
9298
[
9399
new JwsHeader(
94100
[
@@ -107,18 +113,59 @@ public function getTokenVariants(): array
107113
null
108114
);
109115

116+
$flatJwsWithUnprotectedHeader = new Jws(
117+
[
118+
new JwsHeader(
119+
[
120+
new PrivateHeaderParameter('custom-header', 'value'),
121+
new Critical(['magento'])
122+
]
123+
)
124+
],
125+
new ClaimsPayload(
126+
[
127+
new PrivateClaim('custom-claim', 'value'),
128+
new PrivateClaim('custom-claim2', 'value2')
129+
]
130+
),
131+
[
132+
new JwsHeader(
133+
[
134+
new PublicHeaderParameter('public-header', 'magento', 'public-value')
135+
]
136+
)
137+
]
138+
);
139+
$rsaPrivateResource = openssl_pkey_new(['private_key_bites' => 512, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
140+
if ($rsaPrivateResource === false) {
141+
throw new \RuntimeException('Failed to create RSA keypair');
142+
}
143+
$rsaPublic = openssl_pkey_get_details($rsaPrivateResource)['key'];
144+
if (!openssl_pkey_export($rsaPrivateResource, $rsaPrivate)) {
145+
throw new \RuntimeException('Failed to read RSA private key');
146+
}
147+
openssl_free_key($rsaPrivateResource);
148+
110149
return [
111150
'jws-HS256' => [
112-
$hmacFlatJws,
113-
new JwsSignatureJwks($jwkFactory->createHs256(random_bytes(128)))
151+
$flatJws,
152+
$enc = new JwsSignatureJwks($jwkFactory->createHs256(random_bytes(128))),
153+
[$enc]
114154
],
115155
'jws-HS384' => [
116-
$hmacFlatJws,
117-
new JwsSignatureJwks($jwkFactory->createHs384(random_bytes(128)))
156+
$flatJws,
157+
$enc = new JwsSignatureJwks($jwkFactory->createHs384(random_bytes(128))),
158+
[$enc]
118159
],
119160
'jws-HS512' => [
120-
$hmacFlatJws,
121-
new JwsSignatureJwks($jwkFactory->createHs512(random_bytes(128)))
161+
$flatJwsWithUnprotectedHeader,
162+
$enc = new JwsSignatureJwks($jwkFactory->createHs512(random_bytes(128))),
163+
[$enc]
164+
],
165+
'jws-RS256' => [
166+
$flatJws,
167+
new JwsSignatureJwks($jwkFactory->createSignRs256($rsaPrivate, null)),
168+
[new JwsSignatureJwks($jwkFactory->createVerifyRs256($rsaPublic))]
122169
]
123170
];
124171
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\Jwt\Header;
10+
11+
use Magento\Framework\Jwt\Jwe\JweHeaderParameterInterface;
12+
use Magento\Framework\Jwt\Jws\JwsHeaderParameterInterface;
13+
14+
/**
15+
* "alg" header.
16+
*/
17+
class Algorithm implements JwsHeaderParameterInterface, JweHeaderParameterInterface
18+
{
19+
/**
20+
* @var string
21+
*/
22+
private $value;
23+
24+
/**
25+
* @param string $value
26+
*/
27+
public function __construct(string $value)
28+
{
29+
$this->value = $value;
30+
}
31+
32+
/**
33+
* @inheritDoc
34+
*/
35+
public function getName(): string
36+
{
37+
return 'alg';
38+
}
39+
40+
/**
41+
* @inheritDoc
42+
*/
43+
public function getValue()
44+
{
45+
return $this->value;
46+
}
47+
48+
/**
49+
* @inheritDoc
50+
*/
51+
public function getClass(): ?int
52+
{
53+
return self::CLASS_REGISTERED;
54+
}
55+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\Jwt\Header;
10+
11+
use Magento\Framework\Jwt\Jwe\JweHeaderParameterInterface;
12+
use Magento\Framework\Jwt\Jws\JwsHeaderParameterInterface;
13+
14+
/**
15+
* "crit" header.
16+
*/
17+
class Critical implements JwsHeaderParameterInterface, JweHeaderParameterInterface
18+
{
19+
/**
20+
* @var string[]
21+
*/
22+
private $value;
23+
24+
/**
25+
* @param string[] $value
26+
*/
27+
public function __construct(array $value)
28+
{
29+
if (!$value) {
30+
throw new \InvalidArgumentException('Critical header cannot be empty');
31+
}
32+
$this->value = array_values($value);
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
*/
38+
public function getName(): string
39+
{
40+
return 'crit';
41+
}
42+
43+
/**
44+
* @inheritDoc
45+
*/
46+
public function getValue()
47+
{
48+
return json_encode($this->value);
49+
}
50+
51+
/**
52+
* @inheritDoc
53+
*/
54+
public function getClass(): ?int
55+
{
56+
return self::CLASS_REGISTERED;
57+
}
58+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\Jwt\Header;
10+
11+
use Magento\Framework\Jwt\Jwe\JweHeaderParameterInterface;
12+
use Magento\Framework\Jwt\Jws\JwsHeaderParameterInterface;
13+
use Magento\Framework\Jwt\Jwk as JwkData;
14+
15+
/**
16+
* "jwk" header.
17+
*/
18+
class Jwk implements JwsHeaderParameterInterface, JweHeaderParameterInterface
19+
{
20+
/**
21+
* @var JwkData
22+
*/
23+
private $value;
24+
25+
/**
26+
* @param JwkData $value
27+
*/
28+
public function __construct(JwkData $value)
29+
{
30+
$this->value = $value;
31+
}
32+
33+
/**
34+
* @inheritDoc
35+
*/
36+
public function getName(): string
37+
{
38+
return 'jwk';
39+
}
40+
41+
/**
42+
* @inheritDoc
43+
*/
44+
public function getValue()
45+
{
46+
return json_encode($this->value->getJsonData());
47+
}
48+
49+
/**
50+
* @inheritDoc
51+
*/
52+
public function getClass(): ?int
53+
{
54+
return self::CLASS_REGISTERED;
55+
}
56+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Framework\Jwt\Header;
10+
11+
use Magento\Framework\Jwt\Jwe\JweHeaderParameterInterface;
12+
use Magento\Framework\Jwt\Jws\JwsHeaderParameterInterface;
13+
14+
/**
15+
* "jku" header.
16+
*/
17+
class JwkSetUrl implements JwsHeaderParameterInterface, JweHeaderParameterInterface
18+
{
19+
/**
20+
* @var string
21+
*/
22+
private $value;
23+
24+
/**
25+
* @param string $value
26+
*/
27+
public function __construct(string $value)
28+
{
29+
$this->value = $value;
30+
}
31+
32+
/**
33+
* @inheritDoc
34+
*/
35+
public function getName(): string
36+
{
37+
return 'jku';
38+
}
39+
40+
/**
41+
* @inheritDoc
42+
*/
43+
public function getValue()
44+
{
45+
return $this->value;
46+
}
47+
48+
/**
49+
* @inheritDoc
50+
*/
51+
public function getClass(): ?int
52+
{
53+
return self::CLASS_REGISTERED;
54+
}
55+
}

0 commit comments

Comments
 (0)