From ab17ee5ecd524880da0c85691eedd0c890b6bcb8 Mon Sep 17 00:00:00 2001 From: Ievgenii Skliarenko Date: Fri, 30 Jan 2026 11:00:30 +0100 Subject: [PATCH 1/3] Introduce `OpenSslException` for detailed error handling in OpenSSL operations --- .../src/JWT/Algorithms/OpenSSLSign.php | 8 ++-- .../src/JWT/Algorithms/OpenSSLVerify.php | 22 ++++++++-- .../Virtue-JWT/src/JWT/OpenSslException.php | 42 +++++++++++++++++++ 3 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 vicephp/Virtue-JWT/src/JWT/OpenSslException.php diff --git a/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLSign.php b/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLSign.php index 9fba788..56bcb49 100644 --- a/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLSign.php +++ b/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLSign.php @@ -3,9 +3,9 @@ namespace Virtue\JWT\Algorithms; use Virtue\Encoding\ASN1; -use Virtue\JWK\AsymmetricKey; use Virtue\JWK\Key\OpenSSL\Exportable; use Virtue\JWT\Algorithm; +use Virtue\JWT\OpenSslException; use Virtue\JWT\SignFailed; use Virtue\JWT\SignsToken; use Webmozart\Assert\Assert; @@ -50,7 +50,8 @@ public function sign(string $msg): string } if (!$private = \openssl_pkey_get_private($this->private->asPem(), $this->private->passphrase())) { - throw new SignFailed('Key or passphrase are invalid.'); + $opensslException = new OpenSslException(OpenSslException::collectErrors()); + throw new SignFailed('Key or passphrase are invalid.', 0, $opensslException); } if (!isset($this->supported[$this->name])) { @@ -85,7 +86,8 @@ public function sign(string $msg): string \openssl_pkey_free($private); } if (!$success) { - throw new SignFailed('OpenSSL error: ' . \openssl_error_string()); + $opensslException = new OpenSslException(OpenSslException::collectErrors()); + throw new SignFailed('OpenSSL error occurred during signing.', 0, $opensslException); } else { return $signature; } diff --git a/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLVerify.php b/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLVerify.php index 3080eca..75a24c8 100644 --- a/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLVerify.php +++ b/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLVerify.php @@ -7,6 +7,7 @@ use Virtue\JWK\Key\EdDSA; use Virtue\JWT\Algorithm; use Virtue\JWT\Base64Url; +use Virtue\JWT\OpenSslException; use Virtue\JWT\Token; use Virtue\JWT\VerificationFailed; use Virtue\JWT\VerifiesToken; @@ -62,7 +63,8 @@ public function verify(Token $token): void } if (!$public = \openssl_pkey_get_public($this->public->asPem())) { - throw new VerificationFailed('Key is invalid.', VerificationFailed::ON_SIGNATURE); + $opensslException = new OpenSslException(OpenSslException::collectErrors()); + throw new VerificationFailed('Key is invalid.', VerificationFailed::ON_SIGNATURE, $opensslException); } $ecPadding = [ 'ES256' => 32, @@ -84,10 +86,22 @@ public function verify(Token $token): void } if ($success === 1) { return; - } elseif ($success === 0) { - throw new VerificationFailed('Could not verify signature.', VerificationFailed::ON_SIGNATURE); } - throw new VerificationFailed('OpenSSL error: ' . \openssl_error_string(), VerificationFailed::ON_SIGNATURE); + $opensslException = new OpenSslException(OpenSslException::collectErrors()); + + if ($success === 0) { + throw new VerificationFailed( + 'Could not verify signature.', + VerificationFailed::ON_SIGNATURE, + $opensslException + ); + } + + throw new VerificationFailed( + 'OpenSSL error occurred during signature verification.', + VerificationFailed::ON_UNKNOWN, + $opensslException + ); } } diff --git a/vicephp/Virtue-JWT/src/JWT/OpenSslException.php b/vicephp/Virtue-JWT/src/JWT/OpenSslException.php new file mode 100644 index 0000000..ad78794 --- /dev/null +++ b/vicephp/Virtue-JWT/src/JWT/OpenSslException.php @@ -0,0 +1,42 @@ +errors = $errors; + $message = 'OpenSSL error(s): ' . implode('; ', $errors); + parent::__construct($message, $code, $previous); + } + + /** + * @return string[] + */ + public function getErrors(): array + { + return $this->errors; + } + + /** + * @return string[] + */ + public static function collectErrors(): array + { + $errors = []; + while ($error = \openssl_error_string()) { + $errors[] = $error; + } + return $errors; + } +} From f9c689331ec183e04e89139155a025e787aed01c Mon Sep 17 00:00:00 2001 From: Ievgenii Skliarenko Date: Fri, 30 Jan 2026 11:12:31 +0100 Subject: [PATCH 2/3] Refactor OpenSSL error handling to simplify exception creation and improve error collection --- vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLSign.php | 6 ++---- .../Virtue-JWT/src/JWT/Algorithms/OpenSSLVerify.php | 11 +++++++---- vicephp/Virtue-JWT/src/JWT/OpenSslException.php | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLSign.php b/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLSign.php index 56bcb49..92d0b0c 100644 --- a/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLSign.php +++ b/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLSign.php @@ -50,8 +50,7 @@ public function sign(string $msg): string } if (!$private = \openssl_pkey_get_private($this->private->asPem(), $this->private->passphrase())) { - $opensslException = new OpenSslException(OpenSslException::collectErrors()); - throw new SignFailed('Key or passphrase are invalid.', 0, $opensslException); + throw new SignFailed('Key or passphrase are invalid.', 0, OpenSslException::collectErrors()); } if (!isset($this->supported[$this->name])) { @@ -86,8 +85,7 @@ public function sign(string $msg): string \openssl_pkey_free($private); } if (!$success) { - $opensslException = new OpenSslException(OpenSslException::collectErrors()); - throw new SignFailed('OpenSSL error occurred during signing.', 0, $opensslException); + throw new SignFailed('OpenSSL error occurred during signing.', 0, OpenSslException::collectErrors()); } else { return $signature; } diff --git a/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLVerify.php b/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLVerify.php index 75a24c8..721973e 100644 --- a/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLVerify.php +++ b/vicephp/Virtue-JWT/src/JWT/Algorithms/OpenSSLVerify.php @@ -63,8 +63,11 @@ public function verify(Token $token): void } if (!$public = \openssl_pkey_get_public($this->public->asPem())) { - $opensslException = new OpenSslException(OpenSslException::collectErrors()); - throw new VerificationFailed('Key is invalid.', VerificationFailed::ON_SIGNATURE, $opensslException); + throw new VerificationFailed( + 'Key is invalid.', + VerificationFailed::ON_SIGNATURE, + OpenSslException::collectErrors() + ); } $ecPadding = [ 'ES256' => 32, @@ -88,7 +91,7 @@ public function verify(Token $token): void return; } - $opensslException = new OpenSslException(OpenSslException::collectErrors()); + $opensslException = OpenSslException::collectErrors(); if ($success === 0) { throw new VerificationFailed( @@ -100,7 +103,7 @@ public function verify(Token $token): void throw new VerificationFailed( 'OpenSSL error occurred during signature verification.', - VerificationFailed::ON_UNKNOWN, + VerificationFailed::ON_SIGNATURE, $opensslException ); } diff --git a/vicephp/Virtue-JWT/src/JWT/OpenSslException.php b/vicephp/Virtue-JWT/src/JWT/OpenSslException.php index ad78794..0574642 100644 --- a/vicephp/Virtue-JWT/src/JWT/OpenSslException.php +++ b/vicephp/Virtue-JWT/src/JWT/OpenSslException.php @@ -10,7 +10,7 @@ class OpenSslException extends \RuntimeException /** * @param string[] $errors */ - public function __construct(array $errors, int $code = 0, ?\Throwable $previous = null) + private function __construct(array $errors, int $code = 0, ?\Throwable $previous = null) { if (empty($errors)) { $errors = ['Unknown OpenSSL error']; @@ -29,14 +29,14 @@ public function getErrors(): array } /** - * @return string[] + * @return self */ - public static function collectErrors(): array + public static function collectErrors(): self { $errors = []; while ($error = \openssl_error_string()) { $errors[] = $error; } - return $errors; + return new self($errors); } } From abb11c1d5d35b351d7eed5ad3f4fbc35d8165107 Mon Sep 17 00:00:00 2001 From: Ievgenii Skliarenko Date: Mon, 2 Feb 2026 09:09:07 +0100 Subject: [PATCH 3/3] Add unit tests for `OpenSslException` to ensure robust error handling and validation --- .../tests/JWT/OpenSslExceptionTest.php | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 vicephp/Virtue-JWT/tests/JWT/OpenSslExceptionTest.php diff --git a/vicephp/Virtue-JWT/tests/JWT/OpenSslExceptionTest.php b/vicephp/Virtue-JWT/tests/JWT/OpenSslExceptionTest.php new file mode 100644 index 0000000..084bbf0 --- /dev/null +++ b/vicephp/Virtue-JWT/tests/JWT/OpenSslExceptionTest.php @@ -0,0 +1,63 @@ +assertEquals(['Unknown OpenSSL error'], $exception->getErrors()); + $this->assertEquals('OpenSSL error(s): Unknown OpenSSL error', $exception->getMessage()); + } + + public function testCollectErrorsWithMultipleErrors(): void + { + @\openssl_pkey_get_private('invalid-key-data'); + + $exception = OpenSslException::collectErrors(); + + $errors = $exception->getErrors(); + $this->assertNotEmpty($errors); + $this->assertIsArray($errors); + + $expectedMessage = 'OpenSSL error(s): ' . implode('; ', $errors); + $this->assertEquals($expectedMessage, $exception->getMessage()); + } + + public function testGetErrors(): void + { + @\openssl_pkey_get_private('invalid-key'); + + $exception = OpenSslException::collectErrors(); + $errors = $exception->getErrors(); + + $this->assertIsArray($errors); + $this->assertNotEmpty($errors); + foreach ($errors as $error) { + $this->assertIsString($error); + } + } + + public function testExceptionCanBeUsedAsPrevious(): void + { + $opensslException = OpenSslException::collectErrors(); + $wrappedException = new \RuntimeException('Wrapper exception', 0, $opensslException); + + $this->assertSame($opensslException, $wrappedException->getPrevious()); + $this->assertEquals('OpenSSL error(s): Unknown OpenSSL error', $opensslException->getMessage()); + } +}