72 lines
2.6 KiB
PHP
72 lines
2.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Apoloz\AlfaBank\Services;
|
|
|
|
use Apoloz\AlfaBank\Contracts\SignerInterface;
|
|
use Apoloz\AlfaBank\Exceptions\SignerException;
|
|
|
|
class OpenSslCmsSigner implements SignerInterface
|
|
{
|
|
public function __construct(
|
|
protected string $certificatePath,
|
|
protected string $privateKeyPath
|
|
) {}
|
|
|
|
public function sign(string $data): string
|
|
{
|
|
if (!file_exists($this->privateKeyPath)) {
|
|
throw new SignerException("Private key file not found: {$this->privateKeyPath}");
|
|
}
|
|
if (!file_exists($this->certificatePath)) {
|
|
throw new SignerException("Certificate file not found: {$this->certificatePath}");
|
|
}
|
|
|
|
$privateKey = openssl_pkey_get_private(file_get_contents($this->privateKeyPath));
|
|
$cert = openssl_x509_read(file_get_contents($this->certificatePath));
|
|
|
|
if (!$privateKey || !$cert) {
|
|
throw new SignerException("Could not read private key or certificate for signing.");
|
|
}
|
|
|
|
$inFile = tempnam(sys_get_temp_dir(), 'alfa_in');
|
|
$outFile = tempnam(sys_get_temp_dir(), 'alfa_out');
|
|
|
|
file_put_contents($inFile, $data);
|
|
|
|
// Используем PKCS7_DETACHED согласно документации Альфа-Банка для digestSignatures
|
|
$status = openssl_pkcs7_sign(
|
|
$inFile,
|
|
$outFile,
|
|
$cert,
|
|
$privateKey,
|
|
[],
|
|
PKCS7_BINARY | PKCS7_DETACHED
|
|
);
|
|
|
|
if (!$status) {
|
|
@unlink($inFile);
|
|
@unlink($outFile);
|
|
throw new SignerException("OpenSSL sign failed: " . openssl_error_string());
|
|
}
|
|
|
|
$signedData = file_get_contents($outFile);
|
|
@unlink($inFile);
|
|
@unlink($outFile);
|
|
|
|
// Надежно извлекаем только Base64 блок подписи из S/MIME или PEM структуры
|
|
$parts = explode("\n\n", $signedData);
|
|
foreach ($parts as $part) {
|
|
// Очищаем часть от пробелов и переносов
|
|
$cleaned = str_replace(["\n", "\r", ' '], '', $part);
|
|
// Ищем блок, который выглядит как длинный Base64 (больше 128 символов) и не содержит S/MIME заголовков
|
|
if (preg_match('/^[a-zA-Z0-9\/+]+=*$/', $cleaned) && strlen($cleaned) > 128) {
|
|
return $cleaned;
|
|
}
|
|
}
|
|
|
|
throw new SignerException("Failed to extract signature body from CMS container. Output: " . substr($signedData, 0, 100));
|
|
}
|
|
}
|