Javascript: удаление элемента массива

В JavaScript как мне кажется не очень удобная реализация работы с массива. Наверное слишком низкоуровневая по сравнению с другими интерпретируемыми языками программирования. Возможно это даёт большую гибкость, но лично мне не удобно, что для простое удаление элемента массива по виду:

mass=[1,2,3,4];
delete mass[2];

Приводит на самом деле не к удалению элемента массива, а пометке, что элемент пустой. Многие пользуются командой splice:

mass=[1,2,3,4];
mass.splice(2,1)

Или даже filter:

brisks_mass=[1,2,3,4];
brisks_mass=brisks_mass.filter((number)=>number!==3) 

Но как говорится «Мне не зашло» (c), ибо есть с чем сравнивать удобство (Python, PHP и т.д.)

ГИС ЖКХ. Отправка и подпись сообщений на PHP

Ранее была статья, с реализацией подписи на Python. Переписал всё то-же самое на PHP. Ну на тот случай, если вдруг какие-то нюансы упустил в реализации. Однако нет, ошибка та-же:

<ns4:ErrorCode>AUT011005</ns4:ErrorCode>
<ns4:Description>Ошибка формата подписи запроса</ns4:Description>

Ниже описываю как формирую запрос. Он собирается из нескольких файлов xml:

in.xml

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:base="http://dom.gosuslugi.ru/schema/integration/base/" xmlns:drs="http://dom.gosuslugi.ru/schema/integration/drs/" xmlns:xd="http://www.w3.org/2000/09/xmldsig#">
   <soapenv:Header>
      <base:RequestHeader>
         <base:Date>2025-03-19T11:26:00+03:00</base:Date>
          <base:MessageGUID>${=java.util.UUID.randomUUID()}</base:MessageGUID>
		<base:orgPPAGUID>cerfwerfewrfer8</base:orgPPAGUID>
      </base:RequestHeader>
   </soapenv:Header>
<soapenv:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">    
    {req}
</soapenv:Body>
</soapenv:Envelope>

req.xml — непосредственно запрос к ГИС ЖКХ. В данном случае получение списка запросов от сторонних организаций о задолженностях потребителей

<drs:exportDSRsRequest Id="{signed_id}" base:version="15.3.0.0">  
<drs:periodOfSendingRequest><base:startDate>2025-05-27</base:startDate><base:endDate>2025-06-28</base:endDate></drs:periodOfSendingRequest>
</drs:exportDSRsRequest>

xades.xml:

<ds:Signature Id="xmldsig-{signature_id}" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
   {ds:SignedInfo}
   <ds:SignatureValue Id="xmldsig-{signature_id}-sigvalue">{signature_value}</ds:SignatureValue>
   <ds:KeyInfo>
       <ds:X509Data>
         <ds:X509Certificate>{x590_cert}</ds:X509Certificate>
       </ds:X509Data>
   </ds:KeyInfo>
   <ds:Object>
      <xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#xmldsig-{signature_id}">{SignedProperties}</xades:QualifyingProperties>
   </ds:Object>
</ds:Signature>

sp.xml:

<xades:SignedProperties Id="xmldsig-{signature_id}-signedprops">      
    <xades:SignedSignatureProperties>
        <xades:SigningTime>{signing_time}</xades:SigningTime>
        <xades:SigningCertificate>
         <xades:Cert>
           <xades:CertDigest>
             <ds:DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256"/>
             <ds:DigestValue>{digest2}</ds:DigestValue>
           </xades:CertDigest>
           <xades:IssuerSerial>
              <ds:X509IssuerName>{x509_issuer_name}</ds:X509IssuerName>
              <ds:X509SerialNumber>{x509_sn}</ds:X509SerialNumber>
           </xades:IssuerSerial>
         </xades:Cert>
        </xades:SigningCertificate>
      </xades:SignedSignatureProperties>
</xades:SignedProperties>      

si.xml:

<ds:SignedInfo>
    <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
      <ds:SignatureMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102012-gostr34112012-256"/>
      <ds:Reference Id="xmldsig-{signature_id}-ref0" URI="#{signed_id}">
          <ds:Transforms>
             <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
             <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
          </ds:Transforms>
         <ds:DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256"/>
         <ds:DigestValue>{digest1}</ds:DigestValue>
     </ds:Reference>
     <ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#xmldsig-{signature_id}-signedprops">
        <ds:DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256"/>
        <ds:DigestValue>{digest3}</ds:DigestValue>
     </ds:Reference>
