Skip to content

Commit d198c2e

Browse files
author
ogorkun
committed
MC-38539: Introduce JWT wrapper
1 parent cfb09d2 commit d198c2e

File tree

3 files changed

+221
-38
lines changed

3 files changed

+221
-38
lines changed

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

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -171,55 +171,25 @@ public function getTokenVariants(): array
171171
]
172172
);
173173

174-
//RSA keys
175-
$rsaPrivateResource = openssl_pkey_new(['private_key_bites' => 512, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
176-
if ($rsaPrivateResource === false) {
177-
throw new \RuntimeException('Failed to create RSA keypair');
178-
}
179-
$rsaPublic = openssl_pkey_get_details($rsaPrivateResource)['key'];
180-
if (!openssl_pkey_export($rsaPrivateResource, $rsaPrivate, 'pass')) {
181-
throw new \RuntimeException('Failed to read RSA private key');
182-
}
183-
openssl_free_key($rsaPrivateResource);
184-
185-
//EC Keys
186-
$curveNameMap = [
187-
256 => 'prime256v1',
188-
384 => 'secp384r1',
189-
512 => 'secp521r1'
190-
];
191-
$ecKeys = [];
192-
foreach ($curveNameMap as $bits => $curve) {
193-
$privateResource = openssl_pkey_new(['curve_name' => $curve, 'private_key_type' => OPENSSL_KEYTYPE_EC]);
194-
if ($privateResource === false) {
195-
throw new \RuntimeException('Failed to create EC keypair');
196-
}
197-
$esPublic = openssl_pkey_get_details($privateResource)['key'];
198-
if (!openssl_pkey_export($privateResource, $esPrivate, 'pass')) {
199-
throw new \RuntimeException('Failed to read EC private key');
200-
}
201-
openssl_free_key($privateResource);
202-
$ecKeys[$bits] = [$esPrivate, $esPublic];
203-
unset($privateResource, $esPublic, $esPrivate);
204-
}
205-
206-
//Shared secret for SHA algorithms
174+
//Keys
175+
[$rsaPrivate, $rsaPublic] = $this->createRsaKeys();
176+
$ecKeys = $this->createEcKeys();
207177
$sharedSecret = random_bytes(128);
208178

209179
return [
210180
'jws-HS256' => [
211181
$flatJws,
212-
$enc = new JwsSignatureJwks($jwkFactory->createHs256(random_bytes(128))),
182+
$enc = new JwsSignatureJwks($jwkFactory->createHs256($sharedSecret)),
213183
[$enc]
214184
],
215185
'jws-HS384' => [
216186
$flatJws,
217-
$enc = new JwsSignatureJwks($jwkFactory->createHs384(random_bytes(128))),
187+
$enc = new JwsSignatureJwks($jwkFactory->createHs384($sharedSecret)),
218188
[$enc]
219189
],
220190
'jws-HS512' => [
221191
$jwsWithUnprotectedHeader,
222-
$enc = new JwsSignatureJwks($jwkFactory->createHs512(random_bytes(128))),
192+
$enc = new JwsSignatureJwks($jwkFactory->createHs512($sharedSecret)),
223193
[$enc]
224194
],
225195
'jws-RS256' => [
@@ -282,6 +252,21 @@ public function getTokenVariants(): array
282252
new JwsSignatureJwks($jwkFactory->createSignEs512($ecKeys[512][0], 'pass')),
283253
[new JwsSignatureJwks($jwkFactory->createVerifyEs512($ecKeys[512][1]))]
284254
],
255+
'jws-PS256' => [
256+
$flatJws,
257+
new JwsSignatureJwks($jwkFactory->createSignPs256($rsaPrivate, 'pass')),
258+
[new JwsSignatureJwks($jwkFactory->createVerifyPs256($rsaPublic))]
259+
],
260+
'jws-PS384' => [
261+
$flatJws,
262+
new JwsSignatureJwks($jwkFactory->createSignPs384($rsaPrivate, 'pass')),
263+
[new JwsSignatureJwks($jwkFactory->createVerifyPs384($rsaPublic))]
264+
],
265+
'jws-PS512' => [
266+
$flatJws,
267+
new JwsSignatureJwks($jwkFactory->createSignPs512($rsaPrivate, 'pass')),
268+
[new JwsSignatureJwks($jwkFactory->createVerifyPs512($rsaPublic))]
269+
],
285270
];
286271
}
287272

@@ -314,4 +299,54 @@ private function verifyAgainstHeaders(array $expected, HeaderInterface $actual):
314299
}
315300
$this->assertTrue($oneIsValid);
316301
}
302+
303+
/**
304+
* Create RSA key-pair.
305+
*
306+
* @return string[] With 1st element as private key, second - public.
307+
*/
308+
private function createRsaKeys(): array
309+
{
310+
$rsaPrivateResource = openssl_pkey_new(['private_key_bites' => 512, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
311+
if ($rsaPrivateResource === false) {
312+
throw new \RuntimeException('Failed to create RSA keypair');
313+
}
314+
$rsaPublic = openssl_pkey_get_details($rsaPrivateResource)['key'];
315+
if (!openssl_pkey_export($rsaPrivateResource, $rsaPrivate, 'pass')) {
316+
throw new \RuntimeException('Failed to read RSA private key');
317+
}
318+
openssl_free_key($rsaPrivateResource);
319+
320+
return [$rsaPrivate, $rsaPublic];
321+
}
322+
323+
/**
324+
* Create EC key pairs for with different curves.
325+
*
326+
* @return array Keys - bits, values contain 2 elements: 0 => private, 1 => public.
327+
*/
328+
private function createEcKeys(): array
329+
{
330+
$curveNameMap = [
331+
256 => 'prime256v1',
332+
384 => 'secp384r1',
333+
512 => 'secp521r1'
334+
];
335+
$ecKeys = [];
336+
foreach ($curveNameMap as $bits => $curve) {
337+
$privateResource = openssl_pkey_new(['curve_name' => $curve, 'private_key_type' => OPENSSL_KEYTYPE_EC]);
338+
if ($privateResource === false) {
339+
throw new \RuntimeException('Failed to create EC keypair');
340+
}
341+
$esPublic = openssl_pkey_get_details($privateResource)['key'];
342+
if (!openssl_pkey_export($privateResource, $esPrivate, 'pass')) {
343+
throw new \RuntimeException('Failed to read EC private key');
344+
}
345+
openssl_free_key($privateResource);
346+
$ecKeys[$bits] = [$esPrivate, $esPublic];
347+
unset($privateResource, $esPublic, $esPrivate);
348+
}
349+
350+
return $ecKeys;
351+
}
317352
}

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class Jwk
7777
*/
7878
private $use;
7979

80+
/**
81+
* @var string|null
82+
*/
83+
private $kid;
84+
8085
/**
8186
* @var string[]|null
8287
*/
@@ -123,6 +128,7 @@ class Jwk
123128
* @param string[]|null $x5c
124129
* @param string|null $x5t
125130
* @param string|null $x5ts256
131+
* @param string|null $kid
126132
*/
127133
public function __construct(
128134
string $kty,
@@ -133,7 +139,8 @@ public function __construct(
133139
?string $x5u = null,
134140
?array $x5c = null,
135141
?string $x5t = null,
136-
?string $x5ts256 = null
142+
?string $x5ts256 = null,
143+
?string $kid = null
137144
) {
138145
$this->kty = $kty;
139146
$this->data = $data;
@@ -144,6 +151,7 @@ public function __construct(
144151
$this->x5c = $x5c;
145152
$this->x5t = $x5t;
146153
$this->x5ts256 = $x5ts256;
154+
$this->kid = $kid;
147155
}
148156

149157
/**
@@ -226,6 +234,16 @@ public function getX509Sha256Thumbprint(): ?string
226234
return $this->x5ts256;
227235
}
228236

237+
/**
238+
* "kid" parameter.
239+
*
240+
* @return string|null
241+
*/
242+
public function getKeyId(): ?string
243+
{
244+
return $this->kid;
245+
}
246+
229247
/**
230248
* Map with algorithm (type) specific data.
231249
*
@@ -251,7 +269,8 @@ public function getJsonData(): array
251269
'x5u' => $this->getX509Url(),
252270
'x5c' => $this->getX509CertificateChain(),
253271
'x5t' => $this->getX509Sha1Thumbprint(),
254-
'x5t#S256' => $this->getX509Sha256Thumbprint()
272+
'x5t#S256' => $this->getX509Sha256Thumbprint(),
273+
'kid' => $this->getKeyId()
255274
];
256275
$data = array_merge($data, $this->getAlgoData());
257276

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

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,50 @@ class JwkFactory
1919
'1.3.132.0.35' => ['name' => 'P-521', 'bits' => 512]
2020
];
2121

22+
/**
23+
* Create JWK object from key data.
24+
*
25+
* @param array $data
26+
* @return Jwk
27+
*/
28+
public function createFromData(array $data): Jwk
29+
{
30+
if (!array_key_exists('kty', $data)) {
31+
throw new \InvalidArgumentException('Missing key type in JWK data (kty)');
32+
}
33+
$kty = $data['kty'];
34+
unset($data['kty']);
35+
$use = array_key_exists('use', $data) ? $data['use'] : null;
36+
unset($data['use']);
37+
$keyOps = array_key_exists('key_ops', $data) ? $data['key_ops'] : null;
38+
unset($data['key_ops']);
39+
$alg = array_key_exists('alg', $data) ? $data['alg'] : null;
40+
unset($data['alg']);
41+
$x5u = array_key_exists('x5u', $data) ? $data['x5u'] : null;
42+
unset($data['use']);
43+
$x5c = array_key_exists('x5c', $data) ? $data['x5c'] : null;
44+
unset($data['x5c']);
45+
$x5t = array_key_exists('x5t', $data) ? $data['x5t'] : null;
46+
unset($data['x5t']);
47+
$x5tS256 = array_key_exists('x5t#S256', $data) ? $data['x5t#S256'] : null;
48+
unset($data['x5t#S256']);
49+
$kid = array_key_exists('kid', $data) ? $data['kid'] : null;
50+
unset($data['kid']);
51+
52+
return new Jwk(
53+
$kty,
54+
$data,
55+
$use,
56+
$keyOps,
57+
$alg,
58+
$x5u,
59+
$x5c,
60+
$x5t,
61+
$x5tS256,
62+
$kid
63+
);
64+
}
65+
2266
/**
2367
* Create JWK for signatures generated with HMAC and SHA256
2468
*
@@ -190,6 +234,75 @@ public function createVerifyEs512(string $publicKey): Jwk
190234
return $this->createVerifyEs(512, $publicKey);
191235
}
192236

237+
/**
238+
* Create JWK to sign JWS with RSASSA-PSS using SHA-256 and MGF1 with SHA-256.
239+
*
240+
* @param string $privateKey
241+
* @param string|null $passPhrase
242+
* @return Jwk
243+
*/
244+
public function createSignPs256(string $privateKey, ?string $passPhrase): Jwk
245+
{
246+
return $this->createSignPs(256, $privateKey, $passPhrase);
247+
}
248+
249+
/**
250+
* Create JWK to verify JWS signed with RSASSA-PSS using SHA-256 and MGF1 with SHA-256.
251+
*
252+
* @param string $publicKey
253+
* @return Jwk
254+
*/
255+
public function createVerifyPs256(string $publicKey): Jwk
256+
{
257+
return $this->createVerifyPs(256, $publicKey);
258+
}
259+
260+
/**
261+
* Create JWK to sign JWS with RSASSA-PSS using SHA-384 and MGF1 with SHA-384.
262+
*
263+
* @param string $privateKey
264+
* @param string|null $passPhrase
265+
* @return Jwk
266+
*/
267+
public function createSignPs384(string $privateKey, ?string $passPhrase): Jwk
268+
{
269+
return $this->createSignPs(384, $privateKey, $passPhrase);
270+
}
271+
272+
/**
273+
* Create JWK to verify JWS signed with RSASSA-PSS using SHA-384 and MGF1 with SHA-384.
274+
*
275+
* @param string $publicKey
276+
* @return Jwk
277+
*/
278+
public function createVerifyPs384(string $publicKey): Jwk
279+
{
280+
return $this->createVerifyPs(384, $publicKey);
281+
}
282+
283+
/**
284+
* Create JWK to sign JWS with RSASSA-PSS using SHA-512 and MGF1 with SHA-512.
285+
*
286+
* @param string $privateKey
287+
* @param string|null $passPhrase
288+
* @return Jwk
289+
*/
290+
public function createSignPs512(string $privateKey, ?string $passPhrase): Jwk
291+
{
292+
return $this->createSignPs(512, $privateKey, $passPhrase);
293+
}
294+
295+
/**
296+
* Create JWK to verify JWS signed with RSASSA-PSS using SHA-512 and MGF1 with SHA-512.
297+
*
298+
* @param string $publicKey
299+
* @return Jwk
300+
*/
301+
public function createVerifyPs512(string $publicKey): Jwk
302+
{
303+
return $this->createVerifyPs(512, $publicKey);
304+
}
305+
193306
private function createHmac(int $bits, string $key): Jwk
194307
{
195308
if (strlen($key) < 128) {
@@ -261,6 +374,22 @@ private function createVerifyRsa(int $bits, string $key): Jwk
261374
);
262375
}
263376

377+
private function createSignPs(int $bits, string $key, ?string $pass): Jwk
378+
{
379+
$data = $this->createSignRsa($bits, $key, $pass)->getJsonData();
380+
$data['alg'] = 'PS' .$bits;
381+
382+
return $this->createFromData($data);
383+
}
384+
385+
private function createVerifyPs(int $bits, string $key): Jwk
386+
{
387+
$data = $this->createVerifyRsa($bits, $key)->getJsonData();
388+
$data['alg'] = 'PS' .$bits;
389+
390+
return $this->createFromData($data);
391+
}
392+
264393
private function createSignEs(int $bits, string $key, ?string $pass): Jwk
265394
{
266395
$resource = openssl_get_privatekey($key, (string)$pass);

0 commit comments

Comments
 (0)