Pull to refresh
570.91
Альфа-Банк
Лучший мобильный банк по версии Markswebb

СМЭВ 3. Электронная подпись сообщений на Java и КриптоПро

Reading time 16 min
Views 46K


Система межведомственного электронного взаимодействия (СМЭВ) задумывалась как цифровая среда предоставления услуг и исполнения государственных и муниципальных функций в электронной форме.

В настоящее время СМЭВ продолжает расширять свои возможности и вовлекать все большее количество участников взаимодействия.

Что оказалось как нельзя кстати, в том числе для коммерческих организаций, в частности банков, которые все больше стремятся перевести свои услуги в цифру и сериализовать процессы.

В этой статье мы поговорим о том, как своими силами подписать запросы и проверить электронные подписи ответов СМЭВ версии 3.0, и о паре интересных нюансов, с которыми пришлось при этом столкнуться.

Здравствуйте!

Может возникнуть вопрос. Почему своими силами? Когда для СМЭВ 3 есть целый Технологический портал, где

  • опубликована вся документация и методические указания,
  • есть раздел с часто задаваемыми вопросами,
  • можно скачать актуальную версию библиотек клиента СМЭВ 3,
  • предоставлены примеры полных конвертов сообщений с подписями,
  • можно даже проверить онлайн свое сообщение или из примера на соответствие схемам сервиса СМЭВ и на предмет валидности его электронной подписи

Все верно, портал, безусловно, крайне полезный, и всеми его подсказками и инструментами можно и нужно пользоваться, но вот код на Java напишем свой.

По той простой причине, что уже есть собственная информационная система, работающая с форматами электронной подписи XMLDSig, XAdES, в которой применяются библиотеки проекта Apache Santuario, реализующие основные стандарты безопасности для XML. А также библиотеки, входящие в состав КриптоПро JCSP, помимо работы с XML, обеспечивающие API криптографических функций СКЗИ КриптоПро CSP.

Написание собственных методов для работы с электронными подписями СМЭВ 3 в данном случае выглядит более целесообразно, нежели разворачивание полного клиента поставляемого:
ФГБУ НИИ «Восход» (до 21 марта 2016 года ФГУП НИИ «Восход») или интеграция, его отдельных классов и пакетов.

В то же время заглянуть в открытый код клиента всегда полезно, а его наличие само по себе говорит о зрелости системы и высоком уровне поддержки.

Анализ исходных данных


Загружаем с портала СМЭВ 3:


Если уже умеем формировать обычный XMLDSig или подписывать, например, конверты сообщений СМЭВ 2, то больше всего начинает интересовать, чем же отличается конверт с подписью СМЭВ 3 от СМЭВ 2.

Открываем пример конверта СМЭВ 3 SendRequestRequestNoAttach.xml

<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1">
   <S:Body>
      <ns2:SendRequestRequest xmlns:ns3="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/faults/1.1" xmlns:ns2="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1" xmlns="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/basic/1.1">
         <ns:SenderProvidedRequestData Id="SIGNED_BY_CONSUMER" xmlns="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1" xmlns:ns="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1" xmlns:ns2="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/basic/1.1">	<ns:MessageID>db0486d0-3c08-11e5-95e2-d4c9eff07b77</ns:MessageID><ns2:MessagePrimaryContent><ns1:BreachRequest xmlns:ns1="urn://x-artefacts-gibdd-gov-ru/breach/root/1.0"  xmlns:ns2="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0"  xmlns:ns3="urn://x-artefacts-smev-gov-ru/supplementary/commons/1.0.1" Id="PERSONAL_SIGNATURE"> <ns1:RequestedInformation> <ns2:RegPointNum>Т785ЕС57</ns2:RegPointNum> </ns1:RequestedInformation> <ns1:Governance> <ns2:Name>ГИБДД РФ</ns2:Name> <ns2:Code>GIBDD</ns2:Code> <ns2:OfficialPerson> <ns3:FamilyName>Загурский</ns3:FamilyName> <ns3:FirstName>Андрей</ns3:FirstName> <ns3:Patronymic>Петрович</ns3:Patronymic> </ns2:OfficialPerson></ns1:Governance> </ns1:BreachRequest> </ns2:MessagePrimaryContent>	<ns:TestMessage/></ns:SenderProvidedRequestData>
         <ns2:CallerInformationSystemSignature><ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411"/><ds:Reference URI="#SIGNED_BY_CONSUMER"><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/><ds:Transform Algorithm="urn://smev-gov-ru/xmldsig/transform"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#gostr3411"/><ds:DigestValue>/jXl70XwnttJB5sSokwh8SaVHwo2gjgILSu0qBaLUAo=</ds:DigestValue></ds:Reference></ds:SignedInfo><ds:SignatureValue>J3746ks34pOcPGQpKzc0sz3n9+gjPtzZbSEEs4c3sTwbtfdaY7N/hxXzEIvXc+3ad9bc35Y8yBhZ/BYbloGt+Q==</ds:SignatureValue><ds:KeyInfo><ds:X509Data><ds:X509Certificate>MIIBcDCCAR2gAwIBAgIEHVmVKDAKBgYqhQMCAgMFADAtMRAwDgYDVQQLEwdTWVNURU0xMQwwCgYDVQQKEwNJUzIxCzAJBgNVBAYTAlJVMB4XDTE1MDUwNzEyMTUzMFoXDTE4MDUwNjEyMTUzMFowLTEQMA4GA1UECxMHU1lTVEVNMTEMMAoGA1UEChMDSVMyMQswCQYDVQQGEwJSVTBjMBwGBiqFAwICEzASBgcqhQMCAiMBBgcqhQMCAh4BA0MABEDoWGZlTUWD43G1N7TEm14+QyXrJWProrzoDoCJRem169q4bezFOUODcNooQJNg3PtAizkWeFcX4b93u8fpVy7RoyEwHzAdBgNVHQ4EFgQUaRG++MAcPZvK/E2vR1BBl5G7s5EwCgYGKoUDAgIDBQADQQCg25vA3RJL3kgcJhVOHA86vnkMAtZYr6HBPa7LpEo0HJrbBF0ygKk50app1lzPdZ5TtK2itfmNgTYiuQHX3+nE</ds:X509Certificate></ds:X509Data></ds:KeyInfo></ds:Signature></ns2:CallerInformationSystemSignature>
      </ns2:SendRequestRequest>
   </S:Body>
</S:Envelope>


Дедуктивным методом выясняется что:
  • больше не используется прием с выносом из содержимого тега Signature в Security заголовок элемента BinarySecurityToken с сертификатом открытого ключа проверки электронной подписи и ссылкой на него через SecurityTokenReference в теле самого Signature, как, например, в СМЭВ 2.4.6. Теперь сертификат должен находиться внутри Signature.
  • второе и, по сути, самое существенное и важное изменение, оказывающее большое влияние на процесс подписи — это добавление новой проприетарной трансформации:

    <ds:Transform Algorithm="urn://smev-gov-ru/xmldsig/transform"/>


Через эту трансформацию распространяются собственные правила каноникализации СМЭВ 3.

Каноникализация — процесс приведения данных, имеющих несколько возможных форм представления, к одному нормализованному стандартному виду.

Перед тем как посчитать хэш подписываемого атрибута в XML-конверте и подписать, необходимо выполнить его конвертацию в заданный правилами СМЭВ 3 вид.

В поисках описания трансформации urn://smev-gov-ru/xmldsig/transform открываем Методические рекомендации 3.4.0.3

Знакомимся с пунктом 4.4.2.1 Правила формирования электронной подписи сообщений

