mirror of
https://github.com/crypto-pro-web/crypto-pro-php.git
synced 2025-04-19 11:53:07 +03:00

* Переименовал класс с константами CADESCOM_ENCODE в CADESCOM_ENCODING_TYPE * Добавил класс с константами CAPICOM_STORE_OPEN_MODE * Добавил метод получения информации о криптопровайдере и плагине * Добавил метод проверки соответствия установленных версий криптопровайдера и плагина * Исправил используемые в классе константы
1118 lines
34 KiB
PHP
Executable File
1118 lines
34 KiB
PHP
Executable File
<?php
|
||
|
||
namespace Webmasterskaya\CryptoPro;
|
||
|
||
use Webmasterskaya\CryptoPro\Constants\CADESCOM_ATTRIBUTE;
|
||
use Webmasterskaya\CryptoPro\Constants\CADESCOM_CADES_TYPE;
|
||
use Webmasterskaya\CryptoPro\Constants\CADESCOM_CONTENT_ENCODING_TYPE;
|
||
use Webmasterskaya\CryptoPro\Constants\CADESCOM_ENCODING_TYPE;
|
||
use Webmasterskaya\CryptoPro\Constants\CADESCOM_HASH_ALGORITHM;
|
||
use Webmasterskaya\CryptoPro\Constants\CADESCOM_STORE_LOCATION;
|
||
use Webmasterskaya\CryptoPro\Constants\CAPICOM_CERTIFICATE_FIND_TYPE;
|
||
use Webmasterskaya\CryptoPro\Constants\CAPICOM_CERTIFICATE_INCLUDE_OPTION;
|
||
use Webmasterskaya\CryptoPro\Constants\CAPICOM_PROPID;
|
||
use Webmasterskaya\CryptoPro\Constants\CAPICOM_STORE_OPEN_MODE;
|
||
use Webmasterskaya\CryptoPro\Helpers\CertificateHelper;
|
||
use Webmasterskaya\CryptoPro\Helpers\ErrorMessageHelper;
|
||
|
||
class CryptoPro
|
||
{
|
||
protected const CP_MY_STORE = 'My';
|
||
|
||
/**
|
||
* Возвращает список сертификатов, доступных пользователю в системе
|
||
*
|
||
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
||
*
|
||
* @throws \Exception
|
||
* @return Certificate[]
|
||
*/
|
||
public static function getUserCertificates(bool $resetCache = false)
|
||
{
|
||
static $certificates;
|
||
|
||
if ($resetCache === true || !isset($certificates))
|
||
{
|
||
$certificates = self::getCertificatesFromStore(CADESCOM_STORE_LOCATION::CURRENT_USER_STORE);
|
||
}
|
||
|
||
return $certificates;
|
||
}
|
||
|
||
/**
|
||
* Возвращает список сертификатов, доступных пользователю в системе, в том числе просроченные и без закрытого ключа
|
||
*
|
||
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
||
*
|
||
* @throws \Exception
|
||
* @return Certificate[]
|
||
*/
|
||
public static function getAllUserCertificates(bool $resetCache = false)
|
||
{
|
||
static $certificates;
|
||
|
||
if ($resetCache === true || !isset($certificates))
|
||
{
|
||
$certificates = self::getCertificatesFromStore(
|
||
CADESCOM_STORE_LOCATION::CURRENT_USER_STORE,
|
||
static::CP_MY_STORE,
|
||
false
|
||
);
|
||
}
|
||
|
||
return $certificates;
|
||
}
|
||
|
||
/**
|
||
* Возвращает список сертификатов, из закрытых ключей и/или сертификаты не установленные всистеме*
|
||
*
|
||
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
||
*
|
||
* @throws \Exception
|
||
* @return Certificate[]
|
||
*/
|
||
public static function getContainerCertificates(bool $resetCache = false)
|
||
{
|
||
static $certificates;
|
||
|
||
if ($resetCache === true || !isset($certificates))
|
||
{
|
||
$certificates = self::getCertificatesFromStore(CADESCOM_STORE_LOCATION::CONTAINER_STORE);
|
||
}
|
||
|
||
return $certificates;
|
||
}
|
||
|
||
/**
|
||
* Возвращает список сертификатов, из закрытых ключей и/или сертификаты не установленные всистеме*, в том числе просроченные и без закрытого ключа
|
||
*
|
||
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
||
*
|
||
* @throws \Exception
|
||
* @return Certificate[]
|
||
*/
|
||
public static function getAllContainerCertificates(bool $resetCache = false)
|
||
{
|
||
static $certificates;
|
||
|
||
if ($resetCache === true || !isset($certificates))
|
||
{
|
||
$certificates = self::getCertificatesFromStore(
|
||
CADESCOM_STORE_LOCATION::CONTAINER_STORE,
|
||
static::CP_MY_STORE,
|
||
false
|
||
);
|
||
}
|
||
|
||
return $certificates;
|
||
}
|
||
|
||
/**
|
||
* Возвращает список сертификатов, доступных пользователю из пользовательского хранилища и закрытых ключей, не установленных в системе
|
||
*
|
||
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
||
*
|
||
* @return Certificate[]
|
||
*/
|
||
public static function getCertificates(bool $resetCache = false)
|
||
{
|
||
static $certificates;
|
||
|
||
if ($resetCache === true || !isset($certificates))
|
||
{
|
||
$availableCertificates = [];
|
||
try
|
||
{
|
||
$availableCertificates = self::getUserCertificates($resetCache);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
// do nothing
|
||
}
|
||
|
||
$containerCertificates = [];
|
||
try
|
||
{
|
||
$containerCertificates = self::getContainerCertificates($resetCache);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
// do nothing
|
||
}
|
||
|
||
self::mergeToAvailableCertificates($availableCertificates, $containerCertificates);
|
||
|
||
$certificates = $availableCertificates;
|
||
}
|
||
|
||
return $certificates;
|
||
}
|
||
|
||
/**
|
||
* Возвращает список сертификатов, доступных пользователю из пользовательского хранилища и закрытых ключей,
|
||
* не установленных в системе, без фильтрации по дате и наличию приватного ключа
|
||
*
|
||
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
||
*
|
||
* @return Certificate[]
|
||
*/
|
||
public static function getAllCertificates(bool $resetCache = false)
|
||
{
|
||
static $certificates;
|
||
|
||
if ($resetCache === true || !isset($certificates))
|
||
{
|
||
$availableCertificates = [];
|
||
try
|
||
{
|
||
$availableCertificates = self::getAllUserCertificates($resetCache);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
// do nothing
|
||
}
|
||
|
||
$containerCertificates = [];
|
||
try
|
||
{
|
||
$containerCertificates = self::getAllContainerCertificates($resetCache);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
// do nothing
|
||
}
|
||
|
||
self::mergeToAvailableCertificates($availableCertificates, $containerCertificates);
|
||
|
||
$certificates = $availableCertificates;
|
||
}
|
||
|
||
return $certificates;
|
||
}
|
||
|
||
/**
|
||
* Возвращает сертификат по отпечатку
|
||
*
|
||
* @param string $thumbprint отпечаток сертификата
|
||
*
|
||
* @throws \Exception
|
||
* @return Certificate
|
||
*/
|
||
public static function getCertificate(string $thumbprint)
|
||
{
|
||
$thumbprint = mb_strtoupper($thumbprint);
|
||
$thumbprint = trim($thumbprint);
|
||
|
||
if (!$thumbprint)
|
||
{
|
||
throw new \Exception('Отпечаток не указан');
|
||
}
|
||
|
||
$certificates = self::getAllCertificates();
|
||
|
||
$found = false;
|
||
|
||
foreach ($certificates as $certificate)
|
||
{
|
||
if ($certificate->thumbprint === $thumbprint)
|
||
{
|
||
$found = $certificate;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ($found === false)
|
||
{
|
||
throw new \Exception('Сертификат с отпечатком: "' . $thumbprint . '" не найден');
|
||
}
|
||
|
||
return $found;
|
||
}
|
||
|
||
/**
|
||
* Создает совмещенную (присоединенную) подпись сообщения
|
||
*
|
||
* @param string $thumbprint отпечаток сертификата
|
||
* @param string $unencryptedMessage подписываемое сообщение
|
||
* @param string|null $pin пин-код доступа к закрытому ключу
|
||
*
|
||
* @throws \Exception
|
||
* @return string подпись в формате PKCS#7
|
||
*/
|
||
public static function createAttachedSignature(string $thumbprint, string $unencryptedMessage, string $pin = null)
|
||
{
|
||
/** @noinspection DuplicatedCode */
|
||
$cadesCertificate = self::getCadesCertificate($thumbprint);
|
||
|
||
try
|
||
{
|
||
$cadesAttrs = new \CPAttribute();
|
||
$cadesSignedData = new \CPSignedData();
|
||
$cadesSigner = new \CPSigner();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации подписи'));
|
||
}
|
||
|
||
// Дату и время устанавливаем в формате generalizedTime https://docs.cryptopro.ru/pki/cplib/class/cdatetime?id=cdatetime-1
|
||
$currentDateTime = (new \DateTime())->format('YmdHis.u') . 'Z';
|
||
|
||
try
|
||
{
|
||
$cadesAttrs->set_Name(CADESCOM_ATTRIBUTE::SIGNING_TIME);
|
||
$cadesAttrs->set_Value($currentDateTime);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке времени подписи'));
|
||
}
|
||
|
||
$messageBase64 = base64_encode($unencryptedMessage);
|
||
|
||
try
|
||
{
|
||
if (!empty($pin))
|
||
{
|
||
$cadesSigner->set_KeyPin($pin);
|
||
}
|
||
|
||
$cadesSigner->set_Certificate($cadesCertificate);
|
||
|
||
$cadesAuthAttrs = $cadesSigner->get_AuthenticatedAttributes();
|
||
$cadesAuthAttrs->Add($cadesAttrs);
|
||
|
||
$cadesSignedData->set_ContentEncoding(CADESCOM_CONTENT_ENCODING_TYPE::BASE64_TO_BINARY);
|
||
$cadesSignedData->set_Content($messageBase64);
|
||
|
||
$cadesSigner->set_Options(CAPICOM_CERTIFICATE_INCLUDE_OPTION::WHOLE_CHAIN);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при указании данных для подписи'));
|
||
}
|
||
|
||
try
|
||
{
|
||
/** @var string $signature */
|
||
$signature = $cadesSignedData->SignCades(
|
||
$cadesSigner,
|
||
CADESCOM_CADES_TYPE::PKCS7_TYPE,
|
||
false,
|
||
CADESCOM_ENCODING_TYPE::BASE64
|
||
);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при подписании данных'));
|
||
}
|
||
|
||
return $signature;
|
||
}
|
||
|
||
/**
|
||
* Создает отсоединенную (открепленную) подпись сообщения
|
||
*
|
||
* @param string $thumbprint отпечаток сертификата
|
||
* @param string $messageHash хеш подписываемого сообщения, сгенерированный по ГОСТ Р 34.11-2012 256 бит
|
||
* @param string|null $pin пин-код доступа к закрытому ключу
|
||
*
|
||
* @throws \Exception
|
||
* @return string подпись в формате PKCS#7
|
||
*/
|
||
public static function createDetachedSignature(string $thumbprint, string $messageHash, string $pin = null)
|
||
{
|
||
/** @noinspection DuplicatedCode */
|
||
$cadesCertificate = self::getCadesCertificate($thumbprint);
|
||
|
||
try
|
||
{
|
||
$cadesAttrs = new \CPAttribute();
|
||
$cadesSignedData = new \CPSignedData();
|
||
$cadesHashedData = new \CPHashedData();
|
||
$cadesSigner = new \CPSigner();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации подписи'));
|
||
}
|
||
|
||
// Дату и время устанавливаем в формате generalizedTime https://docs.cryptopro.ru/pki/cplib/class/cdatetime?id=cdatetime-1
|
||
$currentDateTime = (new \DateTime())->format('YmdHis.u') . 'Z';
|
||
|
||
try
|
||
{
|
||
$cadesAttrs->set_Name(CADESCOM_ATTRIBUTE::SIGNING_TIME);
|
||
$cadesAttrs->set_Value($currentDateTime);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке времени подписи'));
|
||
}
|
||
|
||
try
|
||
{
|
||
if (!empty($pin))
|
||
{
|
||
$cadesSigner->set_KeyPin($pin);
|
||
}
|
||
|
||
$cadesSigner->set_Certificate($cadesCertificate);
|
||
|
||
$cadesAuthAttrs = $cadesSigner->get_AuthenticatedAttributes();
|
||
$cadesAuthAttrs->Add($cadesAttrs);
|
||
|
||
$cadesSigner->set_Options(CAPICOM_CERTIFICATE_INCLUDE_OPTION::WHOLE_CHAIN);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке сертификата'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesHashedData->set_Algorithm(CADESCOM_HASH_ALGORITHM::HASH_CP_GOST_3411_2012_256);
|
||
$cadesHashedData->set_DataEncoding(CADESCOM_CONTENT_ENCODING_TYPE::BASE64_TO_BINARY);
|
||
$cadesHashedData->SetHashValue($messageHash);
|
||
|
||
$cadesSignedData->set_ContentEncoding(CADESCOM_CONTENT_ENCODING_TYPE::BASE64_TO_BINARY);
|
||
// Для получения объекта отсоединенной (открепленной) подписи, необходимо задать любой контент.
|
||
// Этот баг описан на форуме.
|
||
// https://www.cryptopro.ru/forum2/default.aspx?g=posts&m=78553#post78553
|
||
$cadesSignedData->set_Content(123);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке хеша'));
|
||
}
|
||
|
||
try
|
||
{
|
||
/** @var string $signature */
|
||
$signature = $cadesSignedData->SignHash(
|
||
$cadesHashedData,
|
||
$cadesSigner,
|
||
CADESCOM_CADES_TYPE::PKCS7_TYPE,
|
||
CADESCOM_ENCODING_TYPE::BASE64
|
||
);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при подписании данных'));
|
||
}
|
||
|
||
return $signature;
|
||
}
|
||
|
||
/**
|
||
* добавляет совмещенную (присоединенную) подпись к раннее подписанному документу (реализует метод coSign)
|
||
*
|
||
* @param string $thumbprint отпечаток сертификата
|
||
* @param string $signedMessage
|
||
* @param string|null $pin пин-код доступа к закрытому ключу
|
||
*
|
||
* @throws \Exception
|
||
* @return string
|
||
*/
|
||
public static function addAttachedSignature(string $thumbprint, string $signedMessage, string $pin = null)
|
||
{
|
||
/** @noinspection DuplicatedCode */
|
||
$cadesCertificate = self::getCadesCertificate($thumbprint);
|
||
|
||
try
|
||
{
|
||
$cadesAttrs = new \CPAttribute();
|
||
$cadesSignedData = new \CPSignedData();
|
||
$cadesSigner = new \CPSigner();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации подписи'));
|
||
}
|
||
|
||
// Дату и время устанавливаем в формате generalizedTime https://docs.cryptopro.ru/pki/cplib/class/cdatetime?id=cdatetime-1
|
||
$currentDateTime = (new \DateTime())->format('YmdHis.u') . 'Z';
|
||
|
||
try
|
||
{
|
||
$cadesAttrs->set_Name(CADESCOM_ATTRIBUTE::SIGNING_TIME);
|
||
$cadesAttrs->set_Value($currentDateTime);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке времени подписи'));
|
||
}
|
||
|
||
$messageBase64 = base64_encode($signedMessage);
|
||
|
||
try
|
||
{
|
||
if (!empty($pin))
|
||
{
|
||
$cadesSigner->set_KeyPin($pin);
|
||
}
|
||
|
||
$cadesSigner->set_Certificate($cadesCertificate);
|
||
|
||
$cadesAuthAttrs = $cadesSigner->get_AuthenticatedAttributes();
|
||
$cadesAuthAttrs->Add($cadesAttrs);
|
||
|
||
$cadesSignedData->set_ContentEncoding(CADESCOM_CONTENT_ENCODING_TYPE::BASE64_TO_BINARY);
|
||
$cadesSignedData->set_Content($messageBase64);
|
||
|
||
$cadesSigner->set_Options(CAPICOM_CERTIFICATE_INCLUDE_OPTION::WHOLE_CHAIN);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при указании данных для подписи'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesSignedData->VerifyCades(
|
||
$signedMessage,
|
||
CADESCOM_CADES_TYPE::PKCS7_TYPE,
|
||
false
|
||
);
|
||
|
||
$signature = $cadesSignedData->CoSignCades(
|
||
$cadesSigner,
|
||
CADESCOM_CADES_TYPE::PKCS7_TYPE,
|
||
CADESCOM_ENCODING_TYPE::BASE64
|
||
);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при подписании данных'));
|
||
}
|
||
|
||
return $signature;
|
||
}
|
||
|
||
/**
|
||
* Добавляет отсоединенную (открепленную) подпись к раннее подписанному документу (реализует метод coSign)
|
||
*
|
||
* @param string $thumbprint отпечаток сертификата
|
||
* @param string $signedMessage подписанное сообщение
|
||
* @param string $messageHash хеш подписываемого сообщения, сгенерированный по ГОСТ Р 34.11-2012 256 бит
|
||
* @param string|null $pin пин-код доступа к закрытому ключу
|
||
*
|
||
* @throws \Exception
|
||
* @return string подпись в формате PKCS#7
|
||
*/
|
||
public static function addDetachedSignature(string $thumbprint, string $signedMessage, string $messageHash, string $pin = null)
|
||
{
|
||
/** @noinspection DuplicatedCode */
|
||
$cadesCertificate = self::getCadesCertificate($thumbprint);
|
||
|
||
try
|
||
{
|
||
$cadesAttrs = new \CPAttribute();
|
||
$cadesSignedData = new \CPSignedData();
|
||
$cadesHashedData = new \CPHashedData();
|
||
$cadesSigner = new \CPSigner();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации подписи'));
|
||
}
|
||
|
||
// Дату и время устанавливаем в формате generalizedTime https://docs.cryptopro.ru/pki/cplib/class/cdatetime?id=cdatetime-1
|
||
$currentDateTime = (new \DateTime())->format('YmdHis.u') . 'Z';
|
||
|
||
try
|
||
{
|
||
$cadesAttrs->set_Name(CADESCOM_ATTRIBUTE::SIGNING_TIME);
|
||
$cadesAttrs->set_Value($currentDateTime);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке времени подписи'));
|
||
}
|
||
|
||
try
|
||
{
|
||
if (!empty($pin))
|
||
{
|
||
$cadesSigner->set_KeyPin($pin);
|
||
}
|
||
|
||
$cadesSigner->set_Certificate($cadesCertificate);
|
||
|
||
$cadesAuthAttrs = $cadesSigner->get_AuthenticatedAttributes();
|
||
$cadesAuthAttrs->Add($cadesAttrs);
|
||
|
||
$cadesSigner->set_Options(CAPICOM_CERTIFICATE_INCLUDE_OPTION::WHOLE_CHAIN);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке сертификата'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesHashedData->set_Algorithm(CADESCOM_HASH_ALGORITHM::HASH_CP_GOST_3411_2012_256);
|
||
$cadesHashedData->set_DataEncoding(CADESCOM_CONTENT_ENCODING_TYPE::BASE64_TO_BINARY);
|
||
$cadesHashedData->SetHashValue($messageHash);
|
||
|
||
$cadesSignedData->set_ContentEncoding(CADESCOM_CONTENT_ENCODING_TYPE::BASE64_TO_BINARY);
|
||
// Для получения объекта отсоединенной (открепленной) подписи, необходимо задать любой контент.
|
||
// Этот баг описан на форуме.
|
||
// https://www.cryptopro.ru/forum2/default.aspx?g=posts&m=78553#post78553
|
||
$cadesSignedData->set_Content(123);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке хеша'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesSignedData->VerifyHash(
|
||
$cadesHashedData,
|
||
$signedMessage,
|
||
CADESCOM_CADES_TYPE::PKCS7_TYPE
|
||
);
|
||
|
||
$signature = $cadesSignedData->CoSignHash(
|
||
$cadesSigner,
|
||
$cadesHashedData,
|
||
CADESCOM_CADES_TYPE::PKCS7_TYPE,
|
||
CADESCOM_ENCODING_TYPE::BASE64
|
||
);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при подписании данных'));
|
||
}
|
||
|
||
return $signature;
|
||
}
|
||
|
||
/**
|
||
* Создает хеш сообщения по ГОСТ Р 34.11-2012 256 бит
|
||
*
|
||
* @param string $unencryptedMessage сообщение для хеширования
|
||
*
|
||
* @throws \Exception
|
||
* @return string
|
||
*/
|
||
public static function createHash(string $unencryptedMessage)
|
||
{
|
||
try
|
||
{
|
||
$cadesHashedData = new \CPHashedData();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации хэширования'));
|
||
}
|
||
|
||
$messageBase64 = base64_encode($unencryptedMessage);
|
||
|
||
try
|
||
{
|
||
$cadesHashedData->set_Algorithm(CADESCOM_HASH_ALGORITHM::HASH_CP_GOST_3411_2012_256);
|
||
$cadesHashedData->set_DataEncoding(CADESCOM_CONTENT_ENCODING_TYPE::BASE64_TO_BINARY);
|
||
$cadesHashedData->Hash($messageBase64);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке хэширования'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$hash = $cadesHashedData->get_Value();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при создании хэша'));
|
||
}
|
||
|
||
return $hash;
|
||
}
|
||
|
||
/**
|
||
* Возвращает информацию о CSP и плагине
|
||
*
|
||
* @throws \Exception
|
||
* @return array
|
||
*/
|
||
public static function getSystemInfo()
|
||
{
|
||
try
|
||
{
|
||
$about = new \About();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при получении информации о системе'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesVersion = $about->PluginVersion();
|
||
|
||
if ($cadesVersion instanceof \Version)
|
||
{
|
||
$cadesVersion = $cadesVersion->toString();
|
||
}
|
||
|
||
if (!$cadesVersion)
|
||
{
|
||
$cadesVersion = $about->get_Version();
|
||
}
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при получении информации о плагине'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cspVersion = $about->CSPVersion();
|
||
$cspVersion = $cspVersion->toString();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при получении информации о CSP'));
|
||
}
|
||
|
||
return [
|
||
'cadesVersion' => $cadesVersion,
|
||
'cspVersion' => $cspVersion,
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Проверяет корректность настроек средств ЭП
|
||
*
|
||
* @throws \Exception
|
||
* @return true
|
||
*/
|
||
public static function isValidSystemSetup()
|
||
{
|
||
$systemInfo = static::getSystemInfo();
|
||
|
||
$extractedCadesVersion = [];
|
||
|
||
if (!preg_match('/(\d+)\.(\d+)\.(\d+)/', $systemInfo['cadesVersion'], $extractedCadesVersion))
|
||
{
|
||
throw new \Exception('Ошибка чтеня версии плагина');
|
||
}
|
||
|
||
list(, $cadesVersionMajor, $cadesVersionMinor, $cadesVersionPatch) = $extractedCadesVersion;
|
||
|
||
if ((int) $cadesVersionMajor < 2
|
||
|| ((int) $cadesVersionMajor === 2 && (int) $cadesVersionMinor === 0 && (int) $cadesVersionPatch < 12438))
|
||
{
|
||
throw new \Exception('Не поддерживаемая версия плагина');
|
||
}
|
||
|
||
if (!preg_match('/(\d+)\.(\d+)\.(\d+)/', $systemInfo['cspVersion'], $extractedCSPVersion))
|
||
{
|
||
throw new \Exception('Ошибка чтеня версии CSP');
|
||
}
|
||
|
||
list(, $cspVersionMajor, $cspVersionMinor, $cspVersionPatch) = $extractedCSPVersion;
|
||
|
||
if ((int) $cspVersionMajor < 4)
|
||
{
|
||
throw new \Exception('Не поддерживаемая версия CSP');
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @param int $storeLocation
|
||
* @param string $storeName
|
||
* @param bool $validOnly Логическое значение , указывающее, возвращаются ли только действительные сертификаты.
|
||
* Если значение равно true, поиск не вернет следующие типы сертификатов:
|
||
* <ul>
|
||
* <li>
|
||
* Сертификаты, срок действия которых истек или еще не действителен.
|
||
* </li>
|
||
* <li>
|
||
* Сертификаты, в которых отсутствует закрытый ключ.
|
||
* </li>
|
||
* <li>
|
||
* Сертификаты не связаны должным образом.
|
||
* </li>
|
||
* <li>
|
||
* Сертификаты, у которых возникли проблемы с подписью.
|
||
* </li>
|
||
* <li>
|
||
* Отозванные сертификаты.
|
||
* </li>
|
||
* </ul>
|
||
*
|
||
* @throws \Exception
|
||
* @return array
|
||
*/
|
||
protected static function getCertificatesFromStore(int $storeLocation, string $storeName = 'My', bool $validOnly = true)
|
||
{
|
||
$certificates = [];
|
||
|
||
try
|
||
{
|
||
$cadesStore = new \CPStore();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при попытке доступа к хранилищу'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesStore->Open($storeLocation, $storeName, CAPICOM_STORE_OPEN_MODE::MAXIMUM_ALLOWED);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при открытии хранилища'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesCertificates = $cadesStore->get_Certificates();
|
||
|
||
if ($validOnly === true)
|
||
{
|
||
$currentDateTime = (new \DateTime())->format('YmdHis.u') . 'Z';
|
||
|
||
$cadesCertificates = $cadesCertificates->Find(
|
||
CAPICOM_CERTIFICATE_FIND_TYPE::TIME_VALID,
|
||
$currentDateTime,
|
||
true
|
||
);
|
||
|
||
$cadesCertificates = $cadesCertificates->Find(
|
||
CAPICOM_CERTIFICATE_FIND_TYPE::EXTENDED_PROPERTY,
|
||
CAPICOM_PROPID::KEY_PROV_INFO,
|
||
false
|
||
);
|
||
}
|
||
|
||
$cadesCertificatesCount = $cadesCertificates->Count();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка получения списка сертификатов'));
|
||
}
|
||
|
||
if (!$cadesCertificatesCount)
|
||
{
|
||
throw new \Exception('Нет доступных сертификатов');
|
||
}
|
||
|
||
try
|
||
{
|
||
while ($cadesCertificatesCount)
|
||
{
|
||
$cadesCertificate = $cadesCertificates->Item($cadesCertificatesCount);
|
||
|
||
$certificates[] = new Certificate(
|
||
$cadesCertificate,
|
||
CertificateHelper::extractCommonName($cadesCertificate->get_SubjectName()),
|
||
$cadesCertificate->get_IssuerName(),
|
||
$cadesCertificate->get_SubjectName(),
|
||
$cadesCertificate->get_Thumbprint(),
|
||
$cadesCertificate->get_ValidFromDate(),
|
||
$cadesCertificate->get_ValidToDate()
|
||
);
|
||
|
||
$cadesCertificatesCount--;
|
||
}
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка обработки сертификатов'));
|
||
}
|
||
|
||
$cadesStore->Close();
|
||
|
||
return $certificates;
|
||
}
|
||
|
||
/**
|
||
* @param string $thumbprint
|
||
* @param int $storeLocation
|
||
* @param string $storeName
|
||
*
|
||
* @throws \Exception
|
||
* @return \CPCertificate
|
||
*/
|
||
protected static function getCadesCertificateFromStore(string $thumbprint, int $storeLocation, string $storeName = 'My')
|
||
{
|
||
$thumbprint = mb_strtoupper($thumbprint);
|
||
$thumbprint = trim($thumbprint);
|
||
|
||
try
|
||
{
|
||
$cadesStore = new \CPStore();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при попытке доступа к хранилищу'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesStore->Open($storeLocation, $storeName, CAPICOM_STORE_OPEN_MODE::MAXIMUM_ALLOWED);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при открытии хранилища'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesCertificates = $cadesStore->get_Certificates();
|
||
$cadesCertificatesCount = $cadesCertificates->Count();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка получения списка сертификатов из хранилища'));
|
||
}
|
||
|
||
if (!$cadesCertificatesCount)
|
||
{
|
||
throw new \Exception('Нет доступных сертификатов в хранилище');
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesCertificates = $cadesCertificates->Find(
|
||
CAPICOM_CERTIFICATE_FIND_TYPE::SHA1_HASH,
|
||
$thumbprint,
|
||
false
|
||
);
|
||
|
||
$cadesCertificatesCount = $cadesCertificates->Count();
|
||
|
||
if (!$cadesCertificatesCount)
|
||
{
|
||
throw new \Exception('Сертификат с отпечатком: "' . $thumbprint . '" не найден в хранилище');
|
||
}
|
||
|
||
// Считаем, что первый сертификат, найденный по отпечатку - наш
|
||
$cadesCertificate = $cadesCertificates->Item(1);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при получении сертификата из хранилища'));
|
||
}
|
||
|
||
return $cadesCertificate;
|
||
}
|
||
|
||
protected static function mergeToAvailableCertificates(array &$availableCertificates = [], array $mergedCertificates = [])
|
||
{
|
||
if (empty($availableCertificates))
|
||
{
|
||
$availableCertificates = $mergedCertificates;
|
||
}
|
||
else
|
||
{
|
||
if (!empty($mergedCertificates))
|
||
{
|
||
$mergedCertificatesCount = count($mergedCertificates) - 1;
|
||
|
||
while ($mergedCertificatesCount)
|
||
{
|
||
$found = false;
|
||
|
||
$currentCertificate = $mergedCertificates[$mergedCertificatesCount];
|
||
|
||
foreach ($availableCertificates as $certificate)
|
||
{
|
||
if ($certificate->thumbprint === $currentCertificate->thumbprint)
|
||
{
|
||
$found = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if ($found)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
$availableCertificates[] = $currentCertificate;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Возвращает сертификат в формате Cades по отпечатку.
|
||
* Сначала ищет в хранилище пользователя, потм - в хранилище закрытого ключа
|
||
*
|
||
* @param string $thumbprint
|
||
*
|
||
* @throws \Exception
|
||
* @return \CPCertificate
|
||
*/
|
||
protected static function getCadesCertificate(string $thumbprint)
|
||
{
|
||
try
|
||
{
|
||
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CADESCOM_STORE_LOCATION::CURRENT_USER_STORE);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
$previousException = $e;
|
||
|
||
try
|
||
{
|
||
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CADESCOM_STORE_LOCATION::CONTAINER_STORE);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception($e->getMessage(), $e->getCode(), $previousException);
|
||
}
|
||
}
|
||
|
||
return $cadesCertificate;
|
||
}
|
||
|
||
/**
|
||
* Проверяет присоеденённую подпись и возвращает информацию о подписантах
|
||
*
|
||
* @param string $signedMessage подписанное сообщение
|
||
*
|
||
* @throws \Exception
|
||
* @return array Информация о подписантах
|
||
*/
|
||
public static function verifyAttachedSignature(string $signedMessage)
|
||
{
|
||
try
|
||
{
|
||
$cadesSignedData = new \CPSignedData();
|
||
$cadesSignedData->set_Content($signedMessage);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации проверки подписи'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesSignedData->VerifyCades($signedMessage, CADES_BES, false);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при проверке подписи'));
|
||
}
|
||
|
||
return static::getSigners($cadesSignedData);
|
||
}
|
||
|
||
/**
|
||
* Проверяет отсоеденённую подпись по значению хэш-функции и возвращает информацию о подписантах
|
||
*
|
||
* @param string $signedMessage подписанное сообщение
|
||
* @param string $messageHash хеш подписываемого сообщения, сгенерированный по ГОСТ Р 34.11-2012 256 бит
|
||
*
|
||
* @throws \Exception
|
||
* @return array Информация о подписантах
|
||
*/
|
||
public static function verifyDetachedSignature(string $signedMessage, string $messageHash)
|
||
{
|
||
try
|
||
{
|
||
$cadesSignedData = new \CPSignedData();
|
||
$cadesHashedData = new \CPHashedData();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации проверки подписи'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesHashedData->set_Algorithm(CADESCOM_HASH_ALGORITHM_CP_GOST_3411_2012_256);
|
||
$cadesHashedData->SetHashValue($messageHash);
|
||
|
||
// Для получения объекта отсоединенной (открепленной) подписи, необходимо задать любой контент.
|
||
// Этот баг описан на форуме.
|
||
// https://www.cryptopro.ru/forum2/default.aspx?g=posts&m=78553#post78553
|
||
$cadesSignedData->set_Content(123);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке хеша'));
|
||
}
|
||
|
||
try
|
||
{
|
||
$cadesSignedData->VerifyHash($cadesHashedData, $signedMessage, CADESCOM_CADES_TYPE::PKCS7_TYPE);
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при проверке подписи'));
|
||
}
|
||
|
||
return static::getSigners($cadesSignedData);
|
||
}
|
||
|
||
/**
|
||
* Извлекает из подписи данные о подписантах
|
||
*
|
||
* @param \CPSignedData $cadesSignedData подписанные данные
|
||
*
|
||
* @throws \Exception
|
||
* @return array информация о подписантах
|
||
*/
|
||
protected static function getSigners(\CPSignedData $cadesSignedData)
|
||
{
|
||
try
|
||
{
|
||
$cadesSigners = $cadesSignedData->get_Signers();
|
||
$cadesSignersCount = (int) $cadesSigners->get_Count();
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка получения списка подписантов'));
|
||
}
|
||
|
||
if (!$cadesSignersCount)
|
||
{
|
||
throw new \Exception('Нет доступных подписантов');
|
||
}
|
||
|
||
$signers = [];
|
||
|
||
try
|
||
{
|
||
while ($cadesSignersCount)
|
||
{
|
||
$cadesSigner = $cadesSigners->get_Item($cadesSignersCount);
|
||
$cadesCertificate = $cadesSigner->get_Certificate();
|
||
$certificate = new Certificate(
|
||
$cadesCertificate,
|
||
CertificateHelper::extractCommonName($cadesCertificate->get_SubjectName()),
|
||
$cadesCertificate->get_IssuerName(),
|
||
$cadesCertificate->get_SubjectName(),
|
||
$cadesCertificate->get_Thumbprint(),
|
||
$cadesCertificate->get_ValidFromDate(),
|
||
$cadesCertificate->get_ValidToDate()
|
||
);
|
||
|
||
$signers[] = [
|
||
'signing_time' => $cadesSigner->get_SigningTime(),
|
||
'certificate' => $certificate
|
||
];
|
||
|
||
$cadesSignersCount--;
|
||
}
|
||
}
|
||
catch (\Throwable $e)
|
||
{
|
||
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при чтении информации о подписанте'));
|
||
}
|
||
|
||
return $signers;
|
||
}
|
||
} |