</ds:SignedInfo>

Т.е. общий алгоритм следующий — заполняем в каждом файлике значения в фигурных скобках, а затем склеиваем всё в единый файл out.xml, который уже непосредственно отправляем по SOAP через туннель с ГИС ЖКХ.

Для начала понадобятся следующие функции:

Вычисление хэш строки по алгоритму ГОСТ Р 34.11-2012 256 бит:

function md_gost($text){
  file_put_contents("tmp.xml", $text);
  $res=cat tmp.xml | openssl dgst -binary -md_gost12_256 | base64 -w 0;
  $res= str_replace("\n", "", $res);
  return trim($res);
};

Получается строка вида: JSO1ALXjmpWXGkdQAs4RcEroYUnXOdbp5j4tbG8bNN8= в формате base64

Получение эмитента сертификата:

function get_issuer($cert_file){
  $res=openssl x509 -in $cert_file -issuer -nameopt sep_multiline,utf8 -noout;
  $res= str_replace("issuer=\n", "", $res);
  $res= str_replace("emailaddress", "1.2.840.113549.1.9.1", $res);
  $res= str_replace("inn", "1.2.643.100.4", $res);
  $res= str_replace("ogrn", "1.2.643.100.1", $res);
  $res= str_replace('"', "\\", $res);
  $res= str_replace(',', "\,", $res);
  $res= explode("\n", $res);
  $result="";
  for ($index = count($res)-1; $index > 0; $index--) {
    if (trim($res[$index])!=""){
        $prop= explode("=",$res[$index]);
        $result=$result.strtolower(trim($prop[0]))."=".trim($prop[1]);
        if ($index>1){
            $result=$result.",";
        };
    };
  };
  return trim($result);
}

Особенностью является «перевертывание» информации, нормализация отображения, а так-же замена ИНН, ОГРН и email на цифровые коды. Согласно рекомендации из «Альбом ТФФ». У меня получилась строка вида:

cn=Федеральная налоговая служба,o=Федеральная налоговая служба,street=ул. Неглинная\, д. 23,l=г. Москва,st=77 Москва,c=RU,ogrn=1047707030513,emailaddress=uc@tax.gov.ru

Получение серийного номера сертификата:

function get_serial($cert_file){
    $res=openssl x509 -in $cert_file -serial -nameopt sep_multiline,utf8 -noout;
    $res= str_replace("serial=", "", $res);
    $res= str_replace("\n", "", $res);
    return base_convert(trim($res),16,10);
}

Согласно Альбому ТФФ, он должен быть в десятичном формате. Вида:

778141053723503123230066408008688602

Канонинализация XML:

Для того чтобы в XML не было лишних и не значащих символов, данные перед подписанием и вычислением хэша необходимо канонинализировать:

function CanonicalXML($xml_text){
    $xml = new DOMDocument( "1.0", "utf-8" );
    @$xml->loadXML($xml_text);
    $xml = @$xml->C14N();
    return $xml;
}

Получение сертификата в формате Base64:

function X590($cert_file){
  $crt= file_get_contents($cert_file);  
  $crt= str_replace("-----BEGIN CERTIFICATE-----", "", $crt);
  $crt= str_replace("-----END CERTIFICATE-----", "", $crt);
  $crt= str_replace("\n", "", $crt);
  $crt= str_replace("\r", "", $crt);
  return trim($crt);
}

Получение подписи текста:

function get_signature($text, $private_file){
    file_put_contents("for_sign.xml", $text);    
    $res=cat for_sign.xml | openssl dgst -md_gost12_256 -sign $private_file -binary | base64 -w 0;
    $res= str_replace("\n", "", $res);
    return trim($res);
}

Возвращается в формате base64

Получение UUID:

