/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * Copyright (c) 2013, MPL CodeInside http://codeinside.ru */ package ru.codeinside.gws3572c; import com.sun.org.apache.xpath.internal.XPathAPI; import junit.framework.Assert; import org.apache.commons.lang.time.DateUtils; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.utils.Base64; import org.apache.xml.security.utils.Constants; import org.junit.Before; import org.junit.Test; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import ru.codeinside.gws.api.ClientRequest; import ru.codeinside.gws.crypto.cryptopro.CryptoProvider; import ru.codeinside.gws.stubs.DummyContext; import xmltype.R; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.soap.SOAPBody; import javax.xml.soap.SOAPMessage; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.security.PublicKey; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.text.ParseException; public class GMPClientSignTest { private GMPClient3572 client; private DocumentBuilder documentBuilder; @Before public void setUp() throws Exception { client = new GMPClient3572(); CryptoProvider cryptoProvider = new CryptoProvider(); client.bindCryptoProvider(cryptoProvider); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(true); documentBuilder = dbf.newDocumentBuilder(); } private DummyContext createContext() throws ParseException { DummyContext ctx = new DummyContext(); ctx.setVariable("payerType" , "1"); ctx.setVariable("payerPersonDocumentID1" , "12345678901"); ctx.setVariable("postBlockIdRequest", "13454"); // идентификатор запроса ctx.setVariable("postBlockSenderIdentifier", "20091d"); // идентификатор отправителя ctx.setVariable("ordinalNumber", "013400000011"); // порядковый номер заявки ctx.setVariable("postBlockTimeStamp", DateUtils.parseDate("25.07.2012 09:40:47", new String[]{"dd.MM.yyyy HH:mm:ss"})); // дата составления запроса ctx.setVariable("supplierOrgInfoName", "Управление Федеральной миграционной службы по Республике Татарстан"); ctx.setVariable("supplierOrgInfoINN", "1655102196"); ctx.setVariable("supplierOrgInfoKPP", "165501001"); ctx.setVariable("accountAccount", "40101810800000010001"); ctx.setVariable("bankBIK", "049205001"); ctx.setVariable("bankName", "ГРКЦ НБ РТ г. Казани"); ctx.setVariable("chargeSupplierBillID", "19255500000000000079"); // Уникальный идентификатор счёта в ИСП ctx.setVariable("chargeBillDate", DateUtils.parseDate( "10.03.2011", new String[]{"dd.MM.yyyy"})); //Дата выставления счёта ctx.setVariable("chargeBillFor", "Госпошлина за выдачу загранпаспорта"); //Дата выставления счёта ctx.setVariable("chargeTotalAmount", "1000,00"); ctx.setVariable("chargeChangeStatus", "1"); /* Статус счёта 1 - новый 2 - изменение 3 - аннулирование */ ctx.setVariable("chargeTreasureBranch", "УФК по Республике Татарстан"); ctx.setVariable("chargeKBK", "19210806000011000110"); ctx.setVariable("chargeOKATO", "92401000000"); ctx.setVariable("chargeApplicationID", "455555"); ctx.setVariable("chargeUnifiedPayerIdentifier", "0100000000006667775555643"); ctx.setVariable("budgetIndexStatus", "0"); ctx.setVariable("budgetPaymentType", "0"); ctx.setVariable("budgetPurpose", "0"); ctx.setVariable("budgetTaxPeriod", "0"); ctx.setVariable("budgetTaxDocNumber", "0"); ctx.setVariable("budgetTaxDocDate", "0"); ctx.setVariable("operationType", "importCharge"); return ctx; } /* Проверяется подпись сущности запроса на импорт. */ @Test public void testSignForEntity() throws Exception { ClientRequest request = client.createClientRequest(createContext()); InputSource is = new InputSource(new StringReader(request.appData)); Document doc = documentBuilder.parse(is); // Element elementForSign = (Element) doc.getElementsByTagNameNS(null, "Charge").item(0); // // Node parentNode; Document detachedDocument; // if (!elementForSign.isSameNode(doc.getDocumentElement())) { // parentNode = elementForSign.getParentNode(); // parentNode.removeChild(elementForSign); // // detachedDocument = documentBuilder.newDocument(); // Node importedElementForSign = detachedDocument.importNode(elementForSign, true); // detachedDocument.appendChild(importedElementForSign); // } else { detachedDocument = doc; // } Element nscontext = detachedDocument.createElementNS(null, "namespaceContext"); nscontext.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:" + "ds".trim(), "http://www.w3.org/2000/09/xmldsig#"); Element certificateElement = (Element) XPathAPI.selectSingleNode(detachedDocument, "//ds:X509Certificate[1]", nscontext); Element sigElement = (Element) certificateElement.getParentNode().getParentNode().getParentNode(); XMLSignature signature = new XMLSignature(sigElement, ""); CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate certKey = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(Base64.decode(certificateElement.getTextContent().trim().getBytes()))); Assert.assertNotNull("There are no information about public key. Verification couldn't be implemented", certKey); Assert.assertTrue("Signature is not valid", signature.checkSignatureValue(certKey)); } /* проверяется подпись от примера присланного казначейством */ @Test public void validateGMPSample() throws Exception { SOAPMessage sampleDoc = R.getSoapResource("gmp/gmd_sample.xml"); SOAPBody soapBody = sampleDoc.getSOAPBody(); Element unifoTransferMsg = first(soapBody, null, "UnifoTransferMsg"); Assert.assertNotNull(unifoTransferMsg); Element messageData = first(unifoTransferMsg, null, "MessageData"); Assert.assertNotNull(messageData); Element appData = first(messageData, null, "AppData"); Assert.assertNotNull(appData); Element importData = first(appData, null, "ImportData"); Assert.assertNotNull(importData); Element importRequest = first(importData, "http://roskazna.ru/xsd/PGU_ImportRequest", "ImportRequest"); Assert.assertNotNull("importRequest not found", importRequest); Element charge = first(importRequest, null, "Charge"); Assert.assertNotNull("charge not found", charge); Document docEntity = createDocumentFromElement(charge); Assert.assertNotNull("doc from charge null", docEntity); Assert.assertTrue("Документ не прошел валидацию", signDocVer(docEntity)); } private Document createDocumentFromElement(Element element) throws ParserConfigurationException, IOException, SAXException { final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // установка флага, определяющего игнорирование пробелов в содержимом элементов при обработке XML-документа dbf.setIgnoringElementContentWhitespace(true); // установка флага, определяющего преобразование узлов CDATA в текстовые узлы при обработке XML-документа dbf.setCoalescing(true); // установка флага, определяющего поддержку пространств имен при обработке XML-документа dbf.setNamespaceAware(true); InputSource is = new InputSource(new StringReader(convertElementToString(element))); return dbf.newDocumentBuilder().parse(is); } /** * Проверка подписи всего XML-документа для алгоритма ГОСТ Р 34.10-2001. * * @throws Exception / */ boolean signDocVer(Document doc) throws Exception { /* Чтение узла подписи <ds:Signature> из XML-документа */ // чтение из загруженного документа содержимого пространства имени Signature final Element nscontext = doc.createElementNS(null, "namespaceContext"); nscontext.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:" + "ds".trim(), Constants.SignatureSpecNS); // выбор из прочитанного содержимого пространства имени узла подписи <ds:Signature> final Element sigElement = (Element) XPathAPI .selectSingleNode(doc, "//ds:Signature[1]", nscontext); /* Проверка подписи XML-документа на основе информации об открытом ключе, хранящейся в XML-документе */ // инициализация объекта проверки подписи final XMLSignature signature = new XMLSignature(sigElement, ""); // чтение узла <ds:KeyInfo> информации об открытом ключе final KeyInfo ki = signature.getKeyInfo(); // чтение сертификата их узла информации об открытом ключе final X509Certificate certKey = ki.getX509Certificate(); // если сертификат найден, то осуществляется проверка // подписи на основе сертфиката if (certKey != null) { return signature.checkSignatureValue(certKey); } // в противном случае осуществляется проверка на открытом ключе else { // чтение открытого ключа из узла информации об открытом ключе final PublicKey pk = ki.getPublicKey(); // если открытый ключ найден, то на нем осуществляется проверка подписи if (pk != null) { return signature.checkSignatureValue(pk); } // в противном случае проверка не может быть выполнена else throw new Exception( "There are no information about public key. Verification couldn't be implemented"); } } private static String convertElementToString(Element signElement) { Transformer transformer; try { transformer = TransformerFactory.newInstance().newTransformer(); // transformer.setOutputProperty(OutputKeys.INDENT, "yes"); StreamResult result = new StreamResult(new StringWriter()); DOMSource source = new DOMSource(signElement); transformer.transform(source, result); return result.getWriter().toString(); } catch (Exception e) { return null; } } private static Element first(final Node parent, final String uri, final String localName) { final NodeList nodes = parent.getChildNodes(); final int n = nodes.getLength(); for (int i = 0; i < n; i++) { final Node node = nodes.item(i); if (node instanceof Element) { final Element element = (Element) node; if (localName.equals(element.getLocalName()) && (uri == null || uri.equals(element.getNamespaceURI()))) { return element; } } } return null; } }