Получение списков сертификатов

This commit is contained in:
Artem Vasilev 2022-10-21 17:17:51 +03:00
parent 895acf2a85
commit 77a8b02954
4 changed files with 647 additions and 0 deletions

View File

@ -4,6 +4,34 @@ namespace Webmasterskaya\CryptoPro;
class Certificate class Certificate
{ {
public $_cadesCertificate;
public $name;
public $issuerName;
public $subjectName;
public $thumbprint;
public $validFrom;
public $validTo;
public function __construct(
\CPCertificate $cadesCertificate,
string $name,
string $issuerName,
string $subjectName,
string $thumbprint,
string $validFrom,
string $validTo
)
{
$this->_cadesCertificate = $cadesCertificate;
$this->name = $name;
$this->issuerName = $issuerName;
$this->subjectName = $subjectName;
$this->thumbprint = $thumbprint;
$this->validFrom = $validFrom;
$this->validTo = $validTo;
}
/** /**
* возвращает флаг действительности сертификата * возвращает флаг действительности сертификата
* *

View File

@ -2,6 +2,9 @@
namespace Webmasterskaya\CryptoPro; namespace Webmasterskaya\CryptoPro;
use Webmasterskaya\CryptoPro\Helpers\CertificateHelper;
use Webmasterskaya\CryptoPro\Helpers\ErrorMessageHelper;
class CryptoPro class CryptoPro
{ {
/** /**
@ -11,6 +14,13 @@ class CryptoPro
*/ */
public static function getUserCertificates(bool $resetCache = false) public static function getUserCertificates(bool $resetCache = false)
{ {
static $certificates;
if ($resetCache === true || !isset($certificates))
{
$certificates = self::getCertificatesFromStore(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE);
}
return $certificates;
} }
/** /**
@ -20,6 +30,13 @@ class CryptoPro
*/ */
public static function getAllUserCertificates(bool $resetCache = false) public static function getAllUserCertificates(bool $resetCache = false)
{ {
static $certificates;
if ($resetCache === true || !isset($certificates))
{
$certificates = self::getCertificatesFromStore(CAPICOM_CURRENT_USER_STORE, CAPICOM_MY_STORE, false, false);
}
return $certificates;
} }
/** /**
@ -29,6 +46,13 @@ class CryptoPro
*/ */
public static function getContainerCertificates(bool $resetCache = false) public static function getContainerCertificates(bool $resetCache = false)
{ {
static $certificates;
if ($resetCache === true || !isset($certificates))
{
$certificates = self::getCertificatesFromStore(CADESCOM_CONTAINER_STORE, CAPICOM_MY_STORE);
}
return $certificates;
} }
/** /**
@ -38,6 +62,79 @@ class CryptoPro
*/ */
public static function getAllContainerCertificates(bool $resetCache = false) public static function getAllContainerCertificates(bool $resetCache = false)
{ {
static $certificates;
if ($resetCache === true || !isset($certificates))
{
$certificates = self::getCertificatesFromStore(CADESCOM_CONTAINER_STORE, CAPICOM_MY_STORE, false, false);
}
return $certificates;
}
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;
}
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;
} }
/** /**
@ -112,4 +209,130 @@ class CryptoPro
{ {
} }
protected static function getCertificatesFromStore(
int $storeLocation, string $storeName, bool $validOnly = true, bool $withPrivateKey = true
)
{
$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, 'Ошибка при открытии хранилища'));
}
$cadesCertificates = null;
$cadesCertificatesCount = 0;
try
{
$cadesCertificates = $cadesStore->get_Certificates();
if ($cadesCertificates)
{
if ($validOnly === true)
{
$cadesCertificates = $cadesCertificates->Find(CERTIFICATE_FIND_TIME_VALID);
}
/**
* Не рассматриваются сертификаты, в которых отсутствует закрытый ключ
* или не действительны на данный момент
*/
if ($withPrivateKey === true)
{
$cadesCertificates = $cadesCertificates->Find(CERTIFICATE_FIND_EXTENDED_PROPERTY, CAPICOM_PROPID_KEY_PROV_INFO);
}
$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->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;
}
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;
}
}
}
}
} }

View File

@ -67,4 +67,16 @@ class CertificateHelper
return []; return [];
} }
public static function extractCommonName($subjectName)
{
$subjectMatch = [];
$commonName = null;
if (preg_match('/CN="?(.+?)"?(?:,|$)/', $subjectName, $subjectMatch))
{
$commonName = preg_replace('/"{2}/g', '"', $subjectMatch[1]);
}
return $commonName;
}
} }

View File

