feat(support): 初始化项目基础结构和核心功能
- 添加了 Laravel 项目配置文件,包括 IDE 字典、模块定义和版本控制设置 - 配置了 PHP 开发环境,包含依赖路径、代码质量工具和测试框架设置 - 实现了模型转换特性 (ModelCastSetter),支持对象到数组的自动序列化 - 创建了通用数组处理类 (Arrayable),提供对象与数组间的便捷转换方法 - 开发了 RSA 加密解密工具类,支持证书解析、密钥验证和数据安全操作 - 编写了单元测试和功能测试示例,确保代码质量和功能正确性 - 设置了 Pest 测试框架基础配置,便于后续扩展测试用例 - 添加了 Carbon 和 Paratest 等常用工具的二进制代理脚本
This commit is contained in:
commit
bc42abeae8
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
*.log
|
||||
.DS_Store
|
||||
.env
|
||||
.env.backup
|
||||
.env.production
|
||||
.phpactor.json
|
||||
.phpunit.result.cache
|
||||
/.fleet
|
||||
/.idea
|
||||
/.nova
|
||||
/.phpunit.cache
|
||||
/.vscode
|
||||
/.zed
|
||||
/auth.json
|
||||
/node_modules
|
||||
/public/build
|
||||
/public/hot
|
||||
/public/storage
|
||||
/storage/*.key
|
||||
/storage/pail
|
||||
/vendor
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
Thumbs.db
|
||||
41
composer.json
Normal file
41
composer.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "jltx/support",
|
||||
"description": "项目辅助功能",
|
||||
"minimum-stability": "stable",
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Jltx\\Support\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Jltx\\Support\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "yeyixianyang",
|
||||
"email": "yeyixianyang@163.com"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^8.4",
|
||||
"illuminate/support": "^12.41",
|
||||
"illuminate/database": "^12.41",
|
||||
"ext-openssl": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"pestphp/pest": "^4.1",
|
||||
"laravel/pint": "^1.26"
|
||||
},
|
||||
"config": {
|
||||
"allow-plugins": {
|
||||
"pestphp/pest-plugin": true
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"format": "./vendor/bin/pint",
|
||||
"test": "./vendor/bin/pest"
|
||||
}
|
||||
}
|
||||
4219
composer.lock
generated
Normal file
4219
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
phpunit.xml
Normal file
18
phpunit.xml
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Test Suite">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>app</directory>
|
||||
<directory>src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
18
src/Concerns/ModelCastSetter.php
Normal file
18
src/Concerns/ModelCastSetter.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Jltx\Support\Concerns;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Jltx\Support\Helpers\Arrayable;
|
||||
|
||||
trait ModelCastSetter
|
||||
{
|
||||
public function set(Model $model, string $key, mixed $value, array $attributes): array
|
||||
{
|
||||
return [
|
||||
$key => $value ? json_encode(
|
||||
$value instanceof Arrayable ? $value->toArray() : (array) $value,
|
||||
) : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
122
src/Helpers/Arrayable.php
Normal file
122
src/Helpers/Arrayable.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace Jltx\Support\Helpers;
|
||||
|
||||
use ArrayAccess;
|
||||
use JsonSerializable;
|
||||
|
||||
abstract class Arrayable implements \Illuminate\Contracts\Support\Arrayable, ArrayAccess, JsonSerializable
|
||||
{
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的将属性转为数组
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach (get_object_vars($this) as $property => $value) {
|
||||
if (is_object($value) && method_exists($value, 'toArray')) {
|
||||
$data[$property] = $value->toArray();
|
||||
} elseif (is_array($value)) {
|
||||
$data[$property] = array_map(function ($item) {
|
||||
return is_object($item) && method_exists($item, 'toArray')
|
||||
? $item->toArray()
|
||||
: $item;
|
||||
}, $value);
|
||||
} else {
|
||||
$data[$property] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否存在属性
|
||||
*/
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
return property_exists($this, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取属性值
|
||||
*/
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
return $this->$offset ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置属性值
|
||||
*/
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
$this->$offset = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除属性值
|
||||
*/
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
if (property_exists($this, $offset)) {
|
||||
unset($this->$offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅获取指定属性
|
||||
*/
|
||||
public function only(array $keys): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach (get_object_vars($this) as $property => $value) {
|
||||
if (in_array($property, $keys, true)) {
|
||||
$result[$property] = $this->normalizeValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一格式化数组值或对象值(用于 only / except)
|
||||
*/
|
||||
protected function normalizeValue(mixed $value): mixed
|
||||
{
|
||||
if (is_object($value) && method_exists($value, 'toArray')) {
|
||||
return $value->toArray();
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
return array_map(fn ($item) => is_object($item) && method_exists($item, 'toArray')
|
||||
? $item->toArray()
|
||||
: $item, $value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 排除指定属性
|
||||
*/
|
||||
public function except(array $keys): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach (get_object_vars($this) as $property => $value) {
|
||||
if (! in_array($property, $keys, true)) {
|
||||
$result[$property] = $this->normalizeValue($value);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
272
src/Security/RSA.php
Normal file
272
src/Security/RSA.php
Normal file
@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace Jltx\Support\Security;
|
||||
|
||||
use OpenSSLAsymmetricKey;
|
||||
use OpenSSLCertificate;
|
||||
use RuntimeException;
|
||||
|
||||
readonly class RSA
|
||||
{
|
||||
private const string PKCS1_PRIVATE_KEY_PREFIX = '-----BEGIN RSA PRIVATE KEY-----';
|
||||
|
||||
private const string PKCS1_PRIVATE_KEY_SUFFIX = '-----END RSA PRIVATE KEY-----';
|
||||
|
||||
private const string PKCS8_PRIVATE_KEY_PREFIX = '-----BEGIN PRIVATE KEY-----';
|
||||
|
||||
private const string PKCS8_PRIVATE_KEY_SUFFIX = '-----END PRIVATE KEY-----';
|
||||
|
||||
private const string PUBLIC_KEY_PREFIX = '-----BEGIN PUBLIC KEY-----';
|
||||
|
||||
private const string PUBLIC_KEY_SUFFIX = '-----END PUBLIC KEY-----';
|
||||
|
||||
private const string CERT_PREFIX = '-----BEGIN CERTIFICATE-----';
|
||||
|
||||
private const string CERT_SUFFIX = '-----END CERTIFICATE-----';
|
||||
|
||||
private const string PKCS8 = 'PKCS#8';
|
||||
|
||||
private const string PKCS1 = 'PKCS#1';
|
||||
|
||||
public static function getPublicKeyStringFromCert(string $certContent): string
|
||||
{
|
||||
$publicKey = self::getPublicKeyFromCert($certContent);
|
||||
$detail = openssl_pkey_get_details($publicKey);
|
||||
if (! $detail) {
|
||||
throw new RuntimeException('获取公钥详情失败');
|
||||
}
|
||||
|
||||
return $detail['key'];
|
||||
}
|
||||
|
||||
public static function getPublicKeyFromCert(string $certContent): OpenSSLAsymmetricKey
|
||||
{
|
||||
$cert = self::getCert($certContent);
|
||||
|
||||
$publicKey = openssl_get_publickey($cert);
|
||||
if (! $publicKey) {
|
||||
throw new RuntimeException('从证书中读取公钥失败');
|
||||
}
|
||||
|
||||
return $publicKey;
|
||||
}
|
||||
|
||||
public static function getCert(string $certContent): OpenSSLCertificate
|
||||
{
|
||||
$cert = openssl_x509_read($certContent);
|
||||
if (! $cert) {
|
||||
throw new RuntimeException('证书加载失败');
|
||||
}
|
||||
|
||||
return $cert;
|
||||
}
|
||||
|
||||
public static function getCertSn(string $certContent): string
|
||||
{
|
||||
$cert = self::getCert($certContent);
|
||||
$info = openssl_x509_parse($cert);
|
||||
if (! $info) {
|
||||
throw new RuntimeException('解析证书失败');
|
||||
}
|
||||
|
||||
return $info['serialNumberHex'];
|
||||
}
|
||||
|
||||
public static function encrypt(
|
||||
string $plaintext,
|
||||
OpenSSLAsymmetricKey $publicKey,
|
||||
int $padding = OPENSSL_PKCS1_OAEP_PADDING,
|
||||
): string {
|
||||
if (! openssl_public_encrypt($plaintext, $encrypted, $publicKey, $padding)) {
|
||||
throw new RuntimeException('加密失败');
|
||||
}
|
||||
|
||||
return base64_encode($encrypted);
|
||||
}
|
||||
|
||||
public static function verify(
|
||||
string $message,
|
||||
string $signature,
|
||||
OpenSSLAsymmetricKey $publicKey,
|
||||
int|string $algo,
|
||||
): bool {
|
||||
return (bool) openssl_verify($message, base64_decode($signature), $publicKey, $algo);
|
||||
}
|
||||
|
||||
public static function sign(string $message, OpenSSLAsymmetricKey $privateKey, int|string $algo): string
|
||||
{
|
||||
if (! openssl_sign($message, $signature, $privateKey, $algo)) {
|
||||
throw new RuntimeException('签名失败');
|
||||
}
|
||||
|
||||
return base64_encode($signature);
|
||||
}
|
||||
|
||||
public static function decrypt(
|
||||
string $ciphertext,
|
||||
OpenSSLAsymmetricKey $privateKey,
|
||||
int $padding = OPENSSL_PKCS1_OAEP_PADDING,
|
||||
): string {
|
||||
if (! openssl_private_decrypt(base64_decode($ciphertext), $decrypted, $privateKey, $padding)) {
|
||||
throw new RuntimeException('解密失败');
|
||||
}
|
||||
|
||||
return $decrypted;
|
||||
}
|
||||
|
||||
public static function certificateFormat(string $certContent): string
|
||||
{
|
||||
$certContent = self::removePrefixAndSuffix($certContent);
|
||||
|
||||
return self::CERT_PREFIX."\n".chunk_split($certContent, 64).self::CERT_SUFFIX;
|
||||
}
|
||||
|
||||
private static function removePrefixAndSuffix(string $content): string
|
||||
{
|
||||
// 去掉可能的PEM头尾和换行符
|
||||
$content = preg_replace('/-----(BEGIN|END)[\w\s]+-----/', '', $content);
|
||||
$content = preg_replace('/\s+/', '', $content);
|
||||
// 删除 PEM 头尾和换行
|
||||
$content = trim($content);
|
||||
|
||||
return str_replace(["\n", "\r", ' '], '', $content);
|
||||
}
|
||||
|
||||
public static function generateKeyPair(): array
|
||||
{
|
||||
$config = [
|
||||
'digest_alg' => 'sha256',
|
||||
'private_key_bits' => 2048,
|
||||
'private_key_type' => OPENSSL_KEYTYPE_RSA,
|
||||
];
|
||||
|
||||
$res = openssl_pkey_new($config);
|
||||
|
||||
if (! $res) {
|
||||
throw new RuntimeException('Failed to create a new RSA key pair.');
|
||||
}
|
||||
|
||||
// 导出私钥
|
||||
$privateKey = '';
|
||||
if (! openssl_pkey_export($res, $privateKey)) {
|
||||
throw new RuntimeException('Failed to export the private key.');
|
||||
}
|
||||
|
||||
// 获取公钥详情
|
||||
$details = openssl_pkey_get_details($res);
|
||||
if (! isset($details['key'])) {
|
||||
throw new RuntimeException('Failed to retrieve the public key.');
|
||||
}
|
||||
|
||||
// 返回公钥和私钥
|
||||
return [
|
||||
'private_key' => $privateKey,
|
||||
'public_key' => $details['key'],
|
||||
];
|
||||
}
|
||||
|
||||
public static function validateCertificateContent(string $certContent): bool
|
||||
{
|
||||
try {
|
||||
$cert = self::getCert($certContent);
|
||||
|
||||
return (bool) $cert;
|
||||
} catch (RuntimeException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function validatePublicKeyContent(string $publicKeyContent): bool
|
||||
{
|
||||
try {
|
||||
$formattedPublicKey = self::publicKeyFormat($publicKeyContent);
|
||||
$publicKey = self::getPublicKey($formattedPublicKey);
|
||||
|
||||
return (bool) $publicKey;
|
||||
} catch (RuntimeException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function publicKeyFormat(string $publicKeyContent): string
|
||||
{
|
||||
$publicKeyContent = self::removePrefixAndSuffix($publicKeyContent);
|
||||
|
||||
return self::PUBLIC_KEY_PREFIX."\n".chunk_split($publicKeyContent, 64).self::PUBLIC_KEY_SUFFIX;
|
||||
}
|
||||
|
||||
public static function getPublicKey(string $publicKeyContent): OpenSSLAsymmetricKey
|
||||
{
|
||||
$publicKey = openssl_pkey_get_public($publicKeyContent);
|
||||
if (! $publicKey) {
|
||||
throw new RuntimeException('非法公钥');
|
||||
}
|
||||
|
||||
return $publicKey;
|
||||
}
|
||||
|
||||
public static function validatePrivateKeyContent(string $privateKeyContent): bool
|
||||
{
|
||||
try {
|
||||
$formattedPrivateKey = self::privateKeyFormat($privateKeyContent);
|
||||
$privateKey = self::getPrivateKey($formattedPrivateKey);
|
||||
|
||||
return (bool) $privateKey;
|
||||
} catch (RuntimeException) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static function privateKeyFormat(string $privateKeyContent): string
|
||||
{
|
||||
$pkcs = self::guessPrivateKeyFormat($privateKeyContent);
|
||||
if (is_null($pkcs)) {
|
||||
throw new RuntimeException('不能识别的PKCS格式');
|
||||
}
|
||||
|
||||
$prefix = $pkcs == self::PKCS8 ? self::PKCS8_PRIVATE_KEY_PREFIX : self::PKCS1_PRIVATE_KEY_PREFIX;
|
||||
$suffix = $pkcs == self::PKCS8 ? self::PKCS8_PRIVATE_KEY_SUFFIX : self::PKCS1_PRIVATE_KEY_SUFFIX;
|
||||
|
||||
$privateKeyContent = self::removePrefixAndSuffix($privateKeyContent);
|
||||
|
||||
return $prefix."\n".chunk_split($privateKeyContent, 64).$suffix;
|
||||
}
|
||||
|
||||
private static function guessPrivateKeyFormat(string $key): ?string
|
||||
{
|
||||
// 去掉可能的PEM头尾和换行符
|
||||
$key = self::removePrefixAndSuffix($key);
|
||||
|
||||
// base64 decode
|
||||
$decoded = base64_decode($key, true);
|
||||
if ($decoded === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 判断是否包含 PKCS#8 的 OID 结构
|
||||
// OID for rsaEncryption is 1.2.840.113549.1.1.1
|
||||
$pkcs8OidSequence = hex2bin('300d06092a864886f70d0101010500');
|
||||
|
||||
if (str_contains($decoded, $pkcs8OidSequence)) {
|
||||
return self::PKCS8;
|
||||
}
|
||||
|
||||
// 如果第一个字节是 0x30,后面是 ASN.1 sequence for integer
|
||||
if (str_starts_with($decoded, "\x30")) {
|
||||
// 简单推测 PKCS#1
|
||||
return self::PKCS1;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getPrivateKey(string $privateKeyContent): OpenSSLAsymmetricKey
|
||||
{
|
||||
$privateKey = openssl_pkey_get_private($privateKeyContent);
|
||||
if (! $privateKey) {
|
||||
throw new RuntimeException('非法私钥');
|
||||
}
|
||||
|
||||
return $privateKey;
|
||||
}
|
||||
}
|
||||
5
tests/Feature/ExampleTest.php
Normal file
5
tests/Feature/ExampleTest.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
test('example', function () {
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
47
tests/Pest.php
Normal file
47
tests/Pest.php
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Jltx\Support\Tests\TestCase;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Test Case
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| The closure you provide to your test functions is always bound to a specific PHPUnit test
|
||||
| case class. By default, that class is "PHPUnit\Framework\TestCase". Of course, you may
|
||||
| need to change it using the "pest()" function to bind a different classes or traits.
|
||||
|
|
||||
*/
|
||||
|
||||
pest()->extend(TestCase::class)->in('Unit', 'Feature');
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Expectations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When you're writing tests, you often need to check that values meet certain conditions. The
|
||||
| "expect()" function gives you access to a set of "expectations" methods that you can use
|
||||
| to assert different things. Of course, you may extend the Expectation API at any time.
|
||||
|
|
||||
*/
|
||||
|
||||
expect()->extend('toBeOne', function () {
|
||||
return $this->toBe(1);
|
||||
});
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Functions
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
|
||||
| project that you don't want to repeat in every file. Here you can also expose helpers as
|
||||
| global functions to help you to reduce the number of lines of code in your test files.
|
||||
|
|
||||
*/
|
||||
|
||||
function something()
|
||||
{
|
||||
// ..
|
||||
}
|
||||
10
tests/TestCase.php
Normal file
10
tests/TestCase.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Jltx\Support\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase as BaseTestCase;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
//
|
||||
}
|
||||
5
tests/Unit/ExampleTest.php
Normal file
5
tests/Unit/ExampleTest.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
test('example', function () {
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
124
tests/Unit/RsaTest.php
Normal file
124
tests/Unit/RsaTest.php
Normal file
@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Jltx\Support\Tests\Unit;
|
||||
|
||||
use Jltx\Support\Security\RSA;
|
||||
use RuntimeException;
|
||||
|
||||
beforeEach(function () {
|
||||
// 生成测试用的密钥对
|
||||
$this->keyPair = RSA::generateKeyPair();
|
||||
$this->privateKeyContent = $this->keyPair['private_key'];
|
||||
$this->publicKeyContent = $this->keyPair['public_key'];
|
||||
|
||||
// 格式化密钥
|
||||
$this->formattedPrivateKey = RSA::privateKeyFormat($this->privateKeyContent);
|
||||
$this->formattedPublicKey = RSA::publicKeyFormat($this->publicKeyContent);
|
||||
|
||||
// 获取密钥资源
|
||||
$this->privateKey = RSA::getPrivateKey($this->formattedPrivateKey);
|
||||
$this->publicKey = RSA::getPublicKey($this->formattedPublicKey);
|
||||
});
|
||||
|
||||
it('can generate a key pair', function () {
|
||||
$keyPair = RSA::generateKeyPair();
|
||||
|
||||
expect($keyPair)->toBeArray()
|
||||
->and($keyPair)->toHaveKey('private_key')
|
||||
->and($keyPair)->toHaveKey('public_key')
|
||||
->and($keyPair['private_key'])->toBeString()
|
||||
->and($keyPair['public_key'])->toBeString();
|
||||
});
|
||||
|
||||
it('can format private key', function () {
|
||||
$formatted = RSA::privateKeyFormat($this->privateKeyContent);
|
||||
|
||||
expect($formatted)->toContain('PRIVATE KEY');
|
||||
});
|
||||
|
||||
it('can format public key', function () {
|
||||
$formatted = RSA::publicKeyFormat($this->publicKeyContent);
|
||||
|
||||
expect($formatted)->toContain('PUBLIC KEY');
|
||||
});
|
||||
|
||||
it('can get private key resource', function () {
|
||||
$privateKey = RSA::getPrivateKey($this->formattedPrivateKey);
|
||||
|
||||
expect($privateKey)->toBeObject();
|
||||
});
|
||||
|
||||
it('can get public key resource', function () {
|
||||
$publicKey = RSA::getPublicKey($this->formattedPublicKey);
|
||||
|
||||
expect($publicKey)->toBeObject();
|
||||
});
|
||||
|
||||
it('can encrypt and decrypt data', function () {
|
||||
$plaintext = 'Hello, World!';
|
||||
|
||||
$encrypted = RSA::encrypt($plaintext, $this->publicKey);
|
||||
$decrypted = RSA::decrypt($encrypted, $this->privateKey);
|
||||
|
||||
expect($encrypted)->toBeString()
|
||||
->and($decrypted)->toBeString()
|
||||
->and($decrypted)->toEqual($plaintext);
|
||||
});
|
||||
|
||||
it('can sign and verify data', function () {
|
||||
$message = 'Hello, World!';
|
||||
$algorithm = OPENSSL_ALGO_SHA256;
|
||||
|
||||
$signature = RSA::sign($message, $this->privateKey, $algorithm);
|
||||
$isValid = RSA::verify($message, $signature, $this->publicKey, $algorithm);
|
||||
|
||||
expect($signature)->toBeString()
|
||||
->and($isValid)->toBeTrue();
|
||||
});
|
||||
|
||||
it('returns false for invalid signature', function () {
|
||||
$message = 'Hello, World!';
|
||||
$fakeMessage = 'Fake Message';
|
||||
$algorithm = OPENSSL_ALGO_SHA256;
|
||||
|
||||
$signature = RSA::sign($message, $this->privateKey, $algorithm);
|
||||
$isValid = RSA::verify($fakeMessage, $signature, $this->publicKey, $algorithm);
|
||||
|
||||
expect($isValid)->toBeFalse();
|
||||
});
|
||||
|
||||
it('validates private key content', function () {
|
||||
$isValid = RSA::validatePrivateKeyContent($this->privateKeyContent);
|
||||
|
||||
expect($isValid)->toBeTrue();
|
||||
});
|
||||
|
||||
it('validates invalid private key content as false', function () {
|
||||
$isValid = RSA::validatePrivateKeyContent('invalid private key');
|
||||
|
||||
expect($isValid)->toBeFalse();
|
||||
});
|
||||
|
||||
it('validates public key content', function () {
|
||||
$isValid = RSA::validatePublicKeyContent($this->publicKeyContent);
|
||||
|
||||
expect($isValid)->toBeTrue();
|
||||
});
|
||||
|
||||
it('validates invalid public key content as false', function () {
|
||||
$isValid = RSA::validatePublicKeyContent('invalid public key');
|
||||
|
||||
expect($isValid)->toBeFalse();
|
||||
});
|
||||
|
||||
it('throws exception for invalid private key', function () {
|
||||
$invalidKey = "-----BEGIN PRIVATE KEY-----\ninvalidcontent\n-----END PRIVATE KEY-----";
|
||||
|
||||
expect(fn () => RSA::getPrivateKey($invalidKey))->toThrow(RuntimeException::class, '非法私钥');
|
||||
});
|
||||
|
||||
it('throws exception for invalid public key', function () {
|
||||
$invalidKey = "-----BEGIN PUBLIC KEY-----\ninvalidcontent\n-----END PUBLIC KEY-----";
|
||||
|
||||
expect(fn () => RSA::getPublicKey($invalidKey))->toThrow(RuntimeException::class, '非法公钥');
|
||||
});
|
||||
7
tests/Unit/SimpleTest.php
Normal file
7
tests/Unit/SimpleTest.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Jltx\Support\Tests\Unit;
|
||||
|
||||
test('simple test', function () {
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user