Skip to content

Instantly share code, notes, and snippets.

@nizom333
Created June 26, 2020 08:29
Show Gist options
  • Save nizom333/b5c4187af91a72ee7c8e0b06941e5e63 to your computer and use it in GitHub Desktop.
Save nizom333/b5c4187af91a72ee7c8e0b06941e5e63 to your computer and use it in GitHub Desktop.
Eimzo pkcs7 sign parser in php. simple analog of dsv server
<?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