Формат подписи XMLDSig detached (https://www.w3.org/TR/xmldsig-core/)

Трансформация, дополнительно к канонизации urn://smev-gov-ru/xmldsig/transform

Требования к форматированию В XML-структуре подписи между элементами не допускается наличие текстовых узлов, в том числе переводов строки.

Пункт Методических указаний 12.4. ПРИЛОЖЕНИЕ 4: ОБРАЗЦОВАЯ РЕАЛИЗАЦИЯ ТРАНСФОРМАЦИИ URN://SMEV-GOV-RU/XMLDSIG/TRANSFORM
содержит Java класс SmevTransformSpi.java, реализующий алгоритм трансформации «urn://smev-gov-ru/xmldsig/transform», наследник org.apache.xml.security.transforms.TransformSpi из библиотеки Apache Santuario.

Таким образом, чтобы обеспечить каноникализацию подписываемого конверта СМЭВ 3, можно использовать в своем коде этот класс трансформации.

Единственным условием и ограничением в этом случае будет, что для обработки XML-документа при формировании подписи или ее проверки нужно использовать именно org.apache.xml.security.signature.XMLSignature из проекта Apache Santuario.

Задействовать инструменты из пакетов javax.xml.crypto.dsig или ru.CryptoPro.JCPxml.xmldsig просто так уже не получится.

Подготовка к подписи по правилам СМЭВ 3


Apache Santuario изначально ничего не знает про ГОСТ криптографические алгоритмы и СКЗИ КриптоПро.

В библиотеке xmlsec-1.5.0.jar в файле \org\apache\xml\security\resource\config.xml содержатся настройки только для работы с зарубежными криптографическими алгоритмами.

Чтобы он начал распознавать и применять ГОСТ, нужно выполнить его инициализацию.

По старинке это делалось так:

//APACHE-SANTUARIO INIT WITH CryptoPro JCP
        System.setProperty("org.apache.xml.security.resource.config", "resource/jcp.xml");
        org.apache.xml.security.Init.init();
        String cfile1 = System.getProperty("org.apache.xml.security.resource.config");
        LOGGER.log(Level.INFO, "Init class URL: " + org.apache.xml.security.Init.class.getProtectionDomain().getCodeSource().getLocation());
        LOGGER.log(Level.INFO, cfile1);

В новых версиях КриптоПро JCP (JCSP) инициализацию выполнит одна строчка:

ru.CryptoPro.JCPxml.xmldsig.JCPXMLDSigInit.init();

Теперь нужно Apache Santuario научить новым правилам трансформации, которые диктует СМЭВ 3. Для этого регистрируем класс трансформации:

  try {
                Transform.register(SmevTransformSpi.ALGORITHM_URN, SmevTransformSpi.class.getName());
                santuarioIgnoreLineBreaks(true);
                LOGGER.log(Level.INFO, "SmevTransformSpi has been initialized");
            } catch (AlgorithmAlreadyRegisteredException e) {
                LOGGER.log(Level.INFO, "SmevTransformSpi Algorithm already registered: " + e.getMessage());
            } 

Заодно сразу выполняем требование из Методических указаний:

Требования к форматированию В XML-структуре подписи между элементами не допускается наличие текстовых узлов, в том числе переводов строки.

santuarioIgnoreLineBreaks(true);

    private static final String IGNORE_LINE_BREAKS_FIELD = "ignoreLineBreaks";

/**
     * Apache Santuario privileged switch IgnoreLineBreaks property
     * 
     * @param mode
     */
    private void santuarioIgnoreLineBreaks(Boolean mode) {
        try {
            Boolean currMode = mode;
            AccessController.doPrivileged(new PrivilegedExceptionAction<Boolean>() {
                
                public Boolean run() throws Exception {
                    Field f = XMLUtils.class.getDeclaredField(IGNORE_LINE_BREAKS_FIELD);
                    f.setAccessible(true);
                    f.set(null, currMode);
                    return false;
                }
            });
            
        } catch (Exception e) {
            LOGGER.warning("santuarioIgnoreLineBreaks " + ExceptionUtils.getFullStackTrace(e));
        }
    }

Делается это в привилегированном блоке AccessController.doPrivileged
и через reflection, из-за особенности реализации свойства ignoreLineBreaks в Santuario.

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

System.setProperty("org.apache.xml.security.ignoreLineBreaks", "true");

не работает.

Через настройку опции JVM:

-Dcom.sun.org.apache.xml.internal.security.ignoreLineBreaks=true

работает.

Если взглянуть на код класса org.apache.xml.security.utils.XMLUtils, то можно увидеть, что поле ignoreLineBreaks статическое, инициализируется в привилегированном блоке из системного свойства «org.apache.xml.security.ignoreLineBreaks».

private static boolean ignoreLineBreaks =
        AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            public Boolean run() {
                return Boolean.valueOf(Boolean.getBoolean
                    ("org.apache.xml.security.ignoreLineBreaks"));
            }
        }).booleanValue();
public static boolean ignoreLineBreaks() {
        return ignoreLineBreaks;
    }

Такая реализация приводит к невозможности гибко настроить в одном Java процессе для части методов игнорировать перевод строк, а для другой части не игнорировать.