function guidv4($data = null) {
    $data = $data ?? random_bytes(16);
    assert(strlen($data) == 16);
    $data[6] = chr(ord($data[6]) & 0x0f | 0x40);
    $data[8] = chr(ord($data[8]) & 0x3f | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

Далее идет уже непосредственно уже вычисление необходимых блоков и формирование выходного файла:

$in_file='in/in.xml';           // xml - главный шаблон, откуда осуществляем сборку
$req_file='in/req.xml';         // запрос в ГИС ЖКХ
$xades_file='in/xades.xml';     // Рыба подписи
$sp_file='in/sp.xml';           // Свойства подписи
$si_file='in/si.xml';           // Непосредственно подписываемый блок
$cert_file='keys/cert.key';     // Сертификат формата X590
$private_file='keys/priv.key';  // Закрытый ключ

$signed_id=guidv4();

$in_xml=file_get_contents($in_file);
$req_xml=CanonicalXML(file_get_contents($req_file));
$req_xml= str_replace("{signed_id}", $signed_id, $req_xml);

$xades_xml=file_get_contents($xades_file);


$digest1=md_gost($req_xml);
echo "digest1=$digest1\n";
$digest2= md_gost(base64_decode(X590($cert_file)));
echo "digest2=$digest2\n";

$dt=new DateTime();
$signing_time=$dt->format(DateTime::ATOM);
$signature_id=guidv4();


$sp_xml=CanonicalXML(file_get_contents($sp_file));
$sp_xml= str_replace("{signing_time}", $signing_time, $sp_xml);
$sp_xml= str_replace("{digest2}", $digest2, $sp_xml);
$sp_xml= str_replace("{x509_issuer_name}", get_issuer($cert_file), $sp_xml);
$sp_xml= str_replace("{x509_sn}", get_serial($cert_file), $sp_xml);
$digest3=md_gost($sp_xml);
echo "digest3=$digest3\n";

$si_xml=CanonicalXML(file_get_contents($si_file));
$si_xml= str_replace("{digest3}", $digest3, $si_xml);
$si_xml= str_replace("{digest1}", $digest1, $si_xml);
$si_xml= str_replace("{signature_id}", $signature_id, $si_xml);
$si_xml= str_replace("{signed_id}", $signed_id, $si_xml);

$signature_value=get_signature($si_xml,$private_file);
echo "signature_value=$signature_value\n";
$xades_xml= str_replace("{signature_value}", $signature_value, $xades_xml);
$xades_xml= str_replace("{x590_cert}", X590($cert_file), $xades_xml);
$xades_xml= str_replace("{SignedProperties}",$sp_xml, $xades_xml);
$xades_xml= str_replace("{ds:SignedInfo}",$si_xml, $xades_xml);
$xades_xml= str_replace("{signature_id}", $signature_id, $xades_xml);

//$in_xml= str_replace("{xades}",$xades_xml, $in_xml);
var_dump($req_xml);

$pz= strpos($req_xml, ">");
$req_1= trim(substr($req_xml,0,$pz+1));
$req_2= trim(substr($req_xml,$pz+1, strlen($req_xml)-$pz));

$req_xml=$req_1.$xades_xml.$req_2;

$in_xml= str_replace("{req}",$req_xml, $in_xml);

file_put_contents("out/out.xml",$in_xml);

Далее полученный файлик можно вставить например в SOAP UI, и попробовать отправить.

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:base="http://dom.gosuslugi.ru/schema/integration/base/" xmlns:drs="http://dom.gosuslugi.ru/schema/integration/drs/" xmlns:xd="http://www.w3.org/2000/09/xmldsig#">
   <soapenv:Header>
      <base:RequestHeader>
         <base:Date>2025-03-19T11:26:00+03:00</base:Date>
          <base:MessageGUID>${=java.util.UUID.randomUUID()}</base:MessageGUID>
		<base:orgPPAGUID>укепуепукепукеп</base:orgPPAGUID>
      </base:RequestHeader>
   </soapenv:Header>
<soapenv:Body xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">    
    <drs:exportDSRsRequest Id="938803c1-341c-41c5-8d24-10fa15dde654" base:version="15.3.0.0"><ds:Signature Id="xmldsig-51a19c5a-7c25-432c-9340-dd7582d4d3ef" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
   <ds:SignedInfo>
    <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:CanonicalizationMethod>
      <ds:SignatureMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102012-gostr34112012-256"></ds:SignatureMethod>
      <ds:Reference Id="xmldsig-51a19c5a-7c25-432c-9340-dd7582d4d3ef-ref0" URI="#938803c1-341c-41c5-8d24-10fa15dde654">
          <ds:Transforms>
             <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform>
             <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></ds:Transform>
          </ds:Transforms>
         <ds:DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256"></ds:DigestMethod>
         <ds:DigestValue>324к234к234к234к/6dgGHM4k52/RDm855aKazGXEeK4=</ds:DigestValue>
     </ds:Reference>
     <ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#xmldsig-51a19c5a-7c25-432c-9340-dd7582d4d3ef-signedprops">
        <ds:DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256"></ds:DigestMethod>
        <ds:DigestValue>34к34к234к234к3</ds:DigestValue>
     </ds:Reference>
</ds:SignedInfo>
   <ds:SignatureValue Id="xmldsig-51a19c5a-7c25-432c-9340-dd7582d4d3ef-sigvalue">mWUI++9N1uEe/234к234к/укеаукепуке</ds:SignatureValue>
   <ds:KeyInfo>
       <ds:X509Data>
         <ds:X509Certificate>укепукепкуепк/укепукепуке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</ds:X509Certificate>
       </ds:X509Data>
   </ds:KeyInfo>
   <ds:Object>
      <xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Target="#xmldsig-51a19c5a-7c25-432c-9340-dd7582d4d3ef"><xades:SignedProperties Id="xmldsig-51a19c5a-7c25-432c-9340-dd7582d4d3ef-signedprops">      
    <xades:SignedSignatureProperties>
        <xades:SigningTime>2025-06-17T10:31:13+03:00</xades:SigningTime>
        <xades:SigningCertificate>
         <xades:Cert>
           <xades:CertDigest>
             <ds:DigestMethod Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256"></ds:DigestMethod>
             <ds:DigestValue>кепукепук</ds:DigestValue>
           </xades:CertDigest>
           <xades:IssuerSerial>
              <ds:X509IssuerName>cn=Федеральная налоговая служба,o=Федеральная налоговая служба,street=ул. Неглинная\, д. 23,l=г. Москва,st=77 Москва,c=RU,ogrn=1047707030513,emailaddress=uc@tax.gov.ru</ds:X509IssuerName>
              <ds:X509SerialNumber>45е34е5354е43е</ds:X509SerialNumber>
           </xades:IssuerSerial>
         </xades:Cert>
        </xades:SigningCertificate>
      </xades:SignedSignatureProperties>
</xades:SignedProperties></xades:QualifyingProperties>
   </ds:Object>
</ds:Signature><drs:periodOfSendingRequest><base:startDate>2025-05-27</base:startDate><base:endDate>2025-06-28</base:endDate></drs:periodOfSendingRequest>
</drs:exportDSRsRequest>
</soapenv:Body>
</soapenv:Envelope>

В ответ получим номер идентификатора запроса. Далее его используем в запросе getState.. И получаем ошибку которая в самом начале статьи. Что не так — пока не понятно.

Update: тут немножко не верная реализация подписи. Нужно подписывать хэш данных, а у меня подписываются данные. Но пока без разницы — ошибка так и осталась.

ГИС ЖКХ — подпись сообщений

Ранее уже описывал как поднять защищенный туннель с ГИС ЖКХ.Теперь настало время научится делать запросы. Для этого их необходимо подписывать. Подписывается тело сообщения XML заключенное внутри тега body:

Сохраню его в файл in.xml. Далее для работы понадобится сертификат в формате x.509, он выгружается в формате BASE64 из Крипто-про

Понадобится он в бинарном виде:

cat 509.base64 | base64 -d >509.bin

Далее собираем следующую рыбу:

<ds:Signature Id="xmldsig-{signature_id}"
	xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
	<ds:SignedInfo>
		<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
		<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411"/>
		<ds:Reference Id="xmldsig-{signature_id}-ref0" URI="#{signed_id}">
			<ds:Transforms>
				<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
				<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
			</ds:Transforms>
			<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/>
			<ds:DigestValue>{digest1}</ds:DigestValue>
		</ds:Reference>
		<ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#xmldsig-{signature_id}-signedprops">
			<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/>
			<ds:DigestValue>{digest3}</ds:DigestValue>
		</ds:Reference>
	</ds:SignedInfo>
	<ds:SignatureValue Id="xmldsig-{signature_id}-sigvalue">
{signature_value}
</ds:SignatureValue>
	<ds:KeyInfo
		xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
		<ds:X509Data>
			<ds:X509Certificate>
{x590_cert}
</ds:X509Certificate>
		</ds:X509Data>
	</ds:KeyInfo>
	<ds:Object>
		<xades:QualifyingProperties Target="#xmldsig-{signature_id}"
			xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"
			xmlns:xades141="http://uri.etsi.org/01903/v1.4.1#">
			<xades:SignedProperties Id="xmldsig-{signature_id}-signedprops">
				<xades:SignedSignatureProperties>
					<xades:SigningTime>{signing_time}</xades:SigningTime>
					<xades:SigningCertificate>
						<xades:Cert>
							<xades:CertDigest>
								<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/>
								<ds:DigestValue>{digest2}</ds:DigestValue>
							</xades:CertDigest>
							<xades:IssuerSerial>
								<ds:X509IssuerName>{x509_issuer_name}</ds:X509IssuerName>
								<ds:X509SerialNumber>{x509_sn}</ds:X509SerialNumber>
							</xades:IssuerSerial>
						</xades:Cert>
					</xades:SigningCertificate>
				</xades:SignedSignatureProperties>
			</xades:SignedProperties>
		</xades:QualifyingProperties>
	</ds:Object>
</ds:Signature>

digest1 считается как хеш-сумма по ГОСТу и выводится в виде BASE64 содержимым тега body (см. выше)

cat in.xml | openssl dgst -engine gost -md_gost94  -binary | base64 >digest1

digest2 считается как хэш-сумма бинарного файла сертификата x509 и преобразованное в base64:

cat 509.bin | openssl dgst -engine gost -md_gost94  -binary | base64 >digest2

Далее заполняем блок SignedProperties, а в частности поля x509_issuer_name и x509_sn. Их можно получить соответственно командами:

openssl x509 -in cert.pem -serial -nameopt sep_multiline,utf8 -noout
openssl x509 -in cert.pem -issuer -nameopt sep_multiline,utf8 -noout

Причем x509_issuer_name нужно привести к виду:

cn=Тестовый УЦ ООО \»КРИПТО-ПРО\»,o=ООО \»КРИПТО-ПРО\»,l=Москва,st=г. Москва,c=RU,street=ул. Сущёвский вал д.18,1.2.643.3.131.1.1=001234567890,1.2.643.100.1=1234567890123

Тег emailaddress, заменяется на 1.2.840.113549.1.9.1, inn на 1.2.643.100.4, а ogrn на 13 1.2.643.100.1

Теперь необходимо подписать то, что мы заполнили между тегами SignedInfo. У меня получилось что-то вроде:

<xd:SignedInfo
	xmlns:base="http://dom.gosuslugi.ru/schema/integration/base/"
	xmlns:drs="http://dom.gosuslugi.ru/schema/integration/drs/"
	xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
	xmlns:xd="http://www.w3.org/2000/09/xmldsig#">
	<xd:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></xd:CanonicalizationMethod>
	<xd:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411"></xd:SignatureMethod>
	<xd:Reference Id="xmldsig-e99d1872-укепкеп54e8bb16d4-ref0" URI="#f9f93dкепуке6-11e5-b4ae-1c6f65dfe2b1">
		<xd:Transforms>
			<xd:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></xd:Transform>
			<xd:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></xd:Transform>
		</xd:Transforms>
		<xd:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"></xd:DigestMethod>
		<xd:DigestValue>b\'укепукепукепке/imNA=\'</xd:DigestValue>
	</xd:Reference>
	<xd:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#xmldsig-e99d1872-4534-11f0-b35a-e454e8bb16d4-signedprops">
		<xd:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"></xd:DigestMethod>
		<xd:DigestValue>DLYx2a/3ц4кц34к3/XQtrUiKviYQ=</xd:DigestValue>
	</xd:Reference>
</xd:SignedInfo>

Подпись осуществляется так:

openssl dgst -sign key.key -out to_sign.sig -binary to_sign

Подпись сохранилась в файл to_sign.sig, помещаем содержимое внутри тега SignatureValue и получившийся таким образом файл пробуем отправить в ГИС ЖКХ.


Некоторые заметки:

1) Как вытащить закрытый ключ с флешки с подписью — нужно воспользоваться утилитой P12FromGostCSP. Если вы пользовались чем-то другим, то с большой долей вероятностью получите ошибку:

root@vlg1w-bb16d4:/home/укацук@укацук.ru/Desktop/ЖКХ/БоевыеКлючи# openssl pkcs12 -in укацук.p12 -engine gost
engine "gost" set.
Enter Import Password:
Bag Attributes
    localKeyID: 01 00 00 00 
    friendlyName: e3йуцвйцуd8c1319fa1d5
    Microsoft CSP Name: Crypto-Pro GOST R 34.10-2012 KC1 CSP
Error outputting keys and certificates
140097466021056:error:06074079:digital envelope routines:EVP_PBE_CipherInit:unknown pbe algorithm:../crypto/evp/evp_pbe.c:95:TYPE=1.2.840.113549.1.12.1.80
140097466021056:error:23077073:PKCS12 routines:PKCS12_pbe_crypt:pkcs12 algor cipherinit error:../crypto/pkcs12/p12_decr.c:41:
140097466021056:error:2306A075:PKCS12 routines:PKCS12_item_decrypt_d2i:pkcs12 pbe crypt error:../crypto/pkcs12/p12_decr.c:94:

2) Формировать подпись или через КриптоПро или через OpenSSL.

