margusk / openssl-wrapper

PHP OpenSSL 扩展的面向对象包装器

dev-master 2022-11-01 08:23 UTC

This package is auto-updated.

Last update: 2024-09-10 13:55:12 UTC


README

Tests

PHP OpenSSL 包装器

PHP OpenSSL 扩展的面向对象包装器

此库解决的问题

PHP 的 OpenSSL 扩展的接口是过时的,只能以过程式的方式使用,这使得使用起来很笨拙,并且需要大量的代码来处理错误和结果。此库试图解决以下不足

  • 错误不是通过异常处理:失败应始终通过返回 false-1 的过程式方式来报告,而不是使用异常。
  • 意外的 PHP 警告:PHP OpenSSL 函数不应发出 PHP 警告。所有警告都应静默收集,并通过异常或返回值传递。这样,调用者可以程序化地决定如何处理它们(例如,将警告记录到特定位置)。
  • 函数结果不在返回值中:OpenSSL 函数产生的(主要)结果也应作为返回值,而不是通过其中一个函数参数(例如 openssl_sign)返回结果。最不自然的例子是 openssl_sealopenssl_csr_new,它们都返回多个输入参数中的值。
  • 密钥/证书/CSR 对象只是简单的内部 DTO:尽管在 PHP 8 中,OpenSSL 资源被替换为对象(OpenSSLCertificateOpenSSLCertificateSigningRequestOpenSSLAsymmetricKey),但它们在内部仍然只是简单的 DTO(数据传输对象)。没有公开方法和属性,并且只能通过将它们指定为 openssl_* 函数的参数以过程式方式使用。此库将这些对象包装起来,以提供对特定对象的 OpenSSL 方法的直接访问。

与扩展的功能几乎完全保留,这意味着所有内部 openssl_* 函数(除已弃用或重复的以外)都被包装。区别在于方法签名:包装器的签名可以更短,并且它的参数从不按引用指定。

此库不做什么:它不会添加或更改任何 OpenSSL 加密功能。唯一目的是提供 OpenSSL 函数的方便的面向对象接口。

要求

安装

使用 composer 安装

composer require margusk/openssl-wrapper

使用方法

静态方法与实例方法

包装器可以使用两种不同的方式使用

  1. 静态地:最简单的方法是使用静态 OpenSSL
use margusk\OpenSSL\Wrapper\OpenSSL;

$result = OpenSSL::pkeyNew([
  'private_key_type' => OPENSSL_KEYTYPE_RSA
]);
  1. 使用 实例:为了能够自定义/拦截异常,请使用 Proxy 实例(有关自定义/拦截异常的说明见下文)
use margusk\OpenSSL\Wrapper\Proxy as OpenSSLProxy;

$proxy = new OpenSSLProxy();

$result = $proxy->pkeyNew([
   'private_key_type' => OPENSSL_KEYTYPE_RSA
]);

将 OpenSSL 函数映射到包装器方法名称

大多数 OpenSSL 函数在包装器类中都有对应的方法(但请参见下面的例外)。

包装器名称的推导方法

  1. 从内部函数名称中删除 openssl_ 前缀
  2. 将剩余部分从 snake-case 转换为 camel-case 格式

例如,openssl_get_cipher_methods 转换为 getCipherMethods,可以使用以下方式调用

  • OpenSSL::getCipherMethods()
  • (new OpenSSLProxy())->getCipherMethods()

以下函数没有被包装

返回值

对包装方法的每次调用都返回从 Result 类派生出的对象。

该对象封装了从开始到结束与调用相关的所有内容。具体来说

  • $result->value() 返回结果的实际值。数据类型取决于调用的方法,可以是 intboolstringarray 或像 AsymmetricKeyCSRCertificate 这样的复杂类型。
  • $result->inParameters() 返回内部函数调用所需的输入参数数组。请注意,可选参数始终提供
  • $result->outParameters() 返回在内部函数可能被修改后的参数数组
  • $result->warnings()->openSSL() 返回在调用过程中 OpenSSL 库报告的警告数组
  • $result->warnings()->php() 返回在调用过程中 PHP (通过发出警告) 报告的警告/错误数组

一些内部函数也通过引用输入参数返回值。包装器不取此类参数,但将这些值返回到 Result 对象中

  1. openssl_csr_new:内部生成的 &$private_key 可以通过 $result->privateKey() 获取
  2. openssl_encrypt:内部生成的 &$tag 值可以通过 $result->tag() 获取
  3. openssl_random_pseudo_bytes:标志 &$strong_result 可以通过 $result->strongResult() 获取
  4. openssl_seal:内部生成的 &$encrypted_keys&$iv 可以通过 $result->encryptedKeys()$result->iv() 获取

如 OpenSSLAsymmetricKey、OpenSSLCertificateSigningRequest 和 OpenSSLCertificate 这样的复杂数据类型

一些函数(例如 openssl_pkey_new)返回特殊的 OpenSSL 对象,不幸的是,它们单独使用时完全无用。这个库使它们变得更有用。因此,每次 openssl_* 函数返回一个对象

  • OpenSSLAsymmetricKey 被包装在 AsymmetricKey
  • OpenSSLCertificateSigningRequest 被包装在 CSR
  • OpenSSLCertificate 被包装在 Certificate

所有这些对象包装器都提供了与 OpenSSL 对象类型相关的功能。例如

  • AsymmetricKey 对象涵盖了 openssl_pkey_* 方法
  • CSR 对象涵盖了 openssl_x509_* 方法
  • Certificate 对象涵盖了 openssl_csr_* 方法

异常处理

当底层的 openssl_* 函数失败时,默认会抛出异常 OpenSSLCallFailedException。这个特殊的异常包含了失败的具体信息,以便稍后检查。具体来说

  • $exception->funcName() 返回内部函数的名称
  • $exception->result() 返回内部函数的确切返回代码
  • $exception->errors()->openSSL() 返回 OpenSSL 库报告的警告/错误数组
  • $exception->errors()->php() 返回 PHP(通过发出警告)报告的警告/错误数组

自定义/拦截异常

有时需要提供自定义异常而不是内置的 OpenSSLCallFailedException。有几种方法可以做到这一点。

最简单的方法是将调用放在 try/catch 块中,然后重新抛出自定义异常,如下所示

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);
}

然而,为了提供更灵活的失败拦截方式(例如,用于日志记录),我们可以注册失败处理程序并将其与特定的或任何类型的 openssl_* 函数关联起来

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";
}

失败处理程序是通过 OpenSSLProxyOptions::registerFailureHandler(string $pattern, Closure $cb) 注册的,其中

  • $pattern 表示执行处理程序的内部函数名称。如果以 regex: 前缀,则其余部分被解释为正则表达式。

    例如,regex:openssl_.* 或 regex:.* 将捕获所有失败的 openssl_* 函数

  • $cb 是接受一个参数(OpenSSLCallFailedException)并返回 Throwable 的回调函数。在回调函数中直接抛出异常而不返回任何内容也是完全可以的。

请注意,OpenSSLProxyOptions 是不可变类,其中对 registerFailureHandler 的每次调用都会返回一个新的克隆实例,其中包含了新添加的处理程序。

要一次性注册多个处理程序而无需克隆并丢弃大量对象,请使用 OpenSSLProxyOptions::registerFailureHandlers(array $callbacks),其中 $callbacks 包含以关联方式指定的 $pattern$cb 参数

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;
        }
    ]);

许可证

此库根据 MIT 许可证发布。