Skip to content

Commit c9b85a1

Browse files
committed
Add support for Ed25519, Ed448, X25519, and X448 key types.
Implemented methods to handle new elliptic curve key types, enhancing the KeyConverter functionality. Updated tests to verify correct behavior and added necessary PHP extensions and version support in workflows. Minor composer and documentation adjustments were also made.
1 parent 3479ed4 commit c9b85a1

File tree

6 files changed

+140
-27
lines changed

6 files changed

+140
-27
lines changed

.github/workflows/integrate.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
uses: "shivammathur/setup-php@v2"
3131
with:
3232
php-version: "8.3"
33-
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
33+
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
3434
tools: castor
3535

3636
- name: "Checkout code"
@@ -70,6 +70,7 @@ jobs:
7070
php-version:
7171
- "8.2"
7272
- "8.3"
73+
- "8.4"
7374
dependencies:
7475
- "lowest"
7576
- "highest"
@@ -79,7 +80,7 @@ jobs:
7980
uses: "shivammathur/setup-php@v2"
8081
with:
8182
php-version: "${{ matrix.php-version }}"
82-
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
83+
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
8384
tools: castor
8485
coverage: "xdebug"
8586

@@ -106,7 +107,7 @@ jobs:
106107
uses: "shivammathur/setup-php@v2"
107108
with:
108109
php-version: "8.3"
109-
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
110+
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
110111
tools: castor
111112

112113
- name: "Checkout code"
@@ -132,7 +133,7 @@ jobs:
132133
uses: "shivammathur/setup-php@v2"
133134
with:
134135
php-version: "8.3"
135-
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
136+
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
136137
tools: castor
137138

138139
- name: "Checkout code"
@@ -161,7 +162,7 @@ jobs:
161162
uses: "shivammathur/setup-php@v2"
162163
with:
163164
php-version: "8.3"
164-
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
165+
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
165166
tools: castor
166167

167168
- name: "Checkout code"
@@ -187,7 +188,7 @@ jobs:
187188
uses: "shivammathur/setup-php@v2"
188189
with:
189190
php-version: "8.3"
190-
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
191+
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
191192
tools: castor
192193
coverage: "xdebug"
193194

.gitsplit.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ splits:
44
- prefix: "src/Library"
55
target: "https://${GH_TOKEN}@github.com/web-token/jwt-library.git"
66
- prefix: "src/Experimental"
7+
target: "https://${GH_TOKEN}@github.com/web-token/jwt-experimental.git"
78

89
origins:
910
- ^\d+\.\d+\.x$

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
"phpstan/phpstan-symfony": "^1.3|^2.0",
8181
"phpunit/phpunit": "^10.5.10|^11.0",
8282
"qossmic/deptrac": "^2.0",
83-
"rector/rector": "^1.0|^2.0.0-rc3",
83+
"rector/rector": "^1.0|^2.0",
8484
"roave/security-advisories": "dev-latest",
8585
"spomky-labs/aes-key-wrap": "^7.0",
8686
"staabm/phpstan-dba": "^0.2.79|^0.3",

phpstan-baseline.neon

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5502,6 +5502,30 @@ parameters:
55025502
count: 1
55035503
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php
55045504

5505+
-
5506+
message: '#^Parameter \#1 \$input of static method Jose\\Component\\KeyManagement\\KeyConverter\\KeyConverter\:\:tryToLoadED25519Key\(\) expects array\{bits\: int, type\: int, key\: string, ed25519\: array\{pub_key\?\: string, priv_key\?\: string\}\}, non\-empty\-array given\.$#'
5507+
identifier: argument.type
5508+
count: 1
5509+
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php
5510+
5511+
-
5512+
message: '#^Parameter \#1 \$input of static method Jose\\Component\\KeyManagement\\KeyConverter\\KeyConverter\:\:tryToLoadED448Key\(\) expects array\{bits\: int, type\: int, key\: string, ed448\: array\{pub_key\?\: string, priv_key\?\: string\}\}, non\-empty\-array given\.$#'
5513+
identifier: argument.type
5514+
count: 1
5515+
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php
5516+
5517+
-
5518+
message: '#^Parameter \#1 \$input of static method Jose\\Component\\KeyManagement\\KeyConverter\\KeyConverter\:\:tryToLoadX25519Key\(\) expects array\{bits\: int, type\: int, key\: string, x25519\: array\{pub_key\?\: string, priv_key\?\: string\}\}, non\-empty\-array given\.$#'
5519+
identifier: argument.type
5520+
count: 1
5521+
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php
5522+
5523+
-
5524+
message: '#^Parameter \#1 \$input of static method Jose\\Component\\KeyManagement\\KeyConverter\\KeyConverter\:\:tryToLoadX448Key\(\) expects array\{bits\: int, type\: int, key\: string, x448\: array\{pub_key\?\: string, priv_key\?\: string\}\}, non\-empty\-array given\.$#'
5525+
identifier: argument.type
5526+
count: 1
5527+
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php
5528+
55055529
-
55065530
message: '#^Parameter \#1 \$pem of static method Jose\\Component\\KeyManagement\\KeyConverter\\KeyConverter\:\:loadKeyFromPEM\(\) expects string, mixed given\.$#'
55075531
identifier: argument.type

