/* * 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 org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI; import org.apache.ws.security.message.token.X509Security; import org.apache.xml.security.Init; import org.apache.xml.security.algorithms.MessageDigestAlgorithm; import org.apache.xml.security.c14n.CanonicalizationException; import org.apache.xml.security.c14n.Canonicalizer; import org.apache.xml.security.exceptions.XMLSecurityException; import org.apache.xml.security.signature.XMLSignature; import org.apache.xml.security.signature.XMLSignatureInput; import org.apache.xml.security.transforms.Transforms; import org.apache.xml.security.utils.Base64; import org.apache.xml.security.utils.Constants; import org.apache.xml.security.utils.DigesterOutputStream; import org.apache.xml.security.utils.XMLUtils; import org.w3c.dom.Attr; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; 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.AppData; import ru.codeinside.gws.api.Signature; import ru.codeinside.gws.api.VerifyResult; import javax.xml.bind.DatatypeConverter; import javax.xml.crypto.dsig.XMLSignatureFactory; import javax.xml.crypto.dsig.dom.DOMValidateContext; import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.soap.SOAPElement; import javax.xml.soap.SOAPEnvelope; import javax.xml.soap.SOAPHeader; import javax.xml.soap.SOAPMessage; import javax.xml.soap.SOAPPart; import javax.xml.stream.EventFilter; import javax.xml.stream.XMLEventReader; import javax.xml.stream.XMLEventWriter; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamConstants; import javax.xml.stream.events.EndElement; import javax.xml.stream.events.StartElement; import javax.xml.stream.events.XMLEvent; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.io.StringWriter; import java.security.InvalidKeyException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.SignatureException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Properties; import java.util.Set; final public class CryptoProvider implements ru.codeinside.gws.api.CryptoProvider { static { if (!Init.isInitialized()) { Init.init(); } SIGNATURE_FACTORY = XMLSignatureFactory.getInstance("DOM", new XMLDSigRI()); } final private static String ENVELOP = "http://schemas.xmlsoap.org/soap/envelope/"; final private static String WSU = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; final private static String WSSE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; final private static String WSS_X509V3 = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3"; final private static String WSS_BASE64_BINARY = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"; final private static String ACTOR_RECIPIENT = "http://smev.gosuslugi.ru/actors/recipient"; final private static String ACTOR_SMEV = "http://smev.gosuslugi.ru/actors/smev"; final private static XMLSignatureFactory SIGNATURE_FACTORY; final private static Log log = LogFactory.getLog(CryptoProvider.class); final private static String DEFAULT_CERT_NAME = "KSPGMU"; final private static String DEFAULT_CERT_PASS = "12345678"; static transient boolean started; static PrivateKey privateKey; static X509Certificate cert; private static VerifyResult verifyMessage(final SOAPMessage message) throws Exception { final long startMs = System.currentTimeMillis(); try { final SOAPPart doc = message.getSOAPPart(); final SOAPEnvelope envelope = doc.getEnvelope(); final SOAPHeader soapHeader = message.getSOAPHeader(); final Element recipient = findSecurityToken(soapHeader, ACTOR_RECIPIENT); final X509Certificate recipientCert; if (recipient != null) { final ValidateResult recipientResult = validate(recipient); if (recipientResult.error != null) { return new VerifyResult("ЭЦП шлюза: " + recipientResult.error, null, recipientResult.cert); } recipientCert = recipientResult.cert; } else { recipientCert = null; } final Element smev = findSecurityToken(soapHeader, ACTOR_SMEV); if (smev == null) { return new VerifyResult("ЭЦП ИС: отсутствует", null, recipientCert); } final ValidateResult smevResult = validate(smev); return new VerifyResult(smevResult.error, smevResult.cert, recipientCert); } finally { if (log.isDebugEnabled()) { log.debug("VERIFY: " + (System.currentTimeMillis() - startMs) + "ms"); } } } /** * Пока загружаем один раз и не обновляем. * <p/> * <p/> * Термин «шифрование на сертификате» происходит из алгоритма RSA. Там можно зашифровать сообщение на сертификате * получателя, так чтоб он своим закрытым ключом его расшифровал. С российскими алгоритмами так нельзя. Для эмуляции * шифрования на сертификате можно использовать такую схему: * <ol> * <li>Сначала надо создать эфемерную ключевую пару.</li> * <li>Потом выработать ключ согласования из эфемерного закрытого и сертификата получателя.</li> * <li>Создать случайный секретный ключ шифрования.</li> * <li>Этот секретный ключ зашифровать на ключе согласования.</li> * <li>Данные шифровать случайным секретным ключом.</li> * <li>Отправить получателю зашифрованные данные, зашифрованный секретный ключ и открытый ключ или его сертификат.</li> * </ol> * <p/> * Т.е. стандартная схема, как в примерах, только ключевая пара не постоянная, а эфемерная, одноразовая. Эта схема * позволяет шифровать сообщение для пользователя имея только его сертификат. Он сможет его расшифровать, но не * будет гарантированно знать, от кого оно пришло, может и от противника. * <p/> * Если создавать вместо эфемерной пары пользовательский постоянный ключ с сертификатом, то вся схема сохранится, но * получатель сможет гарантированно назвать отправителя. * * @throws KeyStoreException * @throws IOException * @throws CertificateException * @throws NoSuchAlgorithmException * @throws UnrecoverableKeyException */ static void loadCertificate() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException { if (!started) { synchronized (CryptoProvider.class) { if (!started) { final long startMs = System.currentTimeMillis(); final KeyStore keystore = KeyStore.getInstance("HDImageStore"); keystore.load(null, null); final Properties properties = new Properties(); properties.setProperty("name", DEFAULT_CERT_NAME); properties.setProperty("pass", DEFAULT_CERT_PASS); final File userHome = new File(System.getProperty("user.home")); final File keyFile = new File(userHome, "gses-key.properties"); if (!keyFile.exists()) { log.warn(keyFile + " не обнаружен, используются ключи тестовой системы"); } else { final FileInputStream is = new FileInputStream(keyFile); properties.load(is); is.close(); } final String certName_ = properties.getProperty("name"); final String certPass_ = properties.getProperty("pass"); privateKey = ((PrivateKey) keystore.getKey(certName_, certPass_.toCharArray())); cert = ((X509Certificate) keystore.getCertificate(certName_)); try { cert.checkValidity(); log.info("Загружен действующий до " + cert.getNotAfter() + " сертификат " + cert.getSubjectDN().getName()); } catch (CertificateExpiredException e) { log.error("Закончилось время действия для сертификата " + cert.getSubjectDN().getName()); cert = null; privateKey = null; } catch (CertificateNotYetValidException e) { log.error("Не началось время действия для сертификата " + cert.getSubjectDN().getName()); cert = null; privateKey = null; } if ((privateKey != null) && (cert != null)) { started = true; } if (log.isDebugEnabled()) { log.debug("LOAD CERTIFICATE: " + (System.currentTimeMillis() - startMs) + "ms"); } } } } } /** * Накопление идентификаторов. */ private static void fixWsuId(final Node node, final DOMValidateContext ctx, final Set<String> ids) { if (node instanceof Element) { final NamedNodeMap attributes = node.getAttributes(); if (attributes != null) { final Node wsuId = attributes.getNamedItemNS(WSU, "Id"); if (wsuId != null) { final String id = wsuId.getNodeValue(); if (ids.contains(id)) { throw new RuntimeException("Нарушение уникальности " + node + " @" + wsuId); } ids.add(id); ctx.setIdAttributeNS((Element) node, WSU, "Id"); } } } final NodeList children = node.getChildNodes(); if (children != null) { for (int i = 0; i < children.getLength(); i++) { fixWsuId(children.item(i), ctx, ids); } } } private static ValidateResult validate(final Element securityToken) throws Exception { final X509Security x509 = new X509Security(securityToken); final X509Certificate cert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate( new ByteArrayInputStream(x509.getToken())); if (cert == null) { return new ValidateResult("Не найден cертификат подписи", null); } try { cert.checkValidity(); } catch (CertificateException e) { return new ValidateResult("Сертификат подписи не действителен", cert); } final Element signature = first(securityToken.getParentNode(), Constants.SignatureSpecNS, "Signature"); if (signature == null) { return new ValidateResult("Не найден элемент подписи", cert); } final DOMValidateContext ctx = new DOMValidateContext(cert.getPublicKey(), signature); fixWsuId(securityToken.getOwnerDocument(), ctx, new HashSet<String>()); final boolean valid = SIGNATURE_FACTORY.unmarshalXMLSignature(ctx).validate(ctx); return new ValidateResult(valid ? null : "Подпись не прошла проверку!", cert); } private static Element findSecurityToken(final Element holder, final String actor) { final Element security = first(holder, WSSE, "Security", ENVELOP, "actor", actor); if (security != null) { return first(security, WSSE, "BinarySecurityToken"); } 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.equals(element.getNamespaceURI())) { return element; } } } return null; } private static Element first(final Node parent, final String uri, final String localName, final String attrUri, final String attr, final String attrValue) { 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()) && attrValue.equals(element.getAttributeNS(attrUri, attr))) { return element; } } } return null; } public void sign(final SOAPMessage message) { try { loadCertificate(); final long startMs = System.currentTimeMillis(); final SOAPPart doc = message.getSOAPPart(); final QName wsuId = doc.getEnvelope().createQName("Id", "wsu"); final SOAPHeader header = message.getSOAPHeader(); final QName actor = header.createQName("actor", header.getPrefix()); final SOAPElement security = header.addChildElement("Security", "wsse", WSSE); security.addAttribute(actor, ACTOR_SMEV); SOAPElement binarySecurityToken = security.addChildElement("BinarySecurityToken", "wsse"); binarySecurityToken.setAttribute("EncodingType", WSS_BASE64_BINARY); binarySecurityToken.setAttribute("ValueType", WSS_X509V3); binarySecurityToken.setValue(DatatypeConverter.printBase64Binary(cert.getEncoded())); binarySecurityToken.addAttribute(wsuId, "CertId"); XMLSignature signature = new XMLSignature(doc, "", XMLSignature.ALGO_ID_SIGNATURE_GOST_GOST3410_3411, Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); { Element element = signature.getElement(); Element keyInfo = doc.createElementNS(Constants.SignatureSpecNS, "KeyInfo"); Element securityTokenReference = doc.createElementNS(WSSE, "SecurityTokenReference"); Element reference = doc.createElementNS(WSSE, "Reference"); reference.setAttribute("URI", "#CertId"); reference.setAttribute("ValueType", WSS_X509V3); securityTokenReference.appendChild(reference); keyInfo.appendChild(securityTokenReference); element.appendChild(keyInfo); security.appendChild(element); } Transforms transforms = new Transforms(doc); transforms.addTransform(Canonicalizer.ALGO_ID_C14N_EXCL_OMIT_COMMENTS); signature.addDocument("#body", transforms, MessageDigestAlgorithm.ALGO_ID_DIGEST_GOST3411); signature.sign(privateKey); if (log.isDebugEnabled()) { log.debug("SIGN: " + (System.currentTimeMillis() - startMs) + "ms"); } } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } @Override public String signElement(String sourceXML, String elementName, String namespace, boolean removeIdAttribute, boolean signatureAfterElement, boolean inclusive) throws Exception { loadCertificate(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setIgnoringElementContentWhitespace(true); dbf.setCoalescing(true); dbf.setNamespaceAware(true); DocumentBuilder documentBuilder = dbf.newDocumentBuilder(); InputSource is = new InputSource(new StringReader(sourceXML)); Document doc = documentBuilder.parse(is); Element elementForSign = (Element) doc.getElementsByTagNameNS(namespace, elementName).item(0); Node parentNode = null; Element detachedElementForSign; 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); // detachedElementForSign = detachedDocument.getDocumentElement(); // } else { detachedElementForSign = elementForSign; detachedDocument = doc; // } String signatureMethodUri = inclusive ? "urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr34102001-gostr3411" : "http://www.w3.org/2001/04/xmldsig-more#gostr34102001-gostr3411"; String canonicalizationMethodUri = inclusive ? "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" : "http://www.w3.org/2001/10/xml-exc-c14n#"; XMLSignature sig = new XMLSignature(detachedDocument, "", signatureMethodUri, canonicalizationMethodUri); String id = (detachedElementForSign.getAttribute("Id") != null) ? detachedElementForSign.getAttribute("Id") : detachedElementForSign.getTagName(); if (!removeIdAttribute) { detachedElementForSign.setAttributeNS(null, "Id", id); Attr idAttr = detachedElementForSign.getAttributeNode("Id"); detachedElementForSign.setIdAttributeNode(idAttr, true); } if (signatureAfterElement) detachedElementForSign.insertBefore(sig.getElement(), detachedElementForSign.getLastChild().getNextSibling()); else { detachedElementForSign.insertBefore(sig.getElement(), detachedElementForSign.getFirstChild()); } Transforms transforms = new Transforms(detachedDocument); transforms.addTransform("http://www.w3.org/2000/09/xmldsig#enveloped-signature"); transforms.addTransform(inclusive ? "http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments" : "http://www.w3.org/2001/10/xml-exc-c14n#"); String digestURI = inclusive ? "urn:ietf:params:xml:ns:cpxmlsec:algorithms:gostr3411" : "http://www.w3.org/2001/04/xmldsig-more#gostr3411"; sig.addDocument(removeIdAttribute ? "" : "#" + id, transforms, digestURI); sig.addKeyInfo(cert); sig.sign(privateKey); if ((!elementForSign.isSameNode(doc.getDocumentElement())) && (parentNode != null)) { Node signedNode = doc.importNode(detachedElementForSign, true); parentNode.appendChild(signedNode); } TransformerFactory tf = TransformerFactory.newInstance(); Transformer trans = tf.newTransformer(); trans.setOutputProperty("omit-xml-declaration", "yes"); StringWriter stringWriter = new StringWriter(); StreamResult streamResult = new StreamResult(stringWriter); trans.transform(new DOMSource(doc), streamResult); return stringWriter.toString(); } @Override public byte[] digest(InputStream source) { try { MessageDigest md = MessageDigest.getInstance("GOST3411"); int count; byte[] buff = new byte[1024]; while ((count = source.read(buff, 0, buff.length)) > 0) { md.update(buff, 0, count); } return md.digest(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } } @Override public VerifyResult verify(final SOAPMessage message) { try { return verifyMessage(message); } catch (final RuntimeException e) { throw e; } catch (final Exception e) { final Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } throw new RuntimeException(e); } } @Override @Deprecated public AppData normalize(List<QName> namespaces, String appData) { try { final Document doc = createDocumentFromFragment(namespaces, appData); NodeList childNodes = doc.getDocumentElement().getChildNodes(); Element body = (Element) childNodes.item(0); String _id; Attr id = body.getAttributeNodeNS(WSU, "Id"); if (id == null) { _id = "AppData"; body.setAttributeNS(WSU, "Id", _id); } else { _id = id.getValue(); } final Transforms transforms = new Transforms(doc); // пока не добавляем ds:Signature, поэтому и не фильтруем // Element signature = doc.createElementNS(Constants.SignatureSpecNS, Constants._TAG_SIGNATURE); // signature = (Element) body.insertBefore(signature, body.getFirstChild()); // transforms.setElement(signature, _id); // transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS); ByteArrayOutputStream c14nStream = new ByteArrayOutputStream(); MessageDigestAlgorithm mda = MessageDigestAlgorithm.getInstance(doc, MessageDigestAlgorithm.ALGO_ID_DIGEST_GOST3411); mda.reset(); XMLSignatureInput output = transforms.performTransforms(new XMLSignatureInput(body), c14nStream); DigesterOutputStream digesterStream = new DigesterOutputStream(mda); output.updateOutputStream(digesterStream); return new AppData(c14nStream.toByteArray(), digesterStream.getDigestValue()); } catch (ParserConfigurationException e) { throw new RuntimeException(e); } catch (SAXException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } catch (CanonicalizationException e) { throw new RuntimeException(e); } catch (XMLSecurityException e) { throw new RuntimeException(e); } } @Override @Deprecated public String inject(final List<QName> namespaces, final AppData normalized, final X509Certificate certificate, final byte[] sig) { try { final String normalizedAppData = new String(normalized.content, "UTF8"); final Document doc = createDocumentFromFragment(namespaces, normalizedAppData); NodeList childNodes = doc.getDocumentElement().getChildNodes(); Element body = (Element) childNodes.item(0); Attr idAttr = body.getAttributeNodeNS(WSU, "Id"); if (idAttr == null) { throw new IllegalStateException("Не нормализованный блок"); } final String id = idAttr.getValue(); final Transforms transforms = new Transforms(doc); Element signature = doc.createElementNS(Constants.SignatureSpecNS, Constants._TAG_SIGNATURE); signature = (Element) body.insertBefore(signature, body.getFirstChild()); transforms.setElement(signature, id); transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE); transforms.addTransform(Transforms.TRANSFORM_C14N_EXCL_OMIT_COMMENTS); ByteArrayOutputStream c14nStream = new ByteArrayOutputStream(); MessageDigestAlgorithm mda = MessageDigestAlgorithm.getInstance(doc, MessageDigestAlgorithm.ALGO_ID_DIGEST_GOST3411); mda.reset(); XMLSignatureInput output = transforms.performTransforms(new XMLSignatureInput(body), c14nStream); DigesterOutputStream digesterStream = new DigesterOutputStream(mda); output.updateOutputStream(digesterStream); AppData check = new AppData(c14nStream.toByteArray(), digesterStream.getDigestValue()); if (!Arrays.equals(check.digest, normalized.digest)) { final StringBuilder sb = new StringBuilder("Хеш сумма не совпадает:\n"); sb.append("параметер: ").append(new String(normalized.digest, "UTF8")).append('\n'); sb.append(" контроль: ").append(new String(check.digest, "UTF8")); throw new IllegalStateException(sb.toString()); } Element keyInfo = doc.createElementNS(Constants.SignatureSpecNS, "KeyInfo"); Element securityTokenReference = doc.createElementNS(WSSE, "SecurityTokenReference"); Element reference = doc.createElementNS(WSSE, "Reference"); reference.setAttribute("URI", "#CertId"); reference.setAttribute("ValueType", WSS_X509V3); securityTokenReference.appendChild(reference); keyInfo.appendChild(securityTokenReference); signature.appendChild(keyInfo); Element signatureValueElement = XMLUtils.createElementInSignatureSpace(doc, Constants._TAG_SIGNATUREVALUE); signature.appendChild(signatureValueElement); String base64codedValue = Base64.encode(sig); if (base64codedValue.length() > 76 && !XMLUtils.ignoreLineBreaks()) { base64codedValue = "\n" + base64codedValue + "\n"; } signatureValueElement.appendChild(doc.createTextNode(base64codedValue)); return saxFilter(doc); } catch (ParserConfigurationException e) { throw new RuntimeException(e); } catch (SAXException e) { throw new RuntimeException(e); } catch (IOException e) { throw new RuntimeException(e); } catch (CanonicalizationException e) { throw new RuntimeException(e); } catch (XMLSecurityException e) { throw new RuntimeException(e); } } @Override public byte[] toPkcs7(final Signature signature) { return SunPkcs7.toPkcs7(signature); } @Override public Signature fromPkcs7(final byte[] pkcs7) { return SunPkcs7.fromPkcs7(pkcs7); } @Override public boolean validate(final Signature signature, final byte[] digest, final byte[] content) { // основной контракт - наличие данных, сертификата и подписи if (content == null || signature == null || signature.certificate == null || signature.sign == null) { return false; } // проверям сначала хеш if (digest != null) { try { final MessageDigest gost3411 = MessageDigest.getInstance("GOST3411"); gost3411.update(content); byte[] digest2 = gost3411.digest(); if (!Arrays.equals(digest, digest2)) { return false; } } catch (NoSuchAlgorithmException e) { throw new IllegalStateException("Отсутсвует GOST3411"); } } // проверка подписи (не сертификата!) try { final java.security.Signature sig = java.security.Signature.getInstance("GOST3411withGOST3410EL"); sig.initVerify(signature.certificate); sig.update(content); return sig.verify(signature.sign); } catch (final NoSuchAlgorithmException e) { throw new IllegalStateException("Отсутсвует GOST3411withGOST3410EL"); } catch (final InvalidKeyException e) { return false; } catch (final SignatureException e) { throw new RuntimeException(e); } } @Override public boolean verifySignature(X509Certificate certificate, InputStream data, byte[] sign) { try { if (certificate == null || data == null || sign == null) { throw new NullPointerException(); } final boolean valid; final java.security.Signature signature = createGost3411WithGost3410(); if (!isKeyValid(certificate, signature)) { valid = false; } else { updateSignature(signature, data); valid = verifySignature(signature, sign); } return valid; } finally { close(data); } } private String domToString(Element node) { try { final TransformerFactory factory = TransformerFactory.newInstance(); final Transformer transformer = factory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); final StringWriter sw = new StringWriter(); transformer.transform(new DOMSource(node), new StreamResult(sw)); return sw.toString(); } catch (TransformerException e) { throw new RuntimeException(e); } } private Document createDocumentFromFragment(List<QName> namespaces, String appData) throws SAXException, IOException, ParserConfigurationException { // эти элементы должны определятся на верхнем уровне: final QName wsu = new QName(WSU, "wsu"); final QName ds = new QName("http://www.w3.org/2000/09/xmldsig#", "ds"); if (namespaces.indexOf(wsu) == -1) { namespaces.add(wsu); } if (namespaces.indexOf(ds) == -1) { namespaces.add(ds); } final StringBuilder sb = new StringBuilder(); sb.append("<root"); for (final QName name : namespaces) { sb.append(" xmlns:"); sb.append(name.getLocalPart()); sb.append("=\""); sb.append(name.getNamespaceURI()); sb.append("\""); } sb.append(">"); sb.append(appData); sb.append("</root>"); final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setIgnoringElementContentWhitespace(true); factory.setCoalescing(true); factory.setNamespaceAware(true); return factory.newDocumentBuilder().parse(new ByteArrayInputStream(sb.toString().getBytes("UTF-8"))); } private String saxFilter(Node node) { try { final Transformer transformer = TransformerFactory.newInstance().newTransformer(); final StringWriter w1 = new StringWriter(); transformer.transform(new DOMSource(node), new StreamResult(w1)); XMLInputFactory xif = XMLInputFactory.newInstance(); XMLEventReader eventReader = xif.createXMLEventReader(new StreamSource(new StringReader(w1.toString()))); XMLEventReader filteredReader = xif.createFilteredReader(eventReader, new EventFilter() { @Override public boolean accept(XMLEvent event) { int type = event.getEventType(); if (type == XMLStreamConstants.START_DOCUMENT || type == XMLStreamConstants.END_DOCUMENT) { return false; } if (event.isStartElement()) { StartElement startElement = (StartElement) event; QName name = startElement.getName(); if ("".equals(name.getNamespaceURI()) && "root".equals(name.getLocalPart())) { return false; } } if (event.isEndElement()) { EndElement endElement = (EndElement) event; QName name = endElement.getName(); if ("".equals(name.getNamespaceURI()) && "root".equals(name.getLocalPart())) { return false; } } return true; } }); StringWriter sw = new StringWriter(); XMLOutputFactory xof = XMLOutputFactory.newInstance(); XMLEventWriter writer = xof.createXMLEventWriter(sw); while (filteredReader.hasNext()) { writer.add(filteredReader.nextEvent()); } return sw.toString(); } catch (Exception e) { throw new RuntimeException(e); } } static class ValidateResult { final public String error; final public X509Certificate cert; ValidateResult(final String error, final X509Certificate cert) { this.error = error; this.cert = cert; } } private boolean verifySignature(final java.security.Signature signature, final byte[] sign) { try { return signature.verify(sign); } catch (SignatureException e) { throw new RuntimeException("CryptoPRO error", e); } } private boolean isKeyValid(final X509Certificate certificate, final java.security.Signature signature) { boolean valid; try { signature.initVerify(certificate); valid = true; } catch (InvalidKeyException e) { log.info("Invalid certificate", e); valid = false; } return valid; } private void updateSignature(final java.security.Signature signature, final InputStream data) { final byte[] buffer = new byte[1024]; int length; try { while ((length = data.read(buffer)) != -1) { signature.update(buffer, 0, length); } } catch (IOException e) { throw new RuntimeException("data reading error", e); } catch (SignatureException e) { throw new RuntimeException("CryptoPRO error", e); } } private void close(final InputStream inputStream) { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { log.info("failure on close", e); } } } private java.security.Signature createGost3411WithGost3410() { final java.security.Signature signature; try { signature = java.security.Signature.getInstance("GOST3411withGOST3410EL"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException("КриптоПРО не установлен", e); } return signature; } }