3) Есть готовая утилита для формирования шапки с подписью написанная на Python 2.7. Я её перевел на Python3, сделав минимальные косметические изменения. В принципе работает. Скачать тут

Update 16.06.2025: на текущий момент не удалось достичь успешной отправки запроса в ГИС ЖКХ. Ошибка:

  "ErrorCode": "AUT011005",
  "Description": "Ошибка формата подписи запроса"

Подпись не подходит ни крипто-про:

/opt/cprocsp/bin/amd64/csptest -keyset -cont 'HDIMAGE\\e32681d3.000\A3EB' -sign 'GOST12_256' -keytype exchange -in for_sign.xml -out for_sign.xml.sgn |cat for_sign.xml.sgn| base64 -w 0

Ни OpenSSL:

cat for_sign.xml | openssl dgst -md_gost12_256 -sign priv.key -binary | base64 -w 0

Mozilla Thunderbird и Exchange

Штатно этот почтовый клиент не поддерживает работу с этим сервисом. Что чрезвычайно странно. Возможно как-то связано как обычно с политикой Microsoft. Для того чтобы научить работать Thunderbird c Exchange необходимо воспользоваться расширениями. Коих ровно два. Которые по сути один с разными названиями. Которые платные. Которые оплатить нельзя потому что потому. Пришлось стать «благородным пиратом», потому что я не согласен с дискриминацией по национальной принадлежности. Ниже ссылка на «взломанную» версию данного плагина:

