ru.i_novus.common.sign.smev3.SmevTransformSpi Maven / Gradle / Ivy
/*-
* -----------------------------------------------------------------
* common-sign-gost
* -----------------------------------------------------------------
* Copyright (C) 2018 - 2019 I-Novus LLC
* -----------------------------------------------------------------
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* -----------------------------------------------------------------
*/
package ru.i_novus.common.sign.smev3;
import org.apache.xml.security.c14n.CanonicalizationException;
import org.apache.xml.security.c14n.InvalidCanonicalizerException;
import org.apache.xml.security.signature.XMLSignatureByteInput;
import org.apache.xml.security.signature.XMLSignatureInput;
import org.apache.xml.security.transforms.Transform;
import org.apache.xml.security.transforms.TransformSpi;
import org.apache.xml.security.transforms.TransformationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static org.apache.commons.lang3.StringUtils.isEmpty;
/**
* Класс, реализующий алгоритм трансформации "urn://smev-gov-ru/xmldsig/transform" для Apache Santuario.
*/
public final class SmevTransformSpi extends TransformSpi {
private static final Logger logger = LoggerFactory.getLogger(SmevTransformSpi.class);
static final String ALGORITHM_URN = "urn://smev-gov-ru/xmldsig/transform";
private static final AttributeSortingComparator attributeSortingComparator = new AttributeSortingComparator();
@Override
protected String engineGetURI() {
return ALGORITHM_URN;
}
@Override
protected XMLSignatureInput enginePerformTransform(
XMLSignatureInput input, OutputStream os, Element transformElement,
String baseURI, boolean secureValidation) throws IOException, TransformationException {
if (os == null) {
return enginePerformTransform(input);
} else {
process(input.getUnprocessedInput(), os);
XMLSignatureInput result = new XMLSignatureByteInput(null);
result.setOutputStream(os);
return result;
}
}
private XMLSignatureInput enginePerformTransform(XMLSignatureInput argInput) throws IOException, TransformationException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
process(argInput.getUnprocessedInput(), result);
byte[] postTransformData = result.toByteArray();
return new XMLSignatureByteInput(postTransformData);
}
@SuppressWarnings("unchecked")
public void process(InputStream argSrc, OutputStream argDst) throws TransformationException {
Deque> prefixMappingStack = new LinkedList<>();
XMLEventReader src = null;
XMLEventWriter dst = null;
try {
src = XMLInputFactory.newInstance().createXMLEventReader(argSrc, StandardCharsets.UTF_8.name());
dst = XMLOutputFactory.newInstance().createXMLEventWriter(argDst, StandardCharsets.UTF_8.name());
XMLEventFactory factory = XMLEventFactory.newInstance();
int prefixCnt = 1;
while (src.hasNext()) {
XMLEvent event = src.nextEvent();
if (event.isCharacters()) {
String data = event.asCharacters().getData();
// Отсекаем whitespace symbols.
if (!data.trim().isEmpty()) {
dst.add(event);
}
} else if (event.isStartElement()) {
List myPrefixMappings = new LinkedList<>();
prefixMappingStack.addFirst(myPrefixMappings);
// Обработка элемента: NS prefix rewriting.
// N.B. Элементы в unqualified form не поддерживаются.
StartElement srcEvent = (StartElement) event;
String nsURI = srcEvent.getName().getNamespaceURI();
String prefix = findPrefix(nsURI, prefixMappingStack);
if (prefix == null) {
prefix = "ns" + (prefixCnt++);
myPrefixMappings.add(factory.createNamespace(prefix, nsURI));
}
StartElement dstEvent = factory.createStartElement(prefix, nsURI, srcEvent.getName().getLocalPart());
dst.add(dstEvent);
// == Обработка атрибутов. Два шага: отсортировать, промэпить namespace URI. ==
Iterator srcAttributeIterator = srcEvent.getAttributes();
// Положим атрибуты в list, чтобы их можно было отсортировать.
List srcAttributeList = new LinkedList<>();
while (srcAttributeIterator.hasNext()) {
srcAttributeList.add(srcAttributeIterator.next());
}
// Сортировка атрибутов по алфавиту.
srcAttributeList.sort(attributeSortingComparator);
// Обработка префиксов. Аналогична обработке префиксов элементов,
// за исключением того, что у атрибут может не иметь namespace.
List dstAttributeList = new LinkedList<>();
for (Attribute srcAttribute : srcAttributeList) {
String attributeNsURI = srcAttribute.getName().getNamespaceURI();
String attributeLocalName = srcAttribute.getName().getLocalPart();
String value = srcAttribute.getValue();
Attribute dstAttribute;
if (!isEmpty(attributeNsURI)) {
String attributePrefix = findPrefix(attributeNsURI, prefixMappingStack);
if (attributePrefix == null) {
attributePrefix = "ns" + (prefixCnt++);
myPrefixMappings.add(factory.createNamespace(attributePrefix, attributeNsURI));
}
dstAttribute = factory.createAttribute(attributePrefix, attributeNsURI, attributeLocalName, value);
} else {
dstAttribute = factory.createAttribute(attributeLocalName, value);
}
dstAttributeList.add(dstAttribute);
}
// Высести namespace prefix mappings для текущего элемента.
// Их порядок детерминирован, т.к. перед мэппингом атрибуты были отсортированы.
// Поэтому дополнительной сотрировки здесь не нужно.
for (Namespace mapping : myPrefixMappings) {
dst.add(mapping);
}
// Вывести атрибуты.
// N.B. Мы не выводим атрибуты сразу вместе с элементом, используя метод
// XMLEventFactory.createStartElement(prefix, nsURI, localName, List, List),
// потому что при использовании этого метода порядок атрибутов в выходном документе
// меняется произвольным образом.
for (Attribute attr : dstAttributeList) {
dst.add(attr);
}
} else if (event.isEndElement()) {
// Гарантируем, что empty tags запишутся в форме , а не в форме .
dst.add(factory.createSpace(""));
// NS prefix rewriting
EndElement srcEvent = (EndElement) event;
String nsURI = srcEvent.getName().getNamespaceURI();
String prefix = findPrefix(nsURI, prefixMappingStack);
if (prefix == null) {
throw new TransformationException("EndElement: prefix mapping is not found for namespace " + nsURI);
}
EndElement dstEvent = factory.createEndElement(prefix, nsURI, srcEvent.getName().getLocalPart());
dst.add(dstEvent);
prefixMappingStack.removeFirst();
} else if (event.isAttribute()) {
// Атрибуты обрабатываются в событии startElement.
}
// Остальные события (processing instructions, start document, etc.) опускаем.
}
} catch (XMLStreamException e) {
Object[] exArgs = {e.getMessage()};
throw new TransformationException(e, "Can not perform transformation " + ALGORITHM_URN, exArgs);
} finally {
if (src != null) {
try {
src.close();
} catch (XMLStreamException e) {
logger.debug("Cannot close source stream", e);
}
}
if (dst != null) {
try {
dst.close();
} catch (XMLStreamException e) {
logger.debug("Cannot close destination stream", e);
}
}
try {
argSrc.close();
} catch (IOException e) {
logger.debug("Cannot close arg source stream", e);
}
if (argDst != null) {
try {
argDst.close();
} catch (IOException e) {
logger.debug("Cannot close arg destination stream", e);
}
}
}
}
private static String findPrefix(String argNamespaceURI, Deque> argMappingStack) {
if (argNamespaceURI == null) {
throw new IllegalArgumentException("No namespace элементы не поддерживаются.");
}
for (List elementMappingList : argMappingStack) {
for (Namespace mapping : elementMappingList) {
if (argNamespaceURI.equals(mapping.getNamespaceURI())) {
return mapping.getPrefix();
}
}
}
return null;
}
private static class AttributeSortingComparator implements Comparator {
@Override
public int compare(Attribute x, Attribute y) {
String xNS = x.getName().getNamespaceURI();
String xLocal = x.getName().getLocalPart();
String yNS = y.getName().getNamespaceURI();
String yLocal = y.getName().getLocalPart();
// Оба атрибута - unqualified.
if (isEmpty(xNS) && isEmpty(yNS)) {
return xLocal.compareTo(yLocal);
}
// Оба атрибута - qualified.
if (!isEmpty(xNS) && !isEmpty(yNS)) {
// Сначала сравниваем namespaces.
int nsComparisonResult = xNS.compareTo(yNS);
if (nsComparisonResult != 0) {
return nsComparisonResult;
} else {
// Если равны - local names.
return xLocal.compareTo(yLocal);
}
}
// Один - qualified, второй - unqualified.
if (isEmpty(xNS)) {
return 1;
} else {
return -1;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy