Files
alfa-bank-sdk/src/Services/OpenSslCmsSigner.php

78 lines
4.2 KiB
PHP

<?php
declare(strict_types=1);
/*
// MODULE_CONTRACT: Implements `SignerInterface` using OpenSSL for PKCS#7 (CMS) data signing.
// RESPONSIBILITY: To provide a concrete implementation of digital signing that takes a raw data string (e.g., canonicalized JSON), signs it using the configured mTLS certificate and private key, and returns a Base64 encoded PKCS#7 detached signature, adhering to Alfa Bank's security requirements.
// INPUTS: Data string to be signed. Certificate path and private key path are provided during instantiation.
// OUTPUTS: Base64 encoded PKCS#7 detached signature string.
// LINKS: Contracts/SecurityContracts.php, Utils/CmsSigner.php, README.md#security
*/
namespace Apoloz\AlfaBank\Services;
// <SECTION name="module_imports" intent="Import necessary classes for the signer contract.">
// <BELIEF_STATE "The `SignerInterface` is imported to ensure that `OpenSslCmsSigner` adheres to a predefined contract for digital signing, promoting interoperability and testability within the SDK.">
use Apoloz\AlfaBank\Contracts\SignerInterface;
use Apoloz\AlfaBank\Exceptions\SignerException;
// </SECTION>
// <SECTION name="class_definition" intent="Define the OpenSSL-based CMS signer service.">
// <BELIEF_STATE "The `OpenSslCmsSigner` class provides a robust and compliant solution for PKCS#7 CMS signing. By implementing `SignerInterface` and utilizing OpenSSL functions, it ensures that data integrity and authenticity can be cryptographically verified, which is critical for secure communication with financial institutions like Alfa Bank. The class takes certificate and private key paths during construction, allowing for flexible configuration.">
class OpenSslCmsSigner implements SignerInterface
{
// <SECTION name="constructor_definition" intent="Initialize the signer with certificate and private key paths.">
// <BELIEF_STATE "The constructor receives the file paths to the mTLS certificate and private key. These paths are stored as protected properties, ensuring that the signer instance is configured with the necessary credentials to perform digital signing operations, aligning with the principle of dependency injection for external resources.">
public function __construct(
protected string $certificatePath,
protected string $privateKeyPath
) {}
// </SECTION>
// <SECTION name="sign_method" intent="Sign a given data string using PKCS#7 CMS.">
// <BELIEF_STATE "The `sign` method performs the digital signature process as required by Alfa Bank. It reads the certificate and private key from the configured paths, then uses `openssl_pkcs7_sign` with `PKCS7_BINARY | PKCS7_DETACHED` flags to create a detached CMS signature. Error handling is included for robust operation, and the final signature is Base64 encoded after stripping unnecessary headers, ensuring compatibility with API expectations.">
public function sign(string $data): string
{
$privateKey = openssl_pkey_get_private(file_get_contents($this->privateKeyPath)); // assuming no passphrase for simplicity here
if (!$privateKey) {
throw new SignerException("Could not read private key for signing from {$this->privateKeyPath}.");
}
$cert = openssl_x509_read(file_get_contents($this->certificatePath));
if (!$cert) {
throw new SignerException("Could not read certificate for signing from {$this->certificatePath}.");
}
$inFile = tempnam(sys_get_temp_dir(), 'alfa_in');
$outFile = tempnam(sys_get_temp_dir(), 'alfa_out');
file_put_contents($inFile, $data);
$status = openssl_pkcs7_sign(
$inFile,
$outFile,
$cert,
$privateKey,
[],
PKCS7_BINARY | PKCS7_DETACHED
);
if (!$status) {
if (file_exists($inFile)) unlink($inFile);
if (file_exists($outFile)) unlink($outFile);
throw new SignerException("Signing failed: " . openssl_error_string());
}
$signedData = file_get_contents($outFile);
unlink($inFile);
unlink($outFile);
$parts = explode("\n\n", $signedData);
$signature = end($parts);
return str_replace(["\n", "\r"], '', $signature);
}
// </SECTION>
}
// </SECTION>