P.S. Как только появится возможность купить данное расширение, файл незамедлительно уберу, и расширение куплю.

Считаете ли вы этичным пользоваться взломанным ПО при отсутствии возможности приобрести лицензионное ПО из-за дискриминации по национальному признаку (санкции)

View Results

Загрузка ... Загрузка ...

Парсинг новостей групп VK

В продолжение предыдущей статьи, появилась необходимость парсить так-же и новости в социальной сети vk с проверкой на наличие стоп-слов. Для этого воспользовался модулем vk на python. Так-же понадобится токен доступа полученный на https://vk.com/apps?act=manage

В итоге код получился примерно следующий:

#!/usr/bin/env python3
# encoding: utf-8
import vk
import json
import funcs

with open('config.json', 'r') as file:
    config_data = json.load(file)
    print(config_data)


api = vk.API(access_token=config_data["vk_api_token"],v='5.131')

for group in config_data["groups"]:
    chan_data = funcs.get_chan_json(group)
    skeep_after = chan_data["las_id"]
    wall_content = api.wall.get(domain=group, count=config_data["limit"])
    poz = 0
    for message in wall_content["items"]:
        #print(message)
        if poz == 0:
            chan_data["las_id"] = message["id"]
            funcs.save_chan_json(group, chan_data)
        if skeep_after == message["id"]:
            print("Все новости уже прочитаны...")
            break
        for word in config_data["alert_words"]:
          if word in message["text"]:
              print(f"--нашли слово {word}")
              funcs.SendMailVK(config_data,group, word, message)
              print(message)
        poz = poz + 1
