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, поиск не вернет следующие типы сертификатов: *