Т.е., если одно приложение выполняет подписи XMLDsig, СМЭВ 2 и СМЭВ 3, все XML документы, обработанные Santuario должны на выходе лишиться перевода строк.

С этим свойством, конечно, возникает вопрос к Apache Santuario:



Подпись сообщений СМЭВ 3


Для подписи документов СМЭВ 3 все готово.

Код подписания выглядит следующим образом:

private static final String XMLDSIG_MORE_GOSTR34102001_GOSTR3411 = "http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411";
    private static final String XMLDSIG_MORE_GOSTR3411 = "http://www.w3.org/2001/04/xmldsig-more#gostr3411";
    private static final String CANONICALIZATION_METHOD = "http://www.w3.org/2001/10/xml-exc-c14n#";
    private static final String DS_SIGNATURE = "//ds:Signature";
    private static final String SIG_ID = "sigID";
    private static final String COULD_NOT_FIND_XML_ELEMENT_NAME = "ERROR! Could not find xmlElementName = ";
    private static final String GRID = "#";
    private static final String XML_SIGNATURE_ERROR = "xmlDSignature ERROR: ";

try {
            // инициализация объекта чтения XML-документа
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            
            // установка флага, определяющего игнорирование пробелов в
            // содержимом элементов при обработке XML-документа
            dbf.setIgnoringElementContentWhitespace(true);
            
            // установка флага, определяющего преобразование узлов CDATA в
            // текстовые узлы при обработке XML-документа
            dbf.setCoalescing(true);
            
            // установка флага, определяющего поддержку пространств имен при
            // обработке XML-документа
            dbf.setNamespaceAware(true);
            
// загрузка содержимого подписываемого документа на основе
            // установленных флагами правил из массива байтов data            DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
         
           Document doc = documentBuilder.parse(new ByteArrayInputStream(data));
            
            /*
             * Добавление узла подписи <ds:Signature> в загруженный XML-документ
             */
            
            // алгоритм подписи (ГОСТ Р 34.10-2001)
            final String signMethod = XMLDSIG_MORE_GOSTR34102001_GOSTR3411;
            
            // алгоритм хеширования, используемый при подписи (ГОСТ Р 34.11-94)
            final String digestMethod = XMLDSIG_MORE_GOSTR3411;
            
            final String canonicalizationMethod = CANONICALIZATION_METHOD;
            
         
            String[][] filters = {{XPath2FilterContainer.SUBTRACT, DS_SIGNATURE}};
            String sigId = SIG_ID;
                
                // инициализация объекта формирования ЭЦП в соответствии с
                // алгоритмом ГОСТ Р 34.10-2001
                XMLSignature sig = new XMLSignature(doc, "", signMethod, canonicalizationMethod);
                
                // определение идентификатора первого узла подписи
               
                sig.setId(sigId);
                
                // получение корневого узла XML-документа
                Element anElement = null;
                if (xmlElementName == null) {
                    anElement = doc.getDocumentElement();
                } else {
                    NodeList nodeList = doc.getElementsByTagName(xmlElementName);
                    anElement = (Element) nodeList.item(0);
                }
                // = doc.getElementById("#AppData");
                // добавление в корневой узел XML-документа узла подписи
                if (anElement != null) {
                    anElement.appendChild(sig.getElement());
                } else {
                    throw new SignatureProcessorException(COULD_NOT_FIND_XML_ELEMENT_NAME + xmlElementName);
                }
                
                /*
                 * Определение правил работы с XML-документом и добавление в узел подписи этих
                 * правил
                 */
                
                // создание узла преобразований <ds:Transforms> обрабатываемого
                // XML-документа
                Transforms transforms = new Transforms(doc);
                
                // добавление в узел преобразований правил работы с документом
                // transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
                transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS);
                transforms.addTransform(SmevTransformSpi.ALGORITHM_URN);
                
                // добавление в узел подписи ссылок (узла <ds:Reference>),
                // определяющих правила работы с
                // XML-документом (обрабатывается текущий документ с заданными в
                // узле <ds:Transforms> правилами
                // и заданным алгоритмом хеширования)
                sig.addDocument(xmlElementID == null ? "" : GRID + xmlElementID, transforms, digestMethod);
                
                /*
                 * Создание подписи всего содержимого XML-документа на основе закрытого ключа,
                 * заданных правил и алгоритмов
                 */
                
                // создание внутри узла подписи узла <ds:KeyInfo> информации об
                // открытом ключе на основе
                // сертификата
                sig.addKeyInfo(x509Cert);
                
                // создание подписи XML-документа
                sig.sign(privateKey);
            
            // определение потока, в который осуществляется запись подписанного
            // XML-документа
            bais = new ByteArrayOutputStream();
            
            // инициализация объекта копирования содержимого XML-документа в
            // поток
            TransformerFactory tf = TransformerFactory.newInstance();
            
            // создание объекта копирования содержимого XML-документа в поток
            Transformer trans = tf.newTransformer();
            
            // копирование содержимого XML-документа в поток
            trans.transform(new DOMSource(doc), new StreamResult(bais));
            bais.close();
        } catch (TransformationException e) {
            throw new SignatureProcessorException("TransformationException " + XML_SIGNATURE_ERROR + e.getMessage());
        } catch (XMLSignatureException e) {
            throw new SignatureProcessorException("XMLSignatureException " + XML_SIGNATURE_ERROR + e.getMessage());
        } catch (TransformerException e) {
            throw new SignatureProcessorException("TransformerException " + XML_SIGNATURE_ERROR + e.getMessage());
        } catch (IOException e) {
            throw new SignatureProcessorException("IOException " + XML_SIGNATURE_ERROR + e.getMessage());
        } catch (XMLSecurityException e) {
            throw new SignatureProcessorException("XMLSecurityException " + XML_SIGNATURE_ERROR + e.getMessage());
        } catch (SAXException e) {
            throw new SignatureProcessorException("SAXException " + XML_SIGNATURE_ERROR + e.getMessage());
        } catch (ParserConfigurationException e) {
            throw new SignatureProcessorException(
                    "ParserConfigurationException " + XML_SIGNATURE_ERROR + e.getMessage());
        }
        return bais.toByteArray();