print("all done..");

По сути код очень простой — получаем через API VK все последние новости из каждой группы. Если в тексте новости находим стоп-слово, то отправляем соответствующее письмо. Так-же использую дополнительный файл функций, которые далее использую во всех парсерах:

#!/usr/bin/env python3
#encoding: utf-8
import json
from datetime import datetime
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from telethon.tl.functions.contacts import ResolveUsernameRequest
from telethon.tl.functions.channels import GetMessagesRequest
from telethon.tl.functions.messages import GetHistoryRequest, ReadHistoryRequest

def save_chan_json(chan,chan_data):
    f = open('saves/'+chan + '.json', "w+")
    json.dump(chan_data, f)
    f.close()

def get_chan_json(chan):
    # узнаём какое последнее сообщение прочитали на канале?
    chan_data = {}
    chan_data["las_id"] = 0
    try:
        with open('saves/'+chan + '.json', 'r') as file:
            chan_data = json.load(file)
            print(chan_data)
            return chan_data
    except:
        save_chan_json(chan, chan_data)
        return chan_data
    return chan_data;


def SendMail(config_data,chan,word,message):
    msg = MIMEMultipart()
    msg['Subject'] = f"Найдено слово '{word}' в новости на канале {chan} в Телеграм"
    msg.add_header('Content-Type', 'text/html')
    message.text=message.text.replace(word,"<strong>"+word+"</strong>")
    dt_pub=message.date.strftime('%d-%m-%Y %H:%M:%S')
    msg.set_payload(f"Канал: <a href='https://vc.com/{chan}'>https://t.me/{chan}</a>, опубликовано {dt_pub}<hr/>"+message.text)

    smtpObj = smtplib.SMTP(config_data["smtp_server"], config_data["smtp_port"])
    smtpObj.starttls()
    smtpObj.login(config_data["email_login"], config_data["from_password"])
    smtpObj.sendmail(config_data["email_from"], config_data["notify_email"], msg.as_string().encode('utf-8'))
    smtpObj.quit()

