Skip to content

Add new targets, update dependencies, and support PHP 8.4 #599

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions .github/workflows/integrate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "8.3"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
tools: castor

- name: "Checkout code"
Expand Down Expand Up @@ -70,6 +70,7 @@ jobs:
php-version:
- "8.2"
- "8.3"
- "8.4"
dependencies:
- "lowest"
- "highest"
Expand All @@ -79,7 +80,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "${{ matrix.php-version }}"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
tools: castor
coverage: "xdebug"

Expand All @@ -106,7 +107,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "8.3"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
tools: castor

- name: "Checkout code"
Expand All @@ -132,7 +133,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "8.3"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
tools: castor

- name: "Checkout code"
Expand Down Expand Up @@ -161,7 +162,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "8.3"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
tools: castor

- name: "Checkout code"
Expand All @@ -187,7 +188,7 @@ jobs:
uses: "shivammathur/setup-php@v2"
with:
php-version: "8.3"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid"
extensions: "gmp, json, mbstring, openssl, sqlite3, curl, uuid, sodium"
tools: castor
coverage: "xdebug"

Expand Down
1 change: 1 addition & 0 deletions .gitsplit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ splits:
- prefix: "src/Library"
target: "https://${GH_TOKEN}@github.com/web-token/jwt-library.git"
- prefix: "src/Experimental"
target: "https://${GH_TOKEN}@github.com/web-token/jwt-experimental.git"

origins:
- ^\d+\.\d+\.x$
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"phpstan/phpstan-symfony": "^1.3|^2.0",
"phpunit/phpunit": "^10.5.10|^11.0",
"qossmic/deptrac": "^2.0",
"rector/rector": "^1.0|^2.0.0-rc3",
"rector/rector": "^1.0|^2.0",
"roave/security-advisories": "dev-latest",
"spomky-labs/aes-key-wrap": "^7.0",
"staabm/phpstan-dba": "^0.2.79|^0.3",
Expand Down
36 changes: 36 additions & 0 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5502,6 +5502,42 @@ parameters:
count: 1
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php

-
message: '#^Parameter \#1 \$details of static method Jose\\Component\\KeyManagement\\KeyConverter\\KeyConverter\:\:tryToLoadECKey\(\) expects array\{type\: int, key\: string\}, non\-empty\-array given\.$#'
identifier: argument.type
count: 1
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php

-
message: '#^Parameter \#1 \$details of static method Jose\\Component\\KeyManagement\\KeyConverter\\KeyConverter\:\:tryToLoadOtherKeyTypes\(\) expects array\{key\: string\}, non\-empty\-array given\.$#'
identifier: argument.type
count: 1
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php

-
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\.$#'
identifier: argument.type
count: 1
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php

-
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\.$#'
identifier: argument.type
count: 1
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php

-
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\.$#'
identifier: argument.type
count: 1
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php

-
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\.$#'
identifier: argument.type
count: 1
path: src/Library/KeyManagement/KeyConverter/KeyConverter.php

-
message: '#^Parameter \#1 \$pem of static method Jose\\Component\\KeyManagement\\KeyConverter\\KeyConverter\:\:loadKeyFromPEM\(\) expects string, mixed given\.$#'
identifier: argument.type
Expand Down
157 changes: 117 additions & 40 deletions src/Library/KeyManagement/KeyConverter/KeyConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
use OpenSSLCertificate;
use ParagonIE\Sodium\Core\Ed25519;
use RuntimeException;
use SpomkyLabs\Pki\ASN1\Type\Constructed\Sequence;
use SpomkyLabs\Pki\ASN1\Type\UnspecifiedType;
use SpomkyLabs\Pki\CryptoEncoding\PEM;
use SpomkyLabs\Pki\CryptoTypes\AlgorithmIdentifier\AlgorithmIdentifier;
use SpomkyLabs\Pki\CryptoTypes\Asymmetric\PrivateKey;
Expand Down Expand Up @@ -228,52 +230,155 @@ private static function loadKeyFromPEM(string $pem, ?string $password = null): a
}

return match ($details['type']) {
OPENSSL_KEYTYPE_EC => self::tryToLoadECKey($pem),
OPENSSL_KEYTYPE_EC => self::tryToLoadECKey($details, $pem),
OPENSSL_KEYTYPE_RSA => RSAKey::createFromPEM($pem)->toArray(),
-1 => self::tryToLoadOtherKeyTypes($pem),
4 => self::tryToLoadX25519Key($details), // OPENSSL_KEYTYPE_X25519
5 => self::tryToLoadED25519Key($details), // OPENSSL_KEYTYPE_ED25519
6 => self::tryToLoadX448Key($details), // OPENSSL_KEYTYPE_X448
7 => self::tryToLoadED448Key($details), // OPENSSL_KEYTYPE_ED448
-1 => self::tryToLoadOtherKeyTypes($details, $pem),
default => throw new InvalidArgumentException('Unsupported key type'),
};
}

/**
* This method tries to load Ed448, X488, Ed25519 and X25519 keys.
*
* @param array{type: int, key: string} $details
*
* @return array<array-key, mixed>
*/
private static function tryToLoadECKey(string $input): array
private static function tryToLoadECKey(array $details, string $input): array
{
try {
return ECKey::createFromPEM($input)->toArray();
} catch (Throwable) {
// no break
}
try {
return self::tryToLoadOtherKeyTypes($input);
return self::tryToLoadOtherKeyTypes($details, $input);
} catch (Throwable) {
// no break
}
throw new InvalidArgumentException('Unable to load the key.');
}

/**
* @param array{bits: int, type: int, key: string, x25519: array{pub_key?: string, priv_key?: string}} $input
*
* @return array<array-key, mixed>
*/
private static function tryToLoadX25519Key(array $input): array
{
$values = [
'kty' => 'OKP',
'crv' => 'X25519',
];
if (array_key_exists('pub_key', $input['x25519'])) {
$values['x'] = Base64UrlSafe::encodeUnpadded($input['x25519']['pub_key']);
} else {
$values['x'] = self::tryToLoadOtherKeyTypes($input, $input['key'])['x'];
}
if (array_key_exists('priv_key', $input['x25519'])) {
$values['d'] = Base64UrlSafe::encodeUnpadded($input['x25519']['priv_key']);
}

return $values;
}

/**
* @param array{bits: int, type: int, key: string, ed25519: array{pub_key?: string, priv_key?: string}} $input
*
* @return array<array-key, mixed>
*/
private static function tryToLoadED25519Key(array $input): array
{
$values = [
'kty' => 'OKP',
'crv' => 'Ed25519',
];
if (array_key_exists('pub_key', $input['ed25519'])) {
$values['x'] = Base64UrlSafe::encodeUnpadded($input['ed25519']['pub_key']);
} else {
$values['x'] = self::tryToLoadOtherKeyTypes($input, $input['key'])['x'];
}
if (array_key_exists('priv_key', $input['ed25519'])) {
$values['d'] = Base64UrlSafe::encodeUnpadded($input['ed25519']['priv_key']);
}

return $values;
}

/**
* @param array{bits: int, type: int, key: string, x448: array{pub_key?: string, priv_key?: string}} $input
*
* @return array<array-key, mixed>
*/
private static function tryToLoadX448Key(array $input): array
{
$values = [
'kty' => 'OKP',
'crv' => 'X448',
];
if (array_key_exists('pub_key', $input['x448'])) {
$values['x'] = Base64UrlSafe::encodeUnpadded($input['x448']['pub_key']);
} else {
$values['x'] = self::tryToLoadOtherKeyTypes($input, $input['key'])['x'];
}
if (array_key_exists('priv_key', $input['x448'])) {
$values['d'] = Base64UrlSafe::encodeUnpadded($input['x448']['priv_key']);
}

return $values;
}

/**
* @param array{bits: int, type: int, key: string, ed448: array{pub_key?: string, priv_key?: string}} $input
*
* @return array<array-key, mixed>
*/
private static function tryToLoadED448Key(array $input): array
{
$values = [
'kty' => 'OKP',
'crv' => 'Ed448',
];
if (array_key_exists('pub_key', $input['ed448'])) {
$values['x'] = Base64UrlSafe::encodeUnpadded($input['ed448']['pub_key']);
} else {
$values['x'] = self::tryToLoadOtherKeyTypes($input, $input['key'])['x'];
}
if (array_key_exists('priv_key', $input['ed448'])) {
$values['d'] = Base64UrlSafe::encodeUnpadded($input['ed448']['priv_key']);
}

return $values;
}

/**
* This method tries to load Ed448, X488, Ed25519 and X25519 keys.
* Only needed on PHP8.3 and earlier.
*
* @param array{key: string} $details
*
* @return array<array-key, mixed>
*/
private static function tryToLoadOtherKeyTypes(string $input): array
private static function tryToLoadOtherKeyTypes(array $details, string $input): array
{
$pem = PEM::fromString($input);
return match ($pem->type()) {
PEM::TYPE_PUBLIC_KEY => self::loadPublicKey($pem),
PEM::TYPE_PRIVATE_KEY => self::loadPrivateKey($pem),
PEM::TYPE_PRIVATE_KEY => self::loadPrivateKey($details, $pem),
default => throw new InvalidArgumentException('Unsupported key type'),
};
}