Основными параметрами здесь являются:

byte[] data, // XML сообщение в виде массива байтов

String xmlElementName, // имя элемента в XML вместе с префиксом, в который следует добавить подпись, для СМЭВ-3 в общем случае "ns2:CallerInformationSystemSignature"

String xmlElementID // ID элемента в XML (если присутствует) вместе с префиксом, на который следует поставить подпись, для СМЭВ-3 в общем случае "SIGNED_BY_CONSUMER"

X509Certificate certificate // сертификат открытого ключа проверки подписи

PrivateKey privateKey // закрытый ключ подписи


Проверка подписи сообщения СМЭВ 3


Код проверки подписи выглядит следующим образом:

private static final QName QNAME_SIGNATURE = new QName("http://www.w3.org/2000/09/xmldsig#", "Signature", "ds");
    private static final String SIGNATURE_NOT_FOUND = "Signature not found!";
    private static final String SIGNATURE_NOT_VALID = "Signature not valid";
    private static final String SMEV_SIGNATURE_PASSED_CORE_VALIDATION = "SmevSignature passed core validation";
    private static final String VERIFY_SIGNATURE_ON_XML_IO_EXCEPTION = "Verify signature on XML IOException: ";
    private static final String VERIFY_SIGNATURE_ON_XML_PARSER_CONFIGURATION_EXCEPTION = "Verify signature on XML ParserConfigurationException: ";
    private static final String VERIFY_SIGNATURE_ON_XML_SAX_EXCEPTION = "Verify signature on XML SAXException: ";
    private static final String VERIFY_SIGNATURE_ON_XML_XML_SIGNATURE_EXCEPTION = "Verify signature on XML XMLSignatureException: ";
    private static final String VERIFY_SIGNATURE_ON_XML_XML_SECURITY_EXCEPTION = "Verify signature on XML XMLSecurityException: ";
    private static final String ID = "Id";

   boolean coreValidity = true;
        try {
            DocumentBuilderFactory bf = DocumentBuilderFactory.newInstance();
            bf.setNamespaceAware(true);
            DocumentBuilder b = bf.newDocumentBuilder();
            Document doc = b.parse(new InputSource(new ByteArrayInputStream(signedXmlData)));
            
            NodeList sigs = doc.getElementsByTagNameNS(QNAME_SIGNATURE.getNamespaceURI(), QNAME_SIGNATURE.getLocalPart());
            org.apache.xml.security.signature.XMLSignature sig = null;
            sigSearch: {
                for (int i = 0; i < sigs.getLength(); i++) {
                    Element sigElement = (Element) sigs.item(i);
                    String sigId = sigElement.getAttribute(ID);
                    if (sigId != null) {
                        sig = new org.apache.xml.security.signature.XMLSignature(sigElement, "");
                        break sigSearch;
                    }
                }
                throw new XMLSignatureVerificationException(SIGNATURE_NOT_FOUND);
            }
            org.apache.xml.security.keys.KeyInfo ki = (org.apache.xml.security.keys.KeyInfo) sig.getKeyInfo();
            
            X509Certificate certificate = ki.getX509Certificate();
            
            if (!sig.checkSignatureValue(certificate.getPublicKey())) {
                coreValidity = false;
                LOGGER.log(Level.INFO, SIGNATURE_NOT_VALID);
            } else {
                LOGGER.log(Level.INFO, String.format(SMEV_SIGNATURE_PASSED_CORE_VALIDATION));
            }
            
        } catch (IOException e) {
            throw new XMLSignatureVerificationException(VERIFY_SIGNATURE_ON_XML_IO_EXCEPTION + ExceptionUtils.getStackTrace(e));
        } catch (ParserConfigurationException e) {
            throw new XMLSignatureVerificationException(VERIFY_SIGNATURE_ON_XML_PARSER_CONFIGURATION_EXCEPTION + ExceptionUtils.getStackTrace(e));
        } catch (SAXException e) {
            throw new XMLSignatureVerificationException(VERIFY_SIGNATURE_ON_XML_SAX_EXCEPTION + ExceptionUtils.getStackTrace(e));
        } catch (org.apache.xml.security.signature.XMLSignatureException e) {
            throw new XMLSignatureVerificationException(VERIFY_SIGNATURE_ON_XML_XML_SIGNATURE_EXCEPTION + ExceptionUtils.getStackTrace(e));
        } catch (XMLSecurityException e) {
            throw new XMLSignatureVerificationException(VERIFY_SIGNATURE_ON_XML_XML_SECURITY_EXCEPTION + ExceptionUtils.getStackTrace(e));
        }
        
        return coreValidity;