def SendMailVK(config_data,chan,word,message):
    msg = MIMEMultipart()
    msg['Subject'] = f"Найдено слово '{word}' в новости на группе {chan} в VK"
    msg.add_header('Content-Type', 'text/html')
    message["text"]=message["text"].replace(word,"<strong>"+word+"</strong>")
    dt_pub=datetime.utcfromtimestamp(message["date"]).strftime('%d-%m-%Y %H:%M:%S')
    msg.set_payload(f"Группа: <a href='https://vc.com/{chan}'>https://vk.com/{chan}</a>, опубликовано {dt_pub}<hr/>"+message["text"])

    smtpObj = smtplib.SMTP(config_data["smtp_server"], config_data["smtp_port"])
    smtpObj.starttls()
    smtpObj.login(config_data["email_login"], config_data["from_password"])
    smtpObj.sendmail(config_data["email_from"], config_data["notify_email"], msg.as_string().encode('utf-8'))
    smtpObj.quit()

def SendMailNews(config_data,url,word,message):
    msg = MIMEMultipart()
    msg['Subject'] = f"Найдено слово '{word}' в новости на сайте {url}"
    msg.add_header('Content-Type', 'text/html')
    if message.get("href")!=None:
     message.string="<a href='"+message["href"]+"'>"+message.string.replace(word,"<strong>"+word+"</strong>")+"</a>"
    else:
        message.string = message.string.replace(word,"<strong>" + word + "</strong>")
    msg.set_payload(message.string)

    smtpObj = smtplib.SMTP(config_data["smtp_server"], config_data["smtp_port"])
    smtpObj.starttls()
    smtpObj.login(config_data["email_login"], config_data["from_password"])
    smtpObj.sendmail(config_data["email_from"], config_data["notify_email"], msg.as_string().encode('utf-8'))
    smtpObj.quit()

1 2 3 4 71