margusk / openssl-wrapper
Object oriented wrapper around PHP OpenSSL extension
Requires
- php: ^8.0
- ext-openssl: *
- margusk/accessors: ^0.1
- margusk/warbsorber: ^0.1
Requires (Dev)
- phpunit/phpunit: ^9.5
This package is auto-updated.
Last update: 2024-11-10 14:18:08 UTC
README
PHP OpenSSL wrapper
Object Oriented wrapper around PHP OpenSSL extension
Problems that this library solves
The interface of PHP's OpenSSL extension is archaic and can only be used procedural way, which makes it awkward to use and requires excessive code to handle errors and results. This library tries to solve following shortcomings:
- Errors are not handled using Exceptions: The failures should be always reported using Exceptions instead of procedural way by returning
false
or-1
. - Unexpected PHP warnings: PHP OpenSSL functions should not emit PHP warnings. All warnings should be silently collected and handed over using Exception or through return value. This way the caller can programmatically decide, how to handle them (e.g. log warnings to specific place).
- Function outcome not in return value: The (primary) result that OpenSSL function produces should also be the return value, instead of returning result through one of the function arguments (e.g.
openssl_sign
). The best examples of awkwardness areopenssl_seal
oropenssl_csr_new
which both return values inside multiple input parameters. - Key/Certificate/CSR objects are just numb internal DTO-s: Although in PHP 8 the OpenSSL resources were replaced with objects (
OpenSSLCertificate
,OpenSSLCertificateSigningRequest
andOpenSSLAsymmetricKey
), they are internally still just plain DTO-s (Data Transfer Objects). There's no methods or properties exposed and they can only be used in procedural way by specifying it as parameter to one of theopenssl_*
functions. This library wraps those objects to provide direct access to OpenSSL methods specific to the object.
The functionality with extension is almost fully retained, which means that all internal openssl_*
functions (except deprecated or duplicates) are wrapped. Difference lies between method signatures: wrapper signatures can be shorter and it's parameters are never specified by reference.
What this library doesn't do: it doesn't add or change any of the OpenSSL cryptographic functionality. The only purpose is to just offer convenient object oriented interface for OpenSSL functions.
Requirements
- PHP >= 8.0
- OpenSSL Extension
Installation
Install with composer:
composer require margusk/openssl-wrapper
Usage
Static methods vs instance methods
Wrapper can be used in 2 different ways:
- Statically: the simplest way is to use static
OpenSSL
class:
use margusk\OpenSSL\Wrapper\OpenSSL; $result = OpenSSL::pkeyNew([ 'private_key_type' => OPENSSL_KEYTYPE_RSA ]);
- Using instance: to have ability to customize/intercept exceptions, use
Proxy
instance (see below for Customizing/intercepting exceptions):
use margusk\OpenSSL\Wrapper\Proxy as OpenSSLProxy; $proxy = new OpenSSLProxy(); $result = $proxy->pkeyNew([ 'private_key_type' => OPENSSL_KEYTYPE_RSA ]);
Mapping OpenSSL function to wrapper method name
Most of the OpenSSL functions have counterparts in wrapper class (but see exceptions below).
Wrapper name can be derived as following:
- Remove
openssl_
prefix from internal function name - Convert the remainder from snake-case into camel-case format
E.g. openssl_get_cipher_methods
transforms to getCipherMethods
and can be called using:
OpenSSL::getCipherMethods()
or(new OpenSSLProxy())->getCipherMethods()
Following functions are not wrapped:
openssl_free_key
- deprecated in PHP 8openssl_pkey_free
- deprecated in PHP 8openssl_x509_free
- deprecated in PHP 8openssl_get_privatekey
- useopenssl_pkey_get_private
insteadopenssl_get_publickey
- useopenssl_pkey_get_public
instead
Return values
Each call to wrapper method returns object derived from Result
class.
This object encapsulate everything associated with the call from the start to the end. Namely:
$result->value()
returns the actual value of the result. The data type depends of the called method and can beint
,bool
,string
,array
or complex type likeAsymmetricKey
,CSR
orCertificate
$result->inParameters()
returns array of input parameters for internal function call. Note that optional parameters are always provided$result->outParameters()
returns array of parameters after beeing possibly modified by internal function$result->warnings()->openSSL()
returns array of the warnings reported by OpenSSL library during the call$result->warnings()->php()
returns array of warnings/errors reported by PHP (by emitting warnings) during the call
Some internal functions return values also through referenced input parameters. Wrapper doesn't take such parameters, but returns those values in Result
object:
openssl_csr_new
: internally generated &$private_key can be received using$result->privateKey()
openssl_encrypt
: internally generated &$tag value can be received using$result->tag()
openssl_random_pseudo_bytes
: flag of &$strong_result can be received using$result->strongResult()
openssl_seal
: internally generated &$encrypted_keys and &$iv can be received using$result->encryptedKeys()
and$result->iv()
Complex types like OpenSSLAsymmetricKey, OpenSSLCertificateSigningRequest and OpenSSLCertificate
Some functions (e.g. openssl_pkey_new
) return special OpenSSL objects, which unfortunately are totally useless on their own. This library makes them a bit more useful. So each time an openssl_*
function returns an object:
OpenSSLAsymmetricKey
is wrapped byAsymmetricKey
OpenSSLCertificateSigningRequest
is wrapped byCSR
OpenSSLCertificate
is wrapped byCertificate
All those object wrappers provide methods related to the type of OpenSSL object. For example:
AsymmetricKey
object hasopenssl_pkey_*
methods coveredCSR
object hasopenssl_x509_*
methods coveredCertificate
object hasopenssl_csr_*
methods covered
Handling Exceptions
When internal openssl_*
function fails under the hood, exception OpenSSLCallFailedException
is thrown by default. This special exception carries the specifics of the failure for later inspection. Namely:
$exception->funcName()
returns the name of internal function$exception->result()
returns the exact result code of internal function$exception->errors()->openSSL()
returns array of the warnings/errors reported by OpenSSL library$exception->errors()->php()
returns array of warnings/errors reported by PHP (by emitting warnings)
Customizing/intercepting exceptions
Sometimes it's nesseccary to provide customized exceptions instead of built-in OpenSSLCallFailedException
. There's couple of ways for doing it.
The simplest would be to just put the call inside try/catch
block and then re-throw with custom exception like this:
use margusk\OpenSSL\Wrapper\Exception\OpenSSLCallFailedException; use margusk\OpenSSL\Wrapper\OpenSSL; class MyCustomException extends Exception {} try { $pkey = OpenSSL::pkeyNew([ 'private_key_type' => OPENSSL_KEYTYPE_RSA ])->value(); } catch (OpenSSLCallFailedException $e) { throw new MyCustomException('Something went wrong', 0, $e); }
However, to provide more flexible way of intercepting failures (e.g. for logging), we can register failure handler and associate it with specific or anykind of openssl_*
function:
use margusk\OpenSSL\Wrapper\Exception\OpenSSLCallFailedException; use margusk\OpenSSL\Wrapper\Proxy as OpenSSLProxy; use margusk\OpenSSL\Wrapper\Proxy\Options as OpenSSLProxyOptions; class MyCustomException extends Exception { // ... } function myLoggingFunc(string $msg) { // ... } // Create Proxy options and register failure handler for "openssl_pkey_new" $options = (new OpenSSLProxyOptions()) ->registerFailureHandler('openssl_pkey_new', function(OpenSSLCallFailedException $exception): Throwable { myLoggingFunc($exception->funcName() . ': ' $exception->getMessage()); return new MyCustomException( $exception->getMessage(), $exception->getCode(), $exception ); }); // Create private wrapper instance $proxy = new OpenSSLProxy($options); // If openssl_pkey_new fails, then error is logged and MyOpenSSLException is thrown instead of OpenSSLCallFailedException try { $pkey = $proxy->pkeyNew([ 'private_key_type' => OPENSSL_KEYTYPE_RSA ])->value(); } catch (MyCustomException $e) { echo "MyCustomException: " . $e->getMessage() . "\n"; }
Failure handler is registered using OpenSSLProxyOptions::registerFailureHandler(string $pattern, Closure $cb)
where:
-
$pattern denotes internal function name for which the handler is executed. If prefixed with regex: then the remainder is interpreted as regular expression.
E.g. regex:openssl_.* or regex:.* will catch all
openssl_*
functions that fail -
$cb is callback accepting 1 parameter of
OpenSSLCallFailedException
and returningThrowable
. It's also totally okay to throw directly from the callback without returning anything.
Note that OpenSSLProxyOptions
is immutable class, where each call to registerFailureHandler
returns cloned instance of itself with new handler added.
To register multiple handlers at once without cloning and throwing away lots of objects for nothing use OpenSSLProxyOptions::registerFailureHandlers(array $callbacks)
, where $callbacks
contains $pattern
and $cb
parameters in associative way:
use margusk\OpenSSL\Wrapper\Proxy\Options as OpenSSLProxyOptions; use margusk\OpenSSL\Wrapper\Exception\OpenSSLCallFailedException; // Register specific handler for "openssl_pkey_new" and another handler for the rest of "openssl_*" functions $options = (new OpenSSLProxyOptions()) ->registerFailureHandlers([ 'openssl_pkey_new' => function(OpenSSLCallFailedException $exception): Throwable { myLoggingFunc1($exception->funcName() . ': ' $exception->getMessage()); return new MyCustomException( $exception->getMessage(), $exception->getCode(), $exception ); }, 'regex:openssl_.*' => function(OpenSSLCallFailedException $exception): Throwable { myLoggingFunc2($exception->funcName() . ': ' $exception->getMessage()); return $exception; } ]);
License
This library is released under the MIT License.