/**
* @param array{key: string} $details
*
* @return array<string, mixed>
*/
private static function loadPrivateKey(PEM $pem): array
private static function loadPrivateKey(array $details, PEM $pem): array
{
try {
$key = PrivateKey::fromPEM($pem);
Expand All @@ -296,12 +401,15 @@ private static function loadPrivateKey(PEM $pem): array
case AlgorithmIdentifier::OID_X25519:
case AlgorithmIdentifier::OID_X448:
$curve = self::getCurve($key->algorithmIdentifier()->oid());
$values = [
$publicKey = PEM::fromString($details['key']);
/** @var UnspecifiedType $publicKeyBits */
$publicKeyBits = Sequence::fromDER($publicKey->data())->at(1);
return [
'kty' => 'OKP',
'crv' => $curve,
'x' => Base64UrlSafe::encodeUnpadded($publicKeyBits->asBitString()->string()),
'd' => Base64UrlSafe::encodeUnpadded($key->privateKeyData()),
];
return self::populatePoints($key, $values);
default:
throw new InvalidArgumentException('Unsupported key type');
}
Expand Down Expand Up @@ -338,37 +446,6 @@ private static function convertDecimalToBas64Url(string $decimal): string
return Base64UrlSafe::encodeUnpadded(BigInteger::fromBase($decimal, 10)->toBytes());
}

/**
* @param array<string, mixed> $values
* @return array<string, mixed>
*/
private static function populatePoints(PrivateKey $key, array $values): array
{
$crv = $values['crv'] ?? null;
assert(is_string($crv), 'Unsupported key type.');
$x = self::getPublicKey($key, $crv);
if ($x !== null) {
$values['x'] = Base64UrlSafe::encodeUnpadded($x);
}

return $values;
}

private static function getPublicKey(PrivateKey $key, string $crv): ?string
{
switch ($crv) {
case 'Ed25519':
return Ed25519::publickey_from_secretkey($key->privateKeyData());
case 'X25519':
if (extension_loaded('sodium')) {
return sodium_crypto_scalarmult_base($key->privateKeyData());
}
// no break
default:
return null;
}
}

private static function checkType(string $curve): void
{
$curves = ['Ed448ph', 'Ed25519ph', 'Ed448', 'Ed25519', 'X448', 'X25519'];
Expand Down
6 changes: 4 additions & 2 deletions tests/Component/KeyManagement/JWKFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ public static function dataKeys(): iterable
'expectedValues' => [
'kty' => 'OKP',
'crv' => 'Ed448',
'x' => 'wwHKDV7s4fBhmFSTzYorlaToGXNcsa7SakZdekT_sexD5ENj5lWP6_KX9_u--w_QSm80rNOodj0A',
'd' => '0GXSbNLOh7NQBlwoF8y2WJmjeP5Puif4_JL4ihFUzRLrb_3r4cH8l_HWJA-2ffY62LEB_ozsehG5',
],
];
Expand All @@ -290,6 +291,7 @@ public static function dataKeys(): iterable
'expectedValues' => [
'kty' => 'OKP',
'crv' => 'X448',
'x' => 'UoPD73NQACC8A-otDUVun4IrMsk775ShMRf4ThDrq4xY2eAI-pOIVujrvBXXd9g8gUNwBT0fmnc',
'd' => 'OHZK0Fp9MAAmk0yZekiAkB8qxpCVAF4dT2x_xmFNDdCTnyDvixaiZ0NSRpAdR59tA6OJmOFfbck',
],
];
Expand All @@ -298,8 +300,8 @@ public static function dataKeys(): iterable
'expectedValues' => [
'kty' => 'OKP',
'crv' => 'Ed25519',
'd' => 'Pr9AxZivB-zSq95wLrZfYa7DQ3TUPqZTkP_0w33r3rc',
'x' => 'wrI33AEj15KHHYplueUE5cnJKtbM8oVHFf6wGnw2oOE',
'd' => 'Pr9AxZivB-zSq95wLrZfYa7DQ3TUPqZTkP_0w33r3rc',
],
];
yield [
Expand All @@ -317,8 +319,8 @@ public static function dataKeys(): iterable
'expectedValues' => [
'kty' => 'OKP',
'crv' => 'X25519',
'd' => 'mG-fgDwkr58hwIeqCQKZbR8HKeY4yg_AzvU6zyNaVUE',
'x' => '3OJLiffmOCQGtil23QGyn0nk9EBKoZx6P-6o-EnsBB4',
'd' => 'mG-fgDwkr58hwIeqCQKZbR8HKeY4yg_AzvU6zyNaVUE',
],
];
}
Expand Down
Loading