@ -0,0 +1,384 @@
<?php
namespace Webmasterskaya\CryptoPro\Helpers;
class ErrorMessageHelper
{
public static function getErrorMessage(\Throwable $error, string $defaultMessage = '')
{
$errorCode = $error->getCode();
if (is_int($errorCode))
{
$errorCode = self::formatErrorCode($errorCode);
}
switch ($errorCode)
{
// Стандартные виндовые коды ошибок
case '800B010E'://The revocation process could not continue - the certificate(s) could not be checked.
$res
= 'Процесс получения цепочки сертификатов не завершён. Подпись не может быть проверена. '
. '(нет доступа к интернету для скачивания сертификатов цепочки)'
. ' или один из сертификатов цепочки аннулирован';
break;
case '800B010A':
$res = 'Не удается построить цепочку сертификатов для доверенного корневого центра';
break;
case '80070057':// The parameter is incorrect
$res = 'Неверный параметр';
break;
case '800B010C':// Internal error.
$res = 'Ваш сертификат был отозван создателем';
break;
case '80090001':
$res = 'Bad UID';
break;
case '80090002':
$res = 'Bad Hash';
break;
case '80090003':
$res = 'Bad Key';
break;
case '80090004':
$res = 'Bad Length';
break;
case '80090005':
$res = 'Bad Data';
break;
case '80090006':
$res = 'Неправильная подпись';
break;
case '80090007':
$res = 'Bad Version of provider';
break;
case '80090008':
$res = 'Invalid algorithm specified';
break;
case '80090009':
$res = 'Invalid flags specified';
break;
case '8009000A':
$res = 'Invalid type specified';
break;
case '8009000B':
$res = 'Key not valid for use in specified state';
break;
case '8009000C':
$res = 'Hash not valid for use in specified state';
break;
case '8009000D':
$res = 'Key does not exist';
break;
case '8009000E':
$res = 'Insufficient memory available for the operation';
break;
case '8009000F':
$res = 'Object already exists';
break;
case '80090010':
$res = 'Access denied';
break;
case '80090011':
$res = 'Object was not found';
break;
case '80090012':
$res = 'Data already encrypted';
break;
case '80090013':
$res = 'Invalid provider specified';
break;
case '80090014':
$res = 'Invalid provider type specified';
break;
case '80090015':
$res = 'Provider\'s public key is invalid';
break;
case '80090016':
$res = 'Keyset does not exist';
break;
case '80090017':
$res = 'Provider type not defined';
break;
case '80090018':
$res = 'Provider type as registered is invalid';
break;
case '80090019':
$res = 'The keyset is not defined';
break;
case '8009001A':
$res = 'Keyset as registered is invalid';
break;
case '8009001B':
$res = 'Provider type does not match registered value';
break;
case '8009001C':
$res = 'The digital signature file is corrupt';
break;
case '8009001D':
$res = 'Provider DLL failed to initialize correctly';
break;
case '8009001E':
$res = 'Provider DLL could not be found';
break;
case '8009001F':
$res = 'The Keyset parameter is invalid';
break;
case '80090020':
$res = 'An internal error occurred';
break;
case '80090021':
$res = 'A base error occurred';
break;
case '80091001':
$res = 'An error was encountered doing a cryptographic message operation';
break;
case '80091002':
$res = 'The cryptographic algorithm is unknown';
break;
case '80091003':
$res = 'The object identifier is badly formatted';
break;
case '80091004':
$res = 'Недопустимый тип криптографического сообщения';
break;
case '80091005':
$res = 'The message is not encoded as expected';
break;
case '80091006':
$res = 'The message does not contain an expected authenticated attribute';
break;
case '80091007': // The hash value is not correct
$res = 'Хеш файла не соответствует подписи';
break;
case '80091008':
$res = 'The index value is not valid';
break;
case '80091009':
$res = 'The message content has already been decrypted';
break;
case '8009100A':
$res = 'The message content has not been decrypted yet';
break;
case '8009100B':
$res = 'The enveloped-data message does not contain the specified recipient';
break;
case '8009100C':
$res = 'The control type is not valid';
break;
case '8009100D':
$res = 'The issuer and/or serial number are/is not valid';
break;
case '8009100E':
$res = 'The original signer is not found';
break;
case '8009100F':
$res = 'The message does not contain the requested attributes';
break;
case '80092001':
$res = 'The length specified for the output data was insufficient';
break;
case '80092002':
$res = 'An error was encountered while encoding or decoding';
break;
case '80092003':
$res = 'An error occurred while reading or writing to the file';
break;
case '80092004':
$res = 'The object or property wasn\'t found';
break;
case '80092005':
$res = 'The object or property already exists';
break;
case '80092006':
$res = 'No provider was specified for the store or object';
break;
case '80092007':
$res = 'The specified certificate is self signed';
break;
case '80092008':
$res = 'The previous certificate or CRL context was deleted';
break;
case '80092009':
$res = 'No match when trying to find the object';
break;
case '8009200A':
$res = 'The type of the cryptographic message being decoded is different than what was expected';
break;
case '8009200B':
$res = 'The certificate doesn\'t have a private key property';
break;
case '8009200C':
$res = 'No certificate was found having a private key property to use for decrypting';
break;
case '8009200D':
$res = 'Either, not a cryptographic message or incorrectly formatted';
break;
case '8009200E':
$res = 'The signed message doesn\'t have a signer for the specified signer index';
break;
case '8009200F':
$res = 'Final closure is pending until additional frees or closes';
break;
case '80092010':
$res = 'The certificate or signature has been revoked';
break;
case '80092011':
$res = 'No .dll or exported function was found to verify revocation';
break;
case '80092012':
$res = 'The called function wasn\'t able to do a revocation check on the certificate or signature';
break;
case '80092013':
$res = 'Since the revocation server was offline, the called function wasn\'t able to complete the revocation check';
break;
case '80092020':
$res = 'The string contains a non-numeric character';
break;
case '80092021':
$res = 'The string contains a non-printable character';
break;
case '80092022':
$res = 'The string contains a character not in the 7 bit ASCII character set';
break;
case '80093000':
$res = 'OSS Certificate encode/decode error code base., '
. 'See asn1code.h for a definition of the OSS runtime errors.'
. 'The OSS error values are offset by CRYPT_E_OSS_ERROR';
break;
// Коды ошибок библиотеки tspcli
case 'C2110100':
case 'C2100100':
$res = 'При попытке отправки запроса возникла ошибка HTTP';
break;
case 'C2110101':
case 'C2100101':
$res = 'Указанный тип аутентификации запрещен групповой политикой';
break;
case 'C2110102':
case 'C2100102':
$res = 'Указанный тип аутентификации прокси-сервера запрещен групповой политикой';
break;
case 'C2100103':
$res = 'Указанная служба штампов запрещена групповой политикой';
break;
case 'C2100104':
$res = 'Использование поля Nonce запрещено групповой политикой';
break;
case 'C2100110':
$res = 'Указанный алгоритм хеширования запрещен групповой политикой';
break;
case 'C2100111':
$res = 'Указанный "PolicyID" запрещен групповой политикой';
break;
case 'C2100120':
$res = 'Значение полей "Nonce" запроса и штампа не совпадают';
break;
case 'C2100121':
$res = 'Не задан адрес службы штампов времени';
break;
case 'C2100122':
$res = 'Штамп времени просрочен (выдан слишком давно)';
break;
case 'C2100123':
$res = 'В запросе отсутствует хэш-значение';
break;
case 'C2100124':
$res = 'Получен ответ службы штампов времени с ошибкой';
break;
// Коды ошибок библиотеки ocspcli
case 'C2110103':
$res = 'Указанная служба OCSP запрещена групповой политикой';
break;
case 'C2110104':
$res = 'Встречено расширение (AcceptableTypes или Nonce), запрещенное групповой политикой';
break;
case 'C2110110':
$res = 'Подписанные OCSP-запросы запрещены политикой';
break;
case 'C2110111':
$res = 'Неподписанные OCSP-запросы запрещены политикой';
break;
case 'C2110120':
$res = 'Поля "Nonce" OCSP-запроса и ответа не совпадают';
break;
case 'C2110121':
$res = 'Не задан адрес службы OCSP';
break;
case 'C2110122':
$res = 'OCSP-ответ просрочен по значению поля "ProducedAt" или "NextUpdate"';
break;
case 'C2110123':
$res = 'Значение поля "ThisUpdate" OCSP-ответа просрочено';
break;
case 'C2110124':
$res = 'Значение поля "NextUpdate" OCSP-ответа меньше значения "ThisUpdate"';
break;
case 'C2110125':
$res = 'В OCSP-ответе не найден запрашиваемый статус сертификата';
break;
case 'C2110126':
$res = 'Сертификат отозван';
break;
case 'C2110127':
$res = 'Статус сертификата не известен';
break;
case 'C2110128':
$res = 'Получен OCSP-ответ с ошибкой';
break;
case 'C2110129':
$res = 'Полученный OCSP-ответ содержит неизвестное критическое расширение';
break;
case 'C2110130':
$res = 'Время Службы OCSP рассинхронизировано со Службой штампов времени';
break;
default:
$res = null;
}
if (!empty($res))
{
return $res . ' (0x' . $errorCode . ')';
}
$res = self::extractMeaningfulErrorMessage($error);
if (!empty($res))
{
return $res;
}
return $defaultMessage ?: 'Неизвестная ошибка. Описание отсутсвует.';
}
/**
* @param \Throwable $error
*
* @return string|null
*/
public static function extractMeaningfulErrorMessage(\Throwable $error)
{
$message = $error->getMessage();
$messageParts = [];
preg_match('/^(.*?)(?:(?:\.?\s?\(?(0x[0-9a-zA-Z]{1,8})\)?)|(?:\.?$))/', $message, $messageParts);
return !empty($messageParts[1]) ? $messageParts[1] . (!empty($messageParts[2]) ? ' (' . $messageParts[2] . ')' : '') : null;
}
/**
* Приводит число к шестнадцатеричному виду и обрезает до 8ми символов с конца
*
* @param int $errorCode Код ошибки
*
* @return string
*/
public static function formatErrorCode(int $errorCode)
{
return strtoupper(substr(dechex($errorCode), -8));
}
}