src/Library/KeyManagement/KeyConverter/KeyConverter.php

Lines changed: 103 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ private static function loadKeyFromPEM(string $pem, ?string $password = null): a
230230
return match ($details['type']) {
231231
OPENSSL_KEYTYPE_EC => self::tryToLoadECKey($pem),
232232
OPENSSL_KEYTYPE_RSA => RSAKey::createFromPEM($pem)->toArray(),
233+
4 => self::tryToLoadX25519Key($details), // OPENSSL_KEYTYPE_X25519
234+
5 => self::tryToLoadED25519Key($details), // OPENSSL_KEYTYPE_ED25519
235+
6 => self::tryToLoadX448Key($details), // OPENSSL_KEYTYPE_X448
236+
7 => self::tryToLoadED448Key($details), // OPENSSL_KEYTYPE_ED448
233237
-1 => self::tryToLoadOtherKeyTypes($pem),
234238
default => throw new InvalidArgumentException('Unsupported key type'),
235239
};
@@ -255,8 +259,101 @@ private static function tryToLoadECKey(string $input): array
255259
throw new InvalidArgumentException('Unable to load the key.');
256260
}
257261

262+
/**
263+
* @param array{bits: int, type: int, key: string, x25519: array{pub_key?: string, priv_key?: string}} $input
264+
*
265+
* @return array<array-key, mixed>
266+
*/
267+
private static function tryToLoadX25519Key(array $input): array
268+
{
269+
$values = [
270+
'kty' => 'OKP',
271+
'crv' => 'X25519',
272+
];
273+
if (array_key_exists('pub_key', $input['x25519'])) {
274+
$values['x'] = Base64UrlSafe::encodeUnpadded($input['x25519']['pub_key']);
275+
} else {
276+
$values['x'] = self::tryToLoadOtherKeyTypes($input['key'])['x'];
277+
}
278+
if (array_key_exists('priv_key', $input['x25519'])) {
279+
$values['d'] = Base64UrlSafe::encodeUnpadded($input['x25519']['priv_key']);
280+
}
281+
282+
return $values;
283+
}
284+
285+
/**
286+
* @param array{bits: int, type: int, key: string, ed25519: array{pub_key?: string, priv_key?: string}} $input
287+
*
288+
* @return array<array-key, mixed>
289+
*/
290+
private static function tryToLoadED25519Key(array $input): array
291+
{
292+
$values = [
293+
'kty' => 'OKP',
294+
'crv' => 'Ed25519',
295+
];
296+
if (array_key_exists('pub_key', $input['ed25519'])) {
297+
$values['x'] = Base64UrlSafe::encodeUnpadded($input['ed25519']['pub_key']);
298+
} else {
299+
$values['x'] = self::tryToLoadOtherKeyTypes($input['key'])['x'];
300+
}
301+
if (array_key_exists('priv_key', $input['ed25519'])) {
302+
$values['d'] = Base64UrlSafe::encodeUnpadded($input['ed25519']['priv_key']);
303+
}
304+
305+
return $values;
306+
}
307+
308+
/**
309+
* @param array{bits: int, type: int, key: string, x448: array{pub_key?: string, priv_key?: string}} $input
310+
*
311+
* @return array<array-key, mixed>
312+
*/
313+
private static function tryToLoadX448Key(array $input): array
314+
{
315+
$values = [
316+
'kty' => 'OKP',
317+
'crv' => 'X448',
318+
];
319+
if (array_key_exists('pub_key', $input['x448'])) {
320+
$values['x'] = Base64UrlSafe::encodeUnpadded($input['x448']['pub_key']);
321+
} else {
322+
$values['x'] = self::tryToLoadOtherKeyTypes($input['key'])['x'];
323+
}
324+
if (array_key_exists('priv_key', $input['x448'])) {
325+
$values['d'] = Base64UrlSafe::encodeUnpadded($input['x448']['priv_key']);
326+
}
327+
328+
return $values;
329+
}
330+
331+
/**
332+
* @param array{bits: int, type: int, key: string, ed448: array{pub_key?: string, priv_key?: string}} $input
333+
*
334+
* @return array<array-key, mixed>
335+
*/
336+
private static function tryToLoadED448Key(array $input): array
337+
{
338+
$values = [
339+
'kty' => 'OKP',
340+
'crv' => 'Ed448',
341+
];
342+
if (array_key_exists('pub_key', $input['ed448'])) {
343+
$values['x'] = Base64UrlSafe::encodeUnpadded($input['ed448']['pub_key']);
344+
} else {
345+
$values['x'] = self::tryToLoadOtherKeyTypes($input['key'])['x'];
346+
}
347+
if (array_key_exists('priv_key', $input['ed448'])) {
348+
$values['d'] = Base64UrlSafe::encodeUnpadded($input['ed448']['priv_key']);
349+
}
350+
351+
return $values;
352+
}
353+
258354
/**
259355
* This method tries to load Ed448, X488, Ed25519 and X25519 keys.
356+
* Only needed on PHP8.3 and earlier.
260357
*
261358
* @return array<array-key, mixed>
262359
*/
@@ -296,12 +393,16 @@ private static function loadPrivateKey(PEM $pem): array
296393
case AlgorithmIdentifier::OID_X25519:
297394
case AlgorithmIdentifier::OID_X448:
298395
$curve = self::getCurve($key->algorithmIdentifier()->oid());
396+
$x = self::getPublicKey($key, $curve);
299397
$values = [
300398
'kty' => 'OKP',
301399
'crv' => $curve,
302-
'd' => Base64UrlSafe::encodeUnpadded($key->privateKeyData()),
303400
];
304-
return self::populatePoints($key, $values);
401+
if ($x !== null) {
402+
$values['x'] = Base64UrlSafe::encodeUnpadded($x);
403+
}
404+
$values['d'] = Base64UrlSafe::encodeUnpadded($key->privateKeyData());
405+
// no break
305406
default:
306407
throw new InvalidArgumentException('Unsupported key type');
307408
}
@@ -338,22 +439,6 @@ private static function convertDecimalToBas64Url(string $decimal): string
338439
return Base64UrlSafe::encodeUnpadded(BigInteger::fromBase($decimal, 10)->toBytes());
339440
}
340441

341-
/**
342-
* @param array<string, mixed> $values
343-
* @return array<string, mixed>
344-
*/
345-
private static function populatePoints(PrivateKey $key, array $values): array
346-
{
347-
$crv = $values['crv'] ?? null;
348-
assert(is_string($crv), 'Unsupported key type.');
349-
$x = self::getPublicKey($key, $crv);
350-
if ($x !== null) {
351-
$values['x'] = Base64UrlSafe::encodeUnpadded($x);
352-
}
353-
354-
return $values;
355-
}
356-
357442
private static function getPublicKey(PrivateKey $key, string $crv): ?string
358443
{
359444
switch ($crv) {

tests/Component/KeyManagement/JWKFactoryTest.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ public static function dataKeys(): iterable
282282
'expectedValues' => [
283283
'kty' => 'OKP',
284284
'crv' => 'Ed448',
285+
'x' => 'wwHKDV7s4fBhmFSTzYorlaToGXNcsa7SakZdekT_sexD5ENj5lWP6_KX9_u--w_QSm80rNOodj0A',
285286
'd' => '0GXSbNLOh7NQBlwoF8y2WJmjeP5Puif4_JL4ihFUzRLrb_3r4cH8l_HWJA-2ffY62LEB_ozsehG5',
286287
],
287288
];
@@ -290,6 +291,7 @@ public static function dataKeys(): iterable
290291
'expectedValues' => [
291292
'kty' => 'OKP',
292293
'crv' => 'X448',
294+
'x' => 'UoPD73NQACC8A-otDUVun4IrMsk775ShMRf4ThDrq4xY2eAI-pOIVujrvBXXd9g8gUNwBT0fmnc',
293295
'd' => 'OHZK0Fp9MAAmk0yZekiAkB8qxpCVAF4dT2x_xmFNDdCTnyDvixaiZ0NSRpAdR59tA6OJmOFfbck',
294296
],
295297
];
@@ -298,8 +300,8 @@ public static function dataKeys(): iterable
298300
'expectedValues' => [
299301
'kty' => 'OKP',
300302
'crv' => 'Ed25519',
301-
'd' => 'Pr9AxZivB-zSq95wLrZfYa7DQ3TUPqZTkP_0w33r3rc',
302303
'x' => 'wrI33AEj15KHHYplueUE5cnJKtbM8oVHFf6wGnw2oOE',
304+
'd' => 'Pr9AxZivB-zSq95wLrZfYa7DQ3TUPqZTkP_0w33r3rc',
303305
],
304306
];
305307
yield [
@@ -317,8 +319,8 @@ public static function dataKeys(): iterable
317319
'expectedValues' => [
318320
'kty' => 'OKP',
319321
'crv' => 'X25519',
320-
'd' => 'mG-fgDwkr58hwIeqCQKZbR8HKeY4yg_AzvU6zyNaVUE',
321322
'x' => '3OJLiffmOCQGtil23QGyn0nk9EBKoZx6P-6o-EnsBB4',
323+
'd' => 'mG-fgDwkr58hwIeqCQKZbR8HKeY4yg_AzvU6zyNaVUE',
322324
],
323325
];
324326
}

0 commit comments

Comments
 (0)