2021-12-14 15:05:43 +03:00
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace Webmasterskaya\CryptoPro;
|
|
|
|
|
|
2022-10-21 17:17:51 +03:00
|
|
|
|
use Webmasterskaya\CryptoPro\Helpers\CertificateHelper;
|
|
|
|
|
use Webmasterskaya\CryptoPro\Helpers\ErrorMessageHelper;
|
|
|
|
|
|
2021-12-14 15:05:43 +03:00
|
|
|
|
class CryptoPro
|
|
|
|
|
{
|
2022-10-24 18:25:56 +03:00
|
|
|
|
protected const CP_MY_STORE = 'My';
|
|
|
|
|
|
2022-10-20 20:06:50 +03:00
|
|
|
|
/**
|
2022-10-21 17:29:43 +03:00
|
|
|
|
* Возвращает список сертификатов, доступных пользователю в системе
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*
|
2022-10-21 17:29:43 +03:00
|
|
|
|
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
|
|
|
|
*
|
|
|
|
|
* @throws \Exception
|
2022-10-20 20:06:50 +03:00
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public static function getUserCertificates(bool $resetCache = false)
|
|
|
|
|
{
|
2022-10-21 17:17:51 +03:00
|
|
|
|
static $certificates;
|
|
|
|
|
if ($resetCache === true || !isset($certificates))
|
|
|
|
|
{
|
2022-10-21 17:29:43 +03:00
|
|
|
|
$certificates = self::getCertificatesFromStore(CURRENT_USER_STORE);
|
2022-10-21 17:17:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $certificates;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-10-21 17:29:43 +03:00
|
|
|
|
* Возвращает список сертификатов, доступных пользователю в системе, в том числе просроченные и без закрытого ключа
|
|
|
|
|
*
|
|
|
|
|
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*
|
2022-10-21 17:29:43 +03:00
|
|
|
|
* @throws \Exception
|
2022-10-20 20:06:50 +03:00
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public static function getAllUserCertificates(bool $resetCache = false)
|
|
|
|
|
{
|
2022-10-21 17:17:51 +03:00
|
|
|
|
static $certificates;
|
|
|
|
|
if ($resetCache === true || !isset($certificates))
|
|
|
|
|
{
|
2022-10-24 18:25:56 +03:00
|
|
|
|
$certificates = self::getCertificatesFromStore(CURRENT_USER_STORE, self::CP_MY_STORE, false, false);
|
2022-10-21 17:17:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $certificates;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-10-21 17:29:43 +03:00
|
|
|
|
* Возвращает список сертификатов, из закрытых ключей и/или сертификаты не установленные всистеме*
|
|
|
|
|
*
|
|
|
|
|
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*
|
2022-10-21 17:29:43 +03:00
|
|
|
|
* @throws \Exception
|
2022-10-20 20:06:50 +03:00
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public static function getContainerCertificates(bool $resetCache = false)
|
|
|
|
|
{
|
2022-10-21 17:17:51 +03:00
|
|
|
|
static $certificates;
|
|
|
|
|
if ($resetCache === true || !isset($certificates))
|
|
|
|
|
{
|
2022-10-21 17:29:43 +03:00
|
|
|
|
$certificates = self::getCertificatesFromStore(CONTAINER_STORE);
|
2022-10-21 17:17:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $certificates;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-10-21 17:29:43 +03:00
|
|
|
|
* Возвращает список сертификатов, из закрытых ключей и/или сертификаты не установленные всистеме*, в том числе просроченные и без закрытого ключа
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*
|
2022-10-21 17:29:43 +03:00
|
|
|
|
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
|
|
|
|
*
|
|
|
|
|
* @throws \Exception
|
2022-10-20 20:06:50 +03:00
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public static function getAllContainerCertificates(bool $resetCache = false)
|
|
|
|
|
{
|
2022-10-21 17:17:51 +03:00
|
|
|
|
static $certificates;
|
|
|
|
|
if ($resetCache === true || !isset($certificates))
|
|
|
|
|
{
|
2022-10-24 18:25:56 +03:00
|
|
|
|
$certificates = self::getCertificatesFromStore(CONTAINER_STORE, self::CP_MY_STORE, false, false);
|
2022-10-21 17:17:51 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $certificates;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-21 17:29:43 +03:00
|
|
|
|
/**
|
|
|
|
|
* Возвращает список сертификатов, доступных пользователю из пользовательского хранилища и закрытых ключей, не установленных в системе
|
|
|
|
|
*
|
|
|
|
|
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
|
|
|
|
*
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2022-10-21 17:17:51 +03:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-21 17:29:43 +03:00
|
|
|
|
/**
|
|
|
|
|
* Возвращает список сертификатов, доступных пользователю из пользовательского хранилища и закрытых ключей,
|
|
|
|
|
* не установленных в системе, без фильтрации по дате и наличию приватного ключа
|
|
|
|
|
*
|
|
|
|
|
* @param bool $resetCache Сбросить кэш. true - повторно получить список сертификатов из хранилища
|
|
|
|
|
*
|
|
|
|
|
* @return array
|
|
|
|
|
*/
|
2022-10-21 17:17:51 +03:00
|
|
|
|
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;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-10-21 17:29:43 +03:00
|
|
|
|
* Возвращает сертификат по отпечатку
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*
|
2022-10-21 18:28:09 +03:00
|
|
|
|
* @param string $thumbprint отпечаток сертификата
|
|
|
|
|
* @param bool $validOnly проверять сертификат по дате и наличию приватного ключа
|
|
|
|
|
*
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
* @return Certificate
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*/
|
2022-10-21 18:28:09 +03:00
|
|
|
|
public static function getCertificate(string $thumbprint, bool $validOnly = true)
|
2022-10-20 20:06:50 +03:00
|
|
|
|
{
|
2022-10-21 18:28:09 +03:00
|
|
|
|
$thumbprint = trim($thumbprint);
|
|
|
|
|
|
|
|
|
|
if (!$thumbprint)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception('Отпечаток не указан');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($validOnly === true)
|
|
|
|
|
{
|
|
|
|
|
$certificates = self::getCertificates();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
$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;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-10-21 18:28:09 +03:00
|
|
|
|
* Создает совмещенную (присоединенную) подпись сообщения
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*
|
2022-10-24 18:25:56 +03:00
|
|
|
|
* @param string $thumbprint отпечаток сертификата
|
|
|
|
|
* @param string $unencryptedMessage подписываемое сообщение
|
|
|
|
|
* @param string|null $pin пин-код доступа к закрытому ключу
|
2022-10-21 18:28:09 +03:00
|
|
|
|
*
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
* @return string подпись в формате PKCS#7
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*/
|
2022-10-24 18:25:56 +03:00
|
|
|
|
public static function createAttachedSignature(string $thumbprint, string $unencryptedMessage, string $pin = null)
|
2022-10-20 20:06:50 +03:00
|
|
|
|
{
|
2022-10-21 18:28:09 +03:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CURRENT_USER_STORE);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CONTAINER_STORE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesAttrs = new \CPAttribute();
|
|
|
|
|
$cadesSignedData = new \CPSignedData();
|
|
|
|
|
$cadesSigner = new \CPSigner();
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$currentDateTime = (new \DateTime())->format('d.m.Y H:i:s');
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesAttrs->set_Name(AUTHENTICATED_ATTRIBUTE_SIGNING_TIME);
|
|
|
|
|
$cadesAttrs->set_Value($currentDateTime);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке времени подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$messageBase64 = base64_encode($unencryptedMessage);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2022-10-24 18:25:56 +03:00
|
|
|
|
if (!empty($pin))
|
|
|
|
|
{
|
|
|
|
|
$cadesSigner->set_KeyPin($pin);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-21 18:28:09 +03:00
|
|
|
|
$cadesSigner->set_Certificate($cadesCertificate);
|
|
|
|
|
|
|
|
|
|
/** @var \CPAttributes $cadesAuthAttrs */
|
|
|
|
|
$cadesAuthAttrs = $cadesSigner->get_AuthenticatedAttributes();
|
|
|
|
|
$cadesAuthAttrs->Add($cadesAttrs);
|
|
|
|
|
|
|
|
|
|
$cadesSignedData->set_ContentEncoding(BASE64_TO_BINARY);
|
|
|
|
|
$cadesSignedData->set_Content($messageBase64);
|
|
|
|
|
|
|
|
|
|
$cadesSigner->set_Options(CERTIFICATE_INCLUDE_WHOLE_CHAIN);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при указании данных для подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
/** @var string $signature */
|
|
|
|
|
$signature = $cadesSignedData->SignCades($cadesSigner, PKCS7_TYPE);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при подписании данных'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $signature;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-10-21 18:28:09 +03:00
|
|
|
|
* Создает отсоединенную (открепленную) подпись сообщения
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*
|
2022-10-24 18:25:56 +03:00
|
|
|
|
* @param string $thumbprint отпечаток сертификата
|
|
|
|
|
* @param string $messageHash хеш подписываемого сообщения, сгенерированный по ГОСТ Р 34.11-2012 256 бит
|
|
|
|
|
* @param string|null $pin пин-код доступа к закрытому ключу
|
2022-10-21 18:40:01 +03:00
|
|
|
|
*
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
* @return string подпись в формате PKCS#7
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*/
|
2022-10-24 18:25:56 +03:00
|
|
|
|
public static function createDetachedSignature(string $thumbprint, string $messageHash, string $pin = null)
|
2022-10-20 20:06:50 +03:00
|
|
|
|
{
|
2022-10-21 18:40:01 +03:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CURRENT_USER_STORE);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CONTAINER_STORE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesAttrs = new \CPAttribute();
|
|
|
|
|
$cadesSignedData = new \CPSignedData();
|
|
|
|
|
$cadesHashedData = new \CPHashedData();
|
|
|
|
|
$cadesSigner = new \CPSigner();
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$currentDateTime = (new \DateTime())->format('d.m.Y H:i:s');
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesAttrs->set_Name(AUTHENTICATED_ATTRIBUTE_SIGNING_TIME);
|
|
|
|
|
$cadesAttrs->set_Value($currentDateTime);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке времени подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2022-10-24 18:25:56 +03:00
|
|
|
|
if (!empty($pin))
|
|
|
|
|
{
|
|
|
|
|
$cadesSigner->set_KeyPin($pin);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-21 18:40:01 +03:00
|
|
|
|
$cadesSigner->set_Certificate($cadesCertificate);
|
|
|
|
|
|
|
|
|
|
/** @var \CPAttributes $cadesAuthAttrs */
|
|
|
|
|
$cadesAuthAttrs = $cadesSigner->get_AuthenticatedAttributes();
|
|
|
|
|
$cadesAuthAttrs->Add($cadesAttrs);
|
|
|
|
|
|
|
|
|
|
$cadesSigner->set_Options(CERTIFICATE_INCLUDE_WHOLE_CHAIN);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
{
|
|
|
|
|
$signature = $cadesSignedData->SignHash($cadesHashedData, $cadesSigner, PKCS7_TYPE);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при подписании данных'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $signature;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* добавляет совмещенную (присоединенную) подпись к раннее подписанному документу (реализует метод coSign)
|
|
|
|
|
*
|
2022-10-24 18:25:56 +03:00
|
|
|
|
* @param string $thumbprint отпечаток сертификата
|
|
|
|
|
* @param string $signedMessage
|
|
|
|
|
* @param string|null $pin пин-код доступа к закрытому ключу
|
|
|
|
|
*
|
|
|
|
|
* @throws \Exception
|
2022-10-21 19:05:55 +03:00
|
|
|
|
* @return string
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*/
|
2022-10-24 18:25:56 +03:00
|
|
|
|
public static function addAttachedSignature(string $thumbprint, string $signedMessage, string $pin = null)
|
2022-10-20 20:06:50 +03:00
|
|
|
|
{
|
2022-10-21 19:05:55 +03:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CURRENT_USER_STORE);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CONTAINER_STORE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesAttrs = new \CPAttribute();
|
|
|
|
|
$cadesSignedData = new \CPSignedData();
|
|
|
|
|
$cadesSigner = new \CPSigner();
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$currentDateTime = (new \DateTime())->format('d.m.Y H:i:s');
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesAttrs->set_Name(AUTHENTICATED_ATTRIBUTE_SIGNING_TIME);
|
|
|
|
|
$cadesAttrs->set_Value($currentDateTime);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке времени подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$messageBase64 = base64_encode($signedMessage);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2022-10-24 18:25:56 +03:00
|
|
|
|
if (!empty($pin))
|
|
|
|
|
{
|
|
|
|
|
$cadesSigner->set_KeyPin($pin);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-21 19:05:55 +03:00
|
|
|
|
$cadesSigner->set_Certificate($cadesCertificate);
|
|
|
|
|
|
|
|
|
|
/** @var \CPAttributes $cadesAuthAttrs */
|
|
|
|
|
$cadesAuthAttrs = $cadesSigner->get_AuthenticatedAttributes();
|
|
|
|
|
$cadesAuthAttrs->Add($cadesAttrs);
|
|
|
|
|
|
|
|
|
|
$cadesSignedData->set_ContentEncoding(BASE64_TO_BINARY);
|
|
|
|
|
$cadesSignedData->set_Content($messageBase64);
|
|
|
|
|
|
|
|
|
|
$cadesSigner->set_Options(CERTIFICATE_INCLUDE_WHOLE_CHAIN);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при указании данных для подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesSignedData->VerifyCades($signedMessage, PKCS7_TYPE);
|
|
|
|
|
$signature = $cadesSignedData->CoSignCades($cadesSigner, PKCS7_TYPE);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при подписании данных'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $signature;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-10-21 19:05:55 +03:00
|
|
|
|
* Добавляет отсоединенную (открепленную) подпись к раннее подписанному документу (реализует метод coSign)
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*
|
2022-10-24 18:25:56 +03:00
|
|
|
|
* @param string $thumbprint отпечаток сертификата
|
|
|
|
|
* @param string $signedMessage подписанное сообщение
|
|
|
|
|
* @param string $messageHash хеш подписываемого сообщения, сгенерированный по ГОСТ Р 34.11-2012 256 бит
|
|
|
|
|
* @param string|null $pin пин-код доступа к закрытому ключу
|
2022-10-21 19:05:55 +03:00
|
|
|
|
*
|
|
|
|
|
* @throws \Exception
|
|
|
|
|
* @return string подпись в формате PKCS#7
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*/
|
2022-10-24 18:25:56 +03:00
|
|
|
|
public static function addDetachedSignature(string $thumbprint, string $signedMessage, string $messageHash, string $pin = null)
|
2022-10-20 20:06:50 +03:00
|
|
|
|
{
|
2022-10-21 19:05:55 +03:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CURRENT_USER_STORE);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificate = self::getCadesCertificateFromStore($thumbprint, CONTAINER_STORE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesAttrs = new \CPAttribute();
|
|
|
|
|
$cadesSignedData = new \CPSignedData();
|
|
|
|
|
$cadesHashedData = new \CPHashedData();
|
|
|
|
|
$cadesSigner = new \CPSigner();
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$currentDateTime = (new \DateTime())->format('d.m.Y H:i:s');
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesAttrs->set_Name(AUTHENTICATED_ATTRIBUTE_SIGNING_TIME);
|
|
|
|
|
$cadesAttrs->set_Value($currentDateTime);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке времени подписи'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2022-10-24 18:25:56 +03:00
|
|
|
|
if (!empty($pin))
|
|
|
|
|
{
|
|
|
|
|
$cadesSigner->set_KeyPin($pin);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-21 19:05:55 +03:00
|
|
|
|
$cadesSigner->set_Certificate($cadesCertificate);
|
|
|
|
|
|
|
|
|
|
/** @var \CPAttributes $cadesAuthAttrs */
|
|
|
|
|
$cadesAuthAttrs = $cadesSigner->get_AuthenticatedAttributes();
|
|
|
|
|
$cadesAuthAttrs->Add($cadesAttrs);
|
|
|
|
|
|
|
|
|
|
$cadesSigner->set_Options(CERTIFICATE_INCLUDE_WHOLE_CHAIN);
|
|
|
|
|
}
|
|
|
|
|
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, PKCS7_TYPE);
|
|
|
|
|
|
|
|
|
|
$signature = $cadesSignedData->CoSignHash($cadesHashedData, $cadesSigner, PKCS7_TYPE);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при подписании данных'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $signature;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* создает XML подпись для документа в формате XML
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public static function createXMLSignature()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2022-10-21 19:05:55 +03:00
|
|
|
|
* Создает хеш сообщения по ГОСТ Р 34.11-2012 256 бит
|
|
|
|
|
*
|
|
|
|
|
* @param string $unencryptedMessage сообщение для хеширования
|
2022-10-20 20:06:50 +03:00
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
2022-10-21 19:05:55 +03:00
|
|
|
|
public static function createHash(string $unencryptedMessage)
|
2022-10-20 20:06:50 +03:00
|
|
|
|
{
|
2022-10-21 19:05:55 +03:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesHashedData = new \CPHashedData();
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при инициализации хэширования'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$messageBase64 = base64_encode($unencryptedMessage);
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesHashedData->set_Algorithm(CADESCOM_HASH_ALGORITHM_CP_GOST_3411_2012_256);
|
|
|
|
|
$cadesHashedData->set_DataEncoding(BASE64_TO_BINARY);
|
|
|
|
|
$cadesHashedData->Hash($messageBase64);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при установке хэширования'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
/** @var string $hash */
|
|
|
|
|
$hash = $cadesHashedData->get_Value();
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при создании хэша'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $hash;
|
2022-10-20 20:06:50 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* возвращает информацию о CSP и плагине
|
|
|
|
|
*
|
|
|
|
|
* @return void
|
|
|
|
|
*/
|
|
|
|
|
public static function getSystemInfo()
|
|
|
|
|
{
|
|
|
|
|
}
|
2021-12-14 15:05:43 +03:00
|
|
|
|
|
2022-10-21 17:17:51 +03:00
|
|
|
|
protected static function getCertificatesFromStore(
|
2022-10-21 17:29:43 +03:00
|
|
|
|
int $storeLocation, string $storeName = 'My', bool $validOnly = true, bool $withPrivateKey = true
|
2022-10-21 17:17:51 +03:00
|
|
|
|
)
|
|
|
|
|
{
|
|
|
|
|
$certificates = [];
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesStore = new \CPStore();
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при попытке доступа к хранилищу'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesStore->Open($storeLocation, $storeName, STORE_OPEN_MAXIMUM_ALLOWED);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при открытии хранилища'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificates = $cadesStore->get_Certificates();
|
|
|
|
|
|
2022-10-21 18:28:09 +03:00
|
|
|
|
/**
|
|
|
|
|
* Не рассматриваются сертификаты не действительны на данный момент
|
|
|
|
|
*/
|
|
|
|
|
if ($validOnly === true)
|
2022-10-21 17:17:51 +03:00
|
|
|
|
{
|
2022-10-21 18:28:09 +03:00
|
|
|
|
$cadesCertificates = $cadesCertificates->Find(CERTIFICATE_FIND_TIME_VALID);
|
|
|
|
|
}
|
2022-10-21 17:17:51 +03:00
|
|
|
|
|
2022-10-21 18:28:09 +03:00
|
|
|
|
/**
|
|
|
|
|
* Не рассматриваются сертификаты, в которых отсутствует закрытый ключ
|
|
|
|
|
*/
|
|
|
|
|
if ($withPrivateKey === true)
|
|
|
|
|
{
|
|
|
|
|
$cadesCertificates = $cadesCertificates->Find(CERTIFICATE_FIND_EXTENDED_PROPERTY, CAPICOM_PROPID_KEY_PROV_INFO);
|
2022-10-21 17:17:51 +03:00
|
|
|
|
}
|
2022-10-21 18:28:09 +03:00
|
|
|
|
|
|
|
|
|
$cadesCertificatesCount = $cadesCertificates->Count();
|
2022-10-21 17:17:51 +03:00
|
|
|
|
}
|
|
|
|
|
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->SubjectName),
|
|
|
|
|
$cadesCertificate->IssuerName,
|
|
|
|
|
$cadesCertificate->SubjectName,
|
|
|
|
|
$cadesCertificate->Thumbprint,
|
|
|
|
|
$cadesCertificate->ValidFromDate,
|
|
|
|
|
$cadesCertificate->ValidToDate
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
$cadesCertificatesCount--;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка обработки сертификатов'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$cadesStore->Close();
|
|
|
|
|
|
|
|
|
|
return $certificates;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-21 18:28:09 +03:00
|
|
|
|
protected static function getCadesCertificateFromStore(string $thumbprint, int $storeLocation, string $storeName = 'My')
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesStore = new \CPStore();
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при попытке доступа к хранилищу'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
$cadesStore->Open($storeLocation, $storeName, STORE_OPEN_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(CERTIFICATE_FIND_SHA1_HASH, $thumbprint);
|
|
|
|
|
|
|
|
|
|
$cadesCertificatesCount = $cadesCertificates->Count();
|
|
|
|
|
|
|
|
|
|
if (!$cadesCertificatesCount)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception('Сертификат с отпечатком: "' . $thumbprint . '" не найден в хранилище');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$cadesCertificate = $cadesCertificates->Item(1);
|
|
|
|
|
}
|
|
|
|
|
catch (\Throwable $e)
|
|
|
|
|
{
|
|
|
|
|
throw new \Exception(ErrorMessageHelper::getErrorMessage($e, 'Ошибка при получении сертификата из хранилища'));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $cadesCertificate;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-21 17:17:51 +03:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-12-14 15:05:43 +03:00
|
|
|
|
}
|