Проблемы. Хэш не совпадает


Внимание!



Для отладки использовался пример конверта СМЭВ 3 SendRequestRequestNoAttach.xml
Из него был удален элемент ds:Signature с целью подписать сообщение заново и сверить с оригиналом.

Несмотря на то, что метод подписи и трансформация SmevTransformSpi, взятая из Методических указаний, отрабатывали, на выходе был подписанный документ, подпись которого при онлайн-проверке на портале СМЭВ 3 трактовалась как

ЭП-ОВ не подтверждена: Ошибка проверки ЭП: Нарушена целостность ЭП

Почему

<ds:DigestValue>e76oVeYGapFDE+PV6glsj0XDjLHydLMd0cSkFPY8fWk=</ds:DigestValue>

не совпадал с оригинальным примером:

<ds:DigestValue>/jXl70XwnttJB5sSokwh8SaVHwo2gjgILSu0qBaLUAo==</ds:DigestValue>

Для диагностики причин в класс SmevTransformSpi в метод process был добавлен свой XMLEventWriter.

ByteArrayOutputStream baos = new ByteArrayOutputStream();
XMLEventWriter bdst =
outputFactory.get().createXMLEventWriter(baos, ENCODING_UTF_8); 

для параллельного анализа всех этапов трансформации.

Нормализованный элемент XML, на который требуется поставить подпись, выглядел следующим образом:

