Skip to content

Commit cfb09d2

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

File tree

2 files changed

+166
-1
lines changed

2 files changed

+166
-1
lines changed

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ public function getTokenVariants(): array
171171
]
172172
);
173173

174+
//RSA keys
174175
$rsaPrivateResource = openssl_pkey_new(['private_key_bites' => 512, 'private_key_type' => OPENSSL_KEYTYPE_RSA]);
175176
if ($rsaPrivateResource === false) {
176177
throw new \RuntimeException('Failed to create RSA keypair');
@@ -180,6 +181,29 @@ public function getTokenVariants(): array
180181
throw new \RuntimeException('Failed to read RSA private key');
181182
}
182183
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
183207
$sharedSecret = random_bytes(128);
184208

185209
return [
@@ -242,7 +266,22 @@ public function getTokenVariants(): array
242266
)
243267
),
244268
[new JwsSignatureJwks($jwkFactory->createVerifyRs256($rsaPublic))]
245-
]
269+
],
270+
'jws-ES256' => [
271+
$flatJws,
272+
new JwsSignatureJwks($jwkFactory->createSignEs256($ecKeys[256][0], 'pass')),
273+
[new JwsSignatureJwks($jwkFactory->createVerifyEs256($ecKeys[256][1]))]
274+
],
275+
'jws-ES384' => [
276+
$flatJws,
277+
new JwsSignatureJwks($jwkFactory->createSignEs384($ecKeys[384][0], 'pass')),
278+
[new JwsSignatureJwks($jwkFactory->createVerifyEs384($ecKeys[384][1]))]
279+
],
280+
'jws-ES512' => [
281+
$flatJws,
282+
new JwsSignatureJwks($jwkFactory->createSignEs512($ecKeys[512][0], 'pass')),
283+
[new JwsSignatureJwks($jwkFactory->createVerifyEs512($ecKeys[512][1]))]
284+
],
246285
];
247286
}
248287

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

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@
1313
*/
1414
class JwkFactory
1515
{
16+
private const EC_CURVE_MAP = [
17+
'1.2.840.10045.3.1.7' => ['name' => 'P-256', 'bits' => 256],
18+
'1.3.132.0.34' => ['name' => 'P-384', 'bits' => 384],
19+
'1.3.132.0.35' => ['name' => 'P-521', 'bits' => 512]
20+
];
21+
1622
/**
1723
* Create JWK for signatures generated with HMAC and SHA256
1824
*
@@ -115,6 +121,75 @@ public function createVerifyRs512(string $publicKey): Jwk
115121
return $this->createVerifyRsa(512, $publicKey);
116122
}
117123

124+
/**
125+
* Create JWK to sign JWS with ECDSA using P-256 and SHA-256.
126+
*
127+
* @param string $privateKey
128+
* @param string|null $passPhrase
129+
* @return Jwk
130+
*/
131+
public function createSignEs256(string $privateKey, ?string $passPhrase): Jwk
132+
{
133+
return $this->createSignEs(256, $privateKey, $passPhrase);
134+
}
135+
136+
/**
137+
* Create JWK to verify JWS signed with ECDSA using P-256 and SHA-256.
138+
*
139+
* @param string $publicKey
140+
* @return Jwk
141+
*/
142+
public function createVerifyEs256(string $publicKey): Jwk
143+
{
144+
return $this->createVerifyEs(256, $publicKey);
145+
}
146+
147+
/**
148+
* Create JWK to sign JWS with ECDSA using P-384 and SHA-384 .
149+
*
150+
* @param string $privateKey
151+
* @param string|null $passPhrase
152+
* @return Jwk
153+
*/
154+
public function createSignEs384(string $privateKey, ?string $passPhrase): Jwk
155+
{
156+
return $this->createSignEs(384, $privateKey, $passPhrase);
157+
}
158+
159+
/**
160+
* Create JWK to verify JWS signed with ECDSA using P-384 and SHA-384 .
161+
*
162+
* @param string $publicKey
163+
* @return Jwk
164+
*/
165+
public function createVerifyEs384(string $publicKey): Jwk
166+
{
167+
return $this->createVerifyEs(384, $publicKey);
168+
}
169+
170+
/**
171+
* Create JWK to sign JWS with ECDSA using P-521 and SHA-512.
172+
*
173+
* @param string $privateKey
174+
* @param string|null $passPhrase
175+
* @return Jwk
176+
*/
177+
public function createSignEs512(string $privateKey, ?string $passPhrase): Jwk
178+
{
179+
return $this->createSignEs(512, $privateKey, $passPhrase);
180+
}
181+
182+
/**
183+
* Create JWK to verify JWS signed with ECDSA using P-521 and SHA-512.
184+
*
185+
* @param string $publicKey
186+
* @return Jwk
187+
*/
188+
public function createVerifyEs512(string $publicKey): Jwk
189+
{
190+
return $this->createVerifyEs(512, $publicKey);
191+
}
192+
118193
private function createHmac(int $bits, string $key): Jwk
119194
{
120195
if (strlen($key) < 128) {
@@ -186,6 +261,57 @@ private function createVerifyRsa(int $bits, string $key): Jwk
186261
);
187262
}
188263

264+
private function createSignEs(int $bits, string $key, ?string $pass): Jwk
265+
{
266+
$resource = openssl_get_privatekey($key, (string)$pass);
267+
$keyData = openssl_pkey_get_details($resource)['ec'];
268+
openssl_free_key($resource);
269+
if (!array_key_exists($keyData['curve_oid'], self::EC_CURVE_MAP)) {
270+
throw new \RuntimeException('Unsupported EC curve');
271+
}
272+
if ($bits !== self::EC_CURVE_MAP[$keyData['curve_oid']]['bits']) {
273+
throw new \RuntimeException('The key cannot be used with SHA-' .$bits .' hashing algorithm');
274+
}
275+
276+
return new Jwk(
277+
Jwk::KEY_TYPE_EC,
278+
[
279+
'd' => self::base64Encode($keyData['d']),
280+
'x' => self::base64Encode($keyData['x']),
281+
'y' => self::base64Encode($keyData['y']),
282+
'crv' => self::EC_CURVE_MAP[$keyData['curve_oid']]['name']
283+
],
284+
Jwk::PUBLIC_KEY_USE_SIGNATURE,
285+
null,
286+
'ES' .$bits
287+
);
288+
}
289+
290+
private function createVerifyEs(int $bits, string $key): Jwk
291+
{
292+
$resource = openssl_get_publickey($key);
293+
$keyData = openssl_pkey_get_details($resource)['ec'];
294+
openssl_free_key($resource);
295+
if (!array_key_exists($keyData['curve_oid'], self::EC_CURVE_MAP)) {
296+
throw new \RuntimeException('Unsupported EC curve');
297+
}
298+
if ($bits !== self::EC_CURVE_MAP[$keyData['curve_oid']]['bits']) {
299+
throw new \RuntimeException('The key cannot be used with SHA-' .$bits .' hashing algorithm');
300+
}
301+
302+
return new Jwk(
303+
Jwk::KEY_TYPE_EC,
304+
[
305+
'x' => self::base64Encode($keyData['x']),
306+
'y' => self::base64Encode($keyData['y']),
307+
'crv' => self::EC_CURVE_MAP[$keyData['curve_oid']]['name']
308+
],
309+
Jwk::PUBLIC_KEY_USE_SIGNATURE,
310+
null,
311+
'ES' .$bits
312+
);
313+
}
314+
189315
/**
190316
* Encode value into Base64Url format.
191317
*

0 commit comments

Comments
 (0)