Created
June 26, 2020 08:29
-
-
Save nizom333/b5c4187af91a72ee7c8e0b06941e5e63 to your computer and use it in GitHub Desktop.
Eimzo pkcs7 sign parser in php. simple analog of dsv server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php /** @noinspection PhpUndefinedMethodInspection PhpUnused */ | |
/*************************************************************************************************************************************************************************** | |
* Разработчик Хакимов Александр https://github.com/hackimov * | |
* Данный класс занимается получением данных из подписи формата pkcs7 ГНК РУз. * | |
* Подпись расшифровывается с помощью phpseclib из X509 вытаскивается BER формат далее декодируется через ASN1 * | |
* Существует ещё дофига OID политик, можно их дополнить в маппинге но я использовал только самые нужные. * | |
* * | |
* Работа библиотеки зависит от дополнительной библиотеки PhpSecLib (Библиотека работы с ASN1 и пр. по умолчанию есть во всех композерах). * | |
* Для корректной работы нужно всего лишь правильно указать путь до библиотеки в use phpseclib\File\ASN1, т.к. библиотека может распологаться у вас в другом неймспейсе. * | |
* Функционал являющийся ядром системы. * | |
* * | |
* Результирующие методы имеют snake_case * | |
* * | |
* * | |
* Примеры использования: * | |
* <?php * | |
* * | |
* use Eimzo\HackimovSignParser; * | |
* * | |
* $signature = HackimovSignParser::sign_parse($sign); * | |
* var_dump($signature); * | |
* $signature = HackimovSignParser::is_signature($sign); * | |
* var_dump($signature); * | |
* $signature = HackimovSignParser::last_by_date($sign); * | |
* var_dump($signature); * | |
***************************************************************************************************************************************************************************/ | |
namespace Eimzo; | |
use phpseclib\File\ASN1; | |
use Exception; | |
class HackimovSignParser | |
{ | |
/** | |
* @timezeone присваивается нужный часовой пояс, т.к. подпись не содержит в себе перевод времени на нужный часовой пояс, следовательно для Ташкента это +5 GMT | |
* @date_format формат дат, в котором вы будете получать результирующие значения времени | |
*/ | |
protected static $timezone = 'Asia/Tashkent'; | |
protected static $date_format = 'Y-m-d H:i:s'; | |
/** | |
* @var string OID политик используемые в маппинге | |
*/ | |
protected static $person_full_name_oid = '2.5.4.3'; | |
protected static $person_surname_oid = '2.5.4.4'; | |
protected static $company_country_oid = '2.5.4.6'; | |
protected static $company_area_oid = '2.5.4.7'; | |
protected static $company_city_oid = '2.5.4.8'; | |
protected static $company_name_oid = '2.5.4.10'; | |
protected static $person_position_oid = '2.5.4.12'; | |
protected static $company_type_oid = '2.5.4.15'; | |
protected static $person_name_oid = '2.5.4.41'; | |
protected static $company_tin_oid = '1.2.860.3.16.1.1'; | |
protected static $person_pin_oid = '1.2.860.3.16.1.2'; | |
protected static $signing_time_oid = '1.2.840.113549.1.9.5'; | |
protected static $person_tin_oid = '0.9.2342.19200300.100.1.1'; | |
/** | |
* @param string $sign | |
* @return array|bool | |
* Возвращает подпись и всех подписантов содержащихся в подписи, используя mapping выдёргивает нужные OID политик для физических и юридических лиц | |
*/ | |
public static function sign_parse(string $sign) | |
{ | |
try { | |
$decoded = self::SignToArray($sign); | |
if(isset($decoded[0])){ | |
$decoded = $decoded[0]; | |
} else { | |
return false; | |
} | |
if(isset($decoded['content']['0']['content']) && $decoded['content']['0']['content'] === '1.2.840.113549.1.7.2'){ | |
$pkcs7Info_d = $decoded['content']['1']['content']; | |
if(isset($pkcs7Info_d['0']['content']['0']['content']) && $pkcs7Info_d['0']['content']['0']['content']){ | |
$signature_data = $pkcs7Info_d['0']['content']['2']['content']['1']['content']['0']['content']; | |
} | |
$signers_data = []; | |
// тут из всего г-на мы получаем нужные подписи по следующей схеме (плюсами обосначены нужные элементы для парсинга) т.е. нам нужен только каждый 3-й элемент | |
// 0 1 2 3 4 5 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
// + - - + - - + - - + - - + - - + - - + - - + - - + - - + - - | |
$sign_iterator = 0; | |
$signers_content_count = count($pkcs7Info_d['0']['content']['3']['content']); | |
for($i=0; $i<$signers_content_count; $i+=3){ | |
$signer = $pkcs7Info_d['0']['content']['3']['content'][$i]; | |
// получаем даты согласно маппингу | |
if($pkcs7Info_d['0']['content']['4']['content']['0']['content']['3']['content']['1']['content']['0']['content'] === self::$signing_time_oid){ | |
$key_inner['signed_at'] = $pkcs7Info_d['0']['content']['4']['content'][$sign_iterator]['content']['3']['content']['1']['content']['1']['content']['0']['content']; | |
} | |
$signer = $signer['content']['0']['content']; | |
unset($signer['0'],$signer['2'],$signer['3']); | |
$signer = array_values($signer); | |
if(is_object($signer['0']['content'])) { | |
$key_inner['key_id'] = dechex($signer['0']['content']->toString(true)); | |
} | |
if($signer['1']['type'] === 16) { | |
$key_inner['key_start'] = $signer['1']['content']['0']['content']; | |
$key_inner['key_stop'] = $signer['1']['content']['1']['content']; | |
} | |
if($signer['2']['type'] === 16){ | |
foreach ($signer['2']['content'] as $oid_data){ | |
if(isset($oid_data['content']['0']['content']['1']['content'], $oid_data['content']['0']['content']['0']['content'])) { | |
if($oid_data['content']['0']['content']['0']['content'] === self::$person_full_name_oid) { | |
$key_inner['person_full_name'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$person_name_oid){ | |
$key_inner['person_name'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$person_surname_oid) { | |
$key_inner['person_surname'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$company_name_oid) { | |
$key_inner['company_name'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$company_area_oid) { | |
$key_inner['company_area'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$company_city_oid) { | |
$key_inner['company_city'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$company_country_oid) { | |
$key_inner['company_country'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$person_tin_oid) { | |
$key_inner['person_tin'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$person_position_oid) { | |
$key_inner['person_position'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$company_tin_oid) { | |
$key_inner['company_tin'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$company_type_oid) { | |
$key_inner['company_type'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
if($oid_data['content']['0']['content']['0']['content'] === self::$person_pin_oid) { | |
$key_inner['person_pin'] = $oid_data['content']['0']['content']['1']['content']; | |
} | |
} | |
} | |
$key_inner['signature_verified'] = true; // это заглушка и не является действительностью | |
$key_inner['signed_string'] = $signature_data; | |
} | |
// преобразование объектов времени с указанием таймзоны +5 GMT и перевод в человечески понятный формат даты | |
$key_random_dates[] = self::DateConfigure($key_inner['signed_at']); | |
$key_inner['key_start'] = self::DateConfigure($key_inner['key_start']); | |
$key_inner['key_stop'] = self::DateConfigure($key_inner['key_stop']); | |
unset($key_inner['signed_at']); | |
$signers_data[] = $key_inner; | |
unset($key_inner); | |
$sign_iterator++; | |
} | |
usort($key_random_dates, ['self','CompareByTimeStamp']); | |
$sorted_dates = array_reverse($key_random_dates); | |
$for_count = count($sorted_dates); | |
for($it = 0; $it<$for_count; $it++){ | |
$signers_data[$it]['signed_at'] = $sorted_dates[$it]; | |
} | |
} | |
/** @noinspection all */ | |
return $signers_data ?? false; | |
} catch (Exception $exception){ | |
return false; | |
} | |
} | |
/** | |
* @param string $sign | |
* @return bool | |
* Проверяет подпись ли это, возвращает true если это подпись. Если это какакая то дичь, возвращает false | |
*/ | |
public static function is_signature(string $sign) : bool | |
{ | |
try { | |
if(is_array(self::SignToArray($sign))){ | |
return true; | |
} | |
return false; | |
} catch (Exception $exception){ | |
return false; | |
} | |
} | |
/** | |
* @param string $sign | |
* @return array|bool|mixed | |
* Получает последнюю по времени подпись (кто подписал последний, требуется для отображения того или иного подписанта) | |
*/ | |
public static function last_by_date(string $sign) | |
{ | |
try { | |
$signers_data = self::sign_parse($sign); | |
if($signers_data === false){ | |
return $signers_data; | |
} | |
$max_data = 0; | |
foreach ($signers_data as $signer_data){ | |
if (strtotime($signer_data['signed_at']) > $max_data) { | |
$max_data = strtotime($signer_data['signed_at']); | |
$last_signature = $signer_data; | |
} | |
} | |
if(!isset($last_signature)){ | |
return false; | |
} | |
return $last_signature; | |
} catch (Exception $exception){ | |
return false; | |
} | |
} | |
/** | |
* @param $str | |
* @return bool|string | |
* Вытаскивает Base64 Encoding Result из base64 если та успешно прошла валидацию | |
*/ | |
public static function HackimovExtractBER($str) | |
{ | |
$temp = preg_replace('#-+[^-]+-+#', '', $str); | |
$temp = str_replace(["\r", "\n", ' '], '', $temp); | |
// если удалось декодировать base64 запиши туда декодированный base64 иначе запиши false | |
$temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? base64_decode($temp) : false; | |
// если декодирование base64 прошло неуспешно и в переменную записалось значение false возвращаем исходную строку | |
return $temp !== false ? $temp : $str; | |
} | |
/** | |
* @param $sign | |
* @return mixed | |
* Переводит подпись в многомерный неразобранный массив без маппинга | |
*/ | |
public static function SignToArray($sign) | |
{ | |
$ber = self::HackimovExtractBER($sign); | |
$asn1 = new ASN1(); | |
return $asn1->decodeBER($ber); | |
} | |
/** | |
* @param $time1 | |
* @param $time2 | |
* @return int | |
* Сортирует даты по времени, по убыванию | |
*/ | |
public static function CompareByTimeStamp($time1, $time2) : int | |
{ | |
if (strtotime($time1) < strtotime($time2)){ | |
return 1; | |
} | |
if (strtotime($time1) > strtotime($time2)){ | |
return -1; | |
} | |
return 0; | |
} | |
/** | |
* @param $date | |
* @return string | |
*/ | |
public static function DateConfigure($date): string | |
{ | |
return date_timezone_set($date, timezone_open(self::$timezone))->format(self::$date_format); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment