ГИС ЖКХ — отправка и подпись запросов часть 3

В продолжение предыдущей статьи. На основе того кода, так и не удалось довести до рабочего состояния. Ошибка так и осталась. Посему было решено..опять всё переписать с нуля.. С учётом полученных новых вводных, а именно:

  • XML подписи оказывается должен быть минимизирован (не должно быть форматирования тегов)
  • подписываться должен хэш данных, а не сами данные
  • при работе с XML как текстом, были опасения про неверную каннизацию

Потому переписал код на отностительно честную работу с XML через DOMDocument. С одним нюансом — так и не научился вставлять один XML документ в другой посредством его. Поэтому присутствует трюк с переводом XML в текст, склейкой двух XML и обратный перевод в DOMDocument. XML шаблоны удалось сократить до двух:

fish.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>c8913980-8cbb-4a46-bdac-7fecd98c7ff8</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="{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></soapenv:Body></soapenv:Envelope>

xades_dom.xml:

<ds:Signature Id="xmldsig-{signature_id}" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><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><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}"><xades:SignedProperties Id="xmldsig-{signature_id}-signedprops"><xades:SignedSignatureProperties><xades:SigningTime>{signing_time}</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><DigestMethod xmlns="http://www.w3.org/2000/09/xmldsig#" Algorithm="urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34112012-256"/><DigestValue xmlns="http://www.w3.org/2000/09/xmldsig#">{digest2}</DigestValue></xades:CertDigest><xades:IssuerSerial><X509IssuerName xmlns="http://www.w3.org/2000/09/xmldsig#">{x509_issuer_name}</X509IssuerName><X509SerialNumber xmlns="http://www.w3.org/2000/09/xmldsig#">{x509_sn}</X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties></xades:QualifyingProperties></ds:Object></ds:Signature>    

И сам код скрипта:

<?php

$fish_file='in/fish.xml';       // xml - главный шаблон, откуда осуществляем сборку
$xades_file='in/xades_dom.xml';      // xml - шаблон подписи
$cert_file='keys/cert.key';     // Сертификат формата X590
$private_file='keys/priv.key';  // Закрытый ключ

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);
};

function md_gost_bin($text){
  file_put_contents("tmp.xml", $text);
  $res=cat tmp.xml | openssl dgst -binary -md_gost12_256>hash.bin;  
  return file_get_contents("hash.bin");
};

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);
}

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);
}

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);
}

function X509($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 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));
}

$dt=new DateTime();
$signing_time=$dt->format(DateTime::ATOM);
$signature_id=guidv4();  // идентификатор блока "подпись"
$signed_id=guidv4();     // идентификатор блока который подписываем

$fish_xml=file_get_contents($fish_file);
$dom = new DOMDocument('1.0', 'utf-8'); 
$dom->loadXML($fish_xml);
$root = $dom->documentElement;

$req_xml=$root->getElementsByTagName('exportDSRsRequest')->item(0);
$req_xml->setAttribute('Id',$signed_id);
$req_can=$req_xml->C14N(true);
$digest1=md_gost($req_can);
echo "req=$req_can\n";
echo "digest1=$digest1\n";
$digest2= md_gost(base64_decode(X509($cert_file)));
echo "digest2=$digest2\n";

$dom = new DOMDocument('1.0', 'utf-8'); 
$tmp_text_xml=$root->C14N(true);

// подмешиваем подпись
$xades= file_get_contents($xades_file);
$tmp_text_xml= str_replace('base:version="15.3.0.0">', 'base:version="15.3.0.0">'.$xades, $tmp_text_xml);

$tmp_text_xml= str_replace("{signing_time}", $signing_time, $tmp_text_xml);
$tmp_text_xml= str_replace("{signature_id}", $signature_id, $tmp_text_xml);
$tmp_text_xml= str_replace("{digest1}", $digest1, $tmp_text_xml);
$tmp_text_xml= str_replace("{digest2}", $digest2, $tmp_text_xml);
$tmp_text_xml= str_replace("{x509_issuer_name}", get_issuer($cert_file), $tmp_text_xml);
$tmp_text_xml= str_replace("{x509_sn}", get_serial($cert_file), $tmp_text_xml);


$dom->loadXML($tmp_text_xml);
$root = $dom->documentElement;

$digest3=md_gost($root->getElementsByTagName('SignedProperties')->item(0)->C14N(true));
echo "digest3=$digest3\n";

$dom = new DOMDocument('1.0', 'utf-8'); 
$tmp_text_xml=$root->C14N(true);
$tmp_text_xml= str_replace("{digest3}", $digest3, $tmp_text_xml);
$tmp_text_xml= str_replace("{x590_cert}", X509($cert_file), $tmp_text_xml);
$tmp_text_xml= str_replace("{signed_id}", $signed_id, $tmp_text_xml);

$dom->loadXML($tmp_text_xml);
$root = $dom->documentElement;

// подписываем
$for_sign=$root->getElementsByTagName('SignedInfo')->item(0)->C14N(true);
$hash_for_sign= md_gost_bin($for_sign);
$sign=get_signature($hash_for_sign,$private_file);
echo "sign=$sign\n";

$dom = new DOMDocument('1.0', 'utf-8'); 
$tmp_text_xml=$root->C14N(true);
$tmp_text_xml= str_replace("{signature_value}", $sign, $tmp_text_xml);

$dom->loadXML($tmp_text_xml);
$root = $dom->documentElement;


//
echo "---------------- ИТОГО ---------------\n";
file_put_contents("out.xml", $root->C14N(true));

Но увы и ах.. Результат его работы хоть с виду еще более нормальный, но ГИС ЖКХ так и продолжает возвращать ошибку:

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

Update 24.07.2025:

Всё получилось. Собака порылась в не верном преобразовании серийного номера в десятичный вид. Итоговый вид функции (правильный):

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);
    $res = echo "ibase=16;$res"|bc;
    $res= str_replace("\n", "", $res);
    return $res;
}

ГИС ЖКХ. Отправка и подпись сообщений на 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: тут немножко не верная реализация подписи. Нужно подписывать хэш данных, а у меня подписываются данные. Но пока без разницы — ошибка так и осталась.

PHP и каноникализация C14N

Цель канонизации — обеспечить, чтобы для одних и тех же данных на уровне XML не менялся хэш, необходимый для проверки подписи.  Т.е. удаляются лишние пробелы, символы и т.д. У себя реализовал примерно так:

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

echo CanonicalXML('<Envelope       xmlns:xsd="http://www.w3.org/2001/XMLSchema" ><Body>Привет всем!</Body  ></Envelope >');

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

Ранее уже описывал как поднять защищенный туннель с ГИС ЖКХ.Теперь настало время научится делать запросы. Для этого их необходимо подписывать. Подписывается тело сообщения 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

1С: Тип не определен (Addln.КартинкаШтрихкода.BarCode)

Такая ошибка возникает в 1С на Linux если не установлена библиотека libpng.so Чтоб не гадать что именно нужно доустановить, можно сделать так:

apt install libpng*

UPDATE: Иногда это не помогает, или ошибка возвращается на некоторых ПК спустя несколько дней. Почему, вопрос открытый. Грешу на несколько кривые корпоративные обновления ОС. Второй способ решить проблему: найти в конфигураторе макет «КомпонентаПечатиШтрихкодов» и загрузить в него последнюю версию компонента скачанного с сайта 1С (на 10.06.2025 это BarcodeLibNative_10_0_10_5.zip). Да, прямо бинарник, прямо архив.

1 7 8 9 10 11 310