Skip to content

Commit 7044f9a

Browse files
authored
fix: add key length validation for ec keys (#615)
1 parent 81ed59e commit 7044f9a

File tree

4 files changed

+96
-4
lines changed

4 files changed

+96
-4
lines changed

src/JWT.php

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,8 @@ public static function sign(
270270
}
271271
if (str_starts_with($alg, 'RS')) {
272272
self::validateRsaKeyLength($key);
273+
} elseif (str_starts_with($alg, 'ES')) {
274+
self::validateEcKeyLength($key, $alg);
273275
}
274276
$success = \openssl_sign($msg, $signature, $key, $algorithm);
275277
if (!$success) {
@@ -330,11 +332,13 @@ private static function verify(
330332
list($function, $algorithm) = static::$supported_algs[$alg];
331333
switch ($function) {
332334
case 'openssl':
333-
if (str_starts_with($algorithm, 'RS')) {
334-
if (!$key = openssl_pkey_get_private($keyMaterial)) {
335-
throw new DomainException('OpenSSL unable to validate key');
336-
}
335+
if (!$key = openssl_pkey_get_public($keyMaterial)) {
336+
throw new DomainException('OpenSSL unable to validate key');
337+
}
338+
if (str_starts_with($alg, 'RS')) {
337339
self::validateRsaKeyLength($key);
340+
} elseif (str_starts_with($alg, 'ES')) {
341+
self::validateEcKeyLength($key, $alg);
338342
}
339343
$success = \openssl_verify($msg, $signature, $keyMaterial, $algorithm);
340344
if ($success === 1) {
@@ -721,4 +725,24 @@ private static function validateRsaKeyLength(#[\SensitiveParameter] OpenSSLAsymm
721725
throw new DomainException('Provided key is too short');
722726
}
723727
}
728+
729+
/**
730+
* Validate RSA key length
731+
*
732+
* @param OpenSSLAsymmetricKey $key RSA key material
733+
* @param string $algorithm The algorithm
734+
* @throws DomainException Provided key is too short
735+
*/
736+
private static function validateEcKeyLength(
737+
#[\SensitiveParameter] OpenSSLAsymmetricKey $key,
738+
string $algorithm
739+
): void {
740+
if (!$keyDetails = openssl_pkey_get_details($key)) {
741+
throw new DomainException('Unable to validate key');
742+
}
743+
$minKeyLength = (int) \str_replace('ES', '', $algorithm);
744+
if ($keyDetails['bits'] < $minKeyLength) {
745+
throw new DomainException('Provided key is too short');
746+
}
747+
}
724748
}

tests/JWTTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,65 @@ public function provideHmac()
678678
];
679679
}
680680

681+
/** @dataProvider provideEcKeyInvalidLength */
682+
public function testEcKeyLengthValidationThrowsExceptionEncode(string $keyFile, string $alg): void
683+
{
684+
$this->expectException(DomainException::class);
685+
$this->expectExceptionMessage('Provided key is too short');
686+
687+
$tooShortEcKey = file_get_contents(__DIR__ . '/data/' . $keyFile);
688+
$payload = ['message' => 'abc'];
689+
690+
JWT::encode($payload, $tooShortEcKey, $alg);
691+
}
692+
693+
public function testEcKeyLengthValidationThrowsExceptionDecode(): void
694+
{
695+
$this->expectException(DomainException::class);
696+
$this->expectExceptionMessage('Provided key is too short');
697+
698+
$payload = ['message' => 'abc'];
699+
700+
$validEcKeyBytes = file_get_contents(__DIR__ . '/data/ecdsa384-private.pem');
701+
$encoded = JWT::encode($payload, $validEcKeyBytes, 'ES256');
702+
703+
$tooShortEcKey = file_get_contents(__DIR__ . '/data/ecdsa192-public.pem');
704+
JWT::decode($encoded, new Key($tooShortEcKey, 'ES256'));
705+
}
706+
707+
/** @dataProvider provideEcKey */
708+
public function testEcKeyLengthValidationPassesWithCorrectLength(
709+
string $privateKeyFile,
710+
string $publicKeyFile,
711+
string $alg
712+
): void {
713+
$payload = ['message' => 'test hmac length'];
714+
715+
// Test with a key that is the required length
716+
$privateKeyBytes = file_get_contents(__DIR__ . '/data/' . $privateKeyFile);
717+
$encoded48 = JWT::encode($payload, $privateKeyBytes, $alg);
718+
719+
$publicKeyBytes = file_get_contents(__DIR__ . '/data/' . $publicKeyFile);
720+
$decoded48 = JWT::decode($encoded48, new Key($publicKeyBytes, $alg));
721+
$this->assertEquals($payload['message'], $decoded48->message);
722+
}
723+
724+
public function provideEcKeyInvalidLength()
725+
{
726+
return [
727+
['ecdsa192-private.pem', 'ES256'],
728+
['ecdsa-private.pem', 'ES384'],
729+
];
730+
}
731+
732+
public function provideEcKey()
733+
{
734+
return [
735+
['ecdsa-private.pem', 'ecdsa-public.pem', 'ES256'],
736+
['ecdsa384-private.pem', 'ecdsa384-public.pem', 'ES384'],
737+
];
738+
}
739+
681740
private function generateHmac256(): Key
682741
{
683742
return new Key(random_bytes(32), 'HS256');

tests/data/ecdsa192-private.pem

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-----BEGIN EC PRIVATE KEY-----
2+
MF8CAQEEGPRkK7lK/9FuZ3BE8ZX+dlHavL22Q9CN2KAKBggqhkjOPQMBAaE0AzIA
3+
BL4pM50YcLq/I9Y8T+C+fwoOtwRW8zdV6yQmG9fD8zWaAs28+UxHeK8VD7THatbp
4+
wg==
5+
-----END EC PRIVATE KEY-----

tests/data/ecdsa192-public.pem

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-----BEGIN PUBLIC KEY-----
2+
MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEvikznRhwur8j1jxP4L5/Cg63BFbz
3+
N1XrJCYb18PzNZoCzbz5TEd4rxUPtMdq1unC
4+
-----END PUBLIC KEY-----

0 commit comments

Comments
 (0)