<ns1:SenderProvidedRequestData xmlns:ns1="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1" Id="SIGNED_BY_CONSUMER"><ns1:MessageID>db0486d0-3c08-11e5-95e2-d4c9eff07b77</ns1:MessageID><ns2:MessagePrimaryContent xmlns:ns2="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/basic/1.1"><ns3:BreachRequest xmlns:ns3="urn://x-artefacts-gibdd-gov-ru/breach/root/1.0" Id="PERSONAL_SIGNATURE"><ns3:RequestedInformation><ns4:RegPointNum xmlns:ns4="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0">Т785ЕС57</ns4:RegPointNum></ns3:RequestedInformation><ns3:Governance><ns4:Name>ГИБДД РФ</ns4:Name><ns4:Code>GIBDD</ns4:Code><ns4:OfficialPerson><ns5:FamilyName xmlns:ns5="urn://x-artefacts-smev-gov-ru/supplementary/commons/1.0.1">Загурский</ns5:FamilyName><ns5:FirstName>Андрей</ns5:FirstName><ns5:Patronymic>Петрович</ns5:Patronymic></ns4:OfficialPerson></ns3:Governance></ns3:BreachRequest></ns2:MessagePrimaryContent><ns1:TestMessage></ns1:TestMessage></ns1:SenderProvidedRequestData>

Поиск решения показал, что, во-первых форум КриптоПро, нормализованный документ может выглядеть на самом деле иначе и соответственно его хэш будет другой и возможно правильный.

Во-вторых, привел в GitHub, где был выложен класс SmevTransformSpi более старой версии.

Старая версия класса трансформации выдала следующий нормализованный документ:

<ns1:SenderProvidedRequestData xmlns:ns1="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/1.1" Id="SIGNED_BY_CONSUMER"><ns1:MessageID>db0486d0-3c08-11e5-95e2-d4c9eff07b77</ns1:MessageID><ns2:MessagePrimaryContent xmlns:ns2="urn://x-artefacts-smev-gov-ru/services/message-exchange/types/basic/1.1"><ns3:BreachRequest xmlns:ns3="urn://x-artefacts-gibdd-gov-ru/breach/root/1.0" Id="PERSONAL_SIGNATURE"><ns3:RequestedInformation><ns4:RegPointNum xmlns:ns4="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0">Т785ЕС57</ns4:RegPointNum></ns3:RequestedInformation><ns3:Governance><ns5:Name xmlns:ns5="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0">ГИБДД РФ</ns5:Name><ns6:Code xmlns:ns6="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0">GIBDD</ns6:Code><ns7:OfficialPerson xmlns:ns7="urn://x-artefacts-gibdd-gov-ru/breach/commons/1.0"><ns8:FamilyName xmlns:ns8="urn://x-artefacts-smev-gov-ru/supplementary/commons/1.0.1">Загурский</ns8:FamilyName><ns9:FirstName xmlns:ns9="urn://x-artefacts-smev-gov-ru/supplementary/commons/1.0.1">Андрей</ns9:FirstName><ns10:Patronymic xmlns:ns10="urn://x-artefacts-smev-gov-ru/supplementary/commons/1.0.1">Петрович</ns10:Patronymic></ns7:OfficialPerson></ns3:Governance></ns3:BreachRequest></ns2:MessagePrimaryContent><ns1:TestMessage></ns1:TestMessage></ns1:SenderProvidedRequestData>

С ним хэш стал совпадать, а подпись успешно проходить валидацию.

Сравнение версий класса SmevTransformSpi показала, что помимо добавленных в новой реализации дополнительных функций логирования и диагностики в debug режиме:

if (logger.isDebugEnabled()) {
                debugStream = new DebugOutputStream(argDst);
                dst = outputFactory.get().createXMLEventWriter(debugStream, ENCODING_UTF_8);
            } else {
                dst = outputFactory.get().createXMLEventWriter(argDst, ENCODING_UTF_8);
            }

Класс из Методических указаний не содержит нужную строчку, или содержит опечатку:



Отсутствует строка:

prefixMappingStack.pop();

, которая удаляет первый объект из стека с префиксами

  Stack<List<Namespace>> prefixMappingStack = new Stack<List<Namespace>>();

, что приводило к неверной работе SmevTransformSpi.

Добавление этой строки в новую версию SmevTransformSpi.java решило проблему.

Работающий класс трансформации и конверт с подписью можно посмотреть в github.com/VBurmistrov/Smev3

Результаты


Подписание конвертов СМЭВ 3 выполняется успешно.

Сообщения проходят проверку на портале Электронного правительства Госуслуги



И в собственном приложении:

Tags:
Hubs:
+26
Comments 16
Comments Comments 16

Articles

Information

Website
digital.alfabank.ru
Registered
Founded
1990
Employees
over 10,000 employees
Location
Россия
Representative
София Никитина