/* * 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.gws.crypto.cryptopro; import com.sun.org.apache.xpath.internal.XPathAPI; import junit.framework.Assert; import org.apache.xml.security.algorithms.MessageDigestAlgorithm; import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xml.security.keys.KeyInfo; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.transforms.Transforms; import org.apache.xml.security.utils.Constants; 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 javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; 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.StringReader; import java.io.StringWriter; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.logging.Logger; /** * Формирование и проверка подписи всего XML-документа для алгоритма ГОСТ Р * 34.10-2001. * * @author Copyright 2004-2007 Crypto-Pro. All rights reserved. * @.Version */ public class SignDoc { @Test public void mainTest() { try { final String signMethod = XMLSignature.ALGO_ID_SIGNATURE_GOST_GOST3410_3411; // алгоритм хеширования, используемый при подписи (ГОСТ Р 34.11-94) final String digestMethod = MessageDigestAlgorithm.ALGO_ID_DIGEST_GOST3411; /* В первую очередь осуществляет регистрация алгоритма подписи ГОСТ Р 34.10-2001*/ //JCPXMLDSigInit.init(); CryptoProvider.loadCertificate(); Logger.getLogger("LOG").info("sign doc begin"); String result = signDoc(signMethod, digestMethod, R.getTextResource("gmp/tests.xml") ); Logger.getLogger("LOG").info("Result \n\n\n\n" + result); Logger.getLogger("LOG").info("sign doc end\nsign doc verify"); Assert.assertTrue(signDocVer(result)); } catch (Exception e) { e.printStackTrace(); } } /** * Формирование подписи всего XML-документа для алгоритма ГОСТ Р 34.10-2001. * * * * * @param signMethod алгоритм подписи (ГОСТ Р 34.10-2001) * @param digestMethod алгоритм хеширования, используемый при подписи (ГОСТ Р * 34.11-94) * @param testDoc подписываемый документ * @throws Exception e */ String signDoc(String signMethod, String digestMethod, String testDoc) throws Exception { // получение закрытого ключа final PrivateKey privateKey = CryptoProvider.privateKey; // генерирование X509-сертификата из закодированного представления сертификата final X509Certificate cert = CryptoProvider.cert; /* Загружаем подписываемый XML-документ из файла */ // инициализация объекта чтения XML-документа final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // установка флага, определяющего игнорирование пробелов в содержимом элементов при обработке XML-документа dbf.setIgnoringElementContentWhitespace(true); // установка флага, определяющего преобразование узлов CDATA в текстовые узлы при обработке XML-документа dbf.setCoalescing(true); // установка флага, определяющего поддержку пространств имен при обработке XML-документа dbf.setNamespaceAware(true); // загрузка содержимого подписываемого документа на основе установленных флагами правил final DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(testDoc)); final Document doc = documentBuilder.parse(is); /* Добавление узла подписи <ds:Signature> в загруженный XML-документ */ // инициализация объекта формирования ЭЦП в соответствии с алгоритмом ГОСТ Р 34.10-2001 final XMLSignature sig = new XMLSignature(doc, "", signMethod); // получение корневого узла XML-документа final Element anElement = doc.getDocumentElement(); // добавление в корневой узел XML-документа узла подписи anElement.appendChild(sig.getElement()); /* Определение правил работы с XML-документом и добавление в узел подписи этих правил */ // создание узла преобразований <ds:Transforms> обрабатываемого XML-документа final Transforms transforms = new Transforms(doc); // добавление в узел преобразований правил работы с документом transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); // transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS); transforms.addTransform(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); // добавление в узел подписи ссылок (узла <ds:Reference>), определяющих правила работы с // XML-документом (обрабатывается текущий документ с заданными в узле <ds:Transforms> правилами // и заданным алгоритмом хеширования) sig.addDocument("", transforms, digestMethod); /* Создание подписи всего содержимого XML-документа на основе закрытого ключа, заданных правил и алгоритмов */ // создание внутри узла подписи узла <ds:KeyInfo> информации об открытом ключе на основе // сертификата sig.addKeyInfo(cert); // создание подписи XML-документа sig.sign(privateKey); Element keyInfo = first(sig.getElement(), "http://www.w3.org/2000/09/xmldsig#", "KeyInfo" ); Element x509Data = first(keyInfo, "http://www.w3.org/2000/09/xmldsig#", "X509Data" ); Element x509SubjectName = doc.createElementNS("http://www.w3.org/2000/09/xmldsig#", "X509SubjectName"); x509SubjectName.appendChild(doc.createTextNode( cert.getSubjectDN().toString())); x509Data.appendChild(x509SubjectName); /* Сохранение подписанного XML-документа в файл */ // определение потока, в который осуществляется запись подписанного XML-документа StreamResult result = new StreamResult(new StringWriter()); // инициализация объекта копирования содержимого XML-документа в поток final TransformerFactory tf = TransformerFactory.newInstance(); // создание объекта копирования содержимого XML-документа в поток final Transformer trans = tf.newTransformer(); // копирование содержимого XML-документа в поток trans.transform(new DOMSource(doc), result); return convertElementToString(doc.getDocumentElement()); } 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; } } /** * Проверка подписи всего XML-документа для алгоритма ГОСТ Р 34.10-2001. * * @param signDoc подписанный документ * @throws Exception / */ boolean signDocVer(String signDoc) throws Exception { // инициализация объекта чтения XML-документа final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // установка флага, определяющего игнорирование пробелов в содержимом элементов при обработке XML-документа dbf.setIgnoringElementContentWhitespace(true); // установка флага, определяющего преобразование узлов CDATA в текстовые узлы при обработке XML-документа dbf.setCoalescing(true); // установка флага, определяющего поддержку пространств имен при обработке XML-документа dbf.setNamespaceAware(true); // загрузка содержимого подписываемого документа на основе установленных флагами правил final DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); /* Загружаем подписанный XML-документ из строки */ InputSource is = new InputSource(new StringReader(signDoc)); final Document doc = documentBuilder.parse(is); /* Чтение узла подписи <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) { boolean result = signature.checkSignatureValue(certKey); Logger.getLogger("LOG").info("The XML signature is " + (result ? "valid (good)" : "invalid (bad)")); return result; } // в противном случае осуществляется проверка на открытом ключе else { // чтение открытого ключа из узла информации об открытом ключе final PublicKey pk = ki.getPublicKey(); // если открытый ключ найден, то на нем осуществляется проверка подписи if (pk != null) { boolean result = signature.checkSignatureValue(pk); Logger.getLogger("LOG").info( "The XML signature is " + (result ? "valid (good)" : "invalid (bad)")); return result; } // в противном случае проверка не может быть выполнена else throw new Exception( "There are no information about public key. Verification couldn't be implemented"); } } 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.equals(element.getNamespaceURI())) { return element; } } } return null; } }