Bug 61182 - Invalid signature created for streamed xslx file

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1800207 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2017-06-28 21:38:23 +00:00
parent 4209a1b2da
commit 51b46a9e4e
6 changed files with 232 additions and 394 deletions

View File

@ -74,8 +74,12 @@ public final class StreamHelper {
out.flush(); // only flush, don't close! out.flush(); // only flush, don't close!
} }
}); });
// xmlContent.setXmlStandalone(true);
trans.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); trans.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
trans.setOutputProperty(OutputKeys.INDENT, "yes"); // don't indent xml documents, the indent will cause errors in calculating the xml signature
// because of different handling of linebreaks in Windows/Unix
// see https://stackoverflow.com/questions/36063375
trans.setOutputProperty(OutputKeys.INDENT, "no");
trans.setOutputProperty(OutputKeys.STANDALONE, "yes"); trans.setOutputProperty(OutputKeys.STANDALONE, "yes");
trans.transform(xmlSource, outputTarget); trans.transform(xmlSource, outputTarget);
} catch (TransformerException e) { } catch (TransformerException e) {

View File

@ -40,24 +40,32 @@ public class SignatureMarshalListener implements EventListener, SignatureConfigu
this.target.set(target); this.target.set(target);
} }
@Override
public void handleEvent(Event e) { public void handleEvent(Event e) {
if (!(e instanceof MutationEvent)) return; if (!(e instanceof MutationEvent)) {
return;
}
MutationEvent mutEvt = (MutationEvent)e; MutationEvent mutEvt = (MutationEvent)e;
EventTarget et = mutEvt.getTarget(); EventTarget et = mutEvt.getTarget();
if (!(et instanceof Element)) return; if (!(et instanceof Element)) {
return;
}
handleElement((Element)et); handleElement((Element)et);
} }
public void handleElement(Element el) { public void handleElement(Element el) {
EventTarget target = this.target.get(); EventTarget target = this.target.get();
String packageId = signatureConfig.getPackageSignatureId();
if (el.hasAttribute("Id")) { if (el.hasAttribute("Id")) {
el.setIdAttribute("Id", true); el.setIdAttribute("Id", true);
} }
setListener(target, this, false); setListener(target, this, false);
if (packageId.equals(el.getAttribute("Id"))) { if (OO_DIGSIG_NS.equals(el.getNamespaceURI())) {
el.setAttributeNS(XML_NS, "xmlns:mdssi", OO_DIGSIG_NS); String parentNS = el.getParentNode().getNamespaceURI();
if (!OO_DIGSIG_NS.equals(parentNS) && !el.hasAttributeNS(XML_NS, "mdssi")) {
el.setAttributeNS(XML_NS, "xmlns:mdssi", OO_DIGSIG_NS);
}
} }
setPrefix(el); setPrefix(el);
setListener(target, this, true); setListener(target, this, true);
@ -86,6 +94,7 @@ public class SignatureMarshalListener implements EventListener, SignatureConfigu
} }
} }
@Override
public void setSignatureConfig(SignatureConfig signatureConfig) { public void setSignatureConfig(SignatureConfig signatureConfig) {
this.signatureConfig = signatureConfig; this.signatureConfig = signatureConfig;
} }

View File

@ -18,9 +18,9 @@
/* ==================================================================== /* ====================================================================
This product contains an ASLv2 licensed version of the OOXML signer This product contains an ASLv2 licensed version of the OOXML signer
package from the eID Applet project package from the eID Applet project
http://code.google.com/p/eid-applet/source/browse/trunk/README.txt http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
Copyright (C) 2008-2014 FedICT. Copyright (C) 2008-2014 FedICT.
================================================================= */ ================================================================= */
package org.apache.poi.poifs.crypt.dsig.facets; package org.apache.poi.poifs.crypt.dsig.facets;
@ -29,6 +29,9 @@ import java.net.URISyntaxException;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -70,13 +73,13 @@ import com.microsoft.schemas.office.x2006.digsig.SignatureInfoV1Document;
/** /**
* Office OpenXML Signature Facet implementation. * Office OpenXML Signature Facet implementation.
* *
* @author fcorneli
* @see <a href="http://msdn.microsoft.com/en-us/library/cc313071.aspx">[MS-OFFCRYPTO]: Office Document Cryptography Structure</a> * @see <a href="http://msdn.microsoft.com/en-us/library/cc313071.aspx">[MS-OFFCRYPTO]: Office Document Cryptography Structure</a>
*/ */
public class OOXMLSignatureFacet extends SignatureFacet { public class OOXMLSignatureFacet extends SignatureFacet {
private static final POILogger LOG = POILogFactory.getLogger(OOXMLSignatureFacet.class); private static final POILogger LOG = POILogFactory.getLogger(OOXMLSignatureFacet.class);
private static final String ID_PACKAGE_OBJECT = "idPackageObject";
@Override @Override
public void preSign( public void preSign(
@ -98,17 +101,16 @@ public class OOXMLSignatureFacet extends SignatureFacet {
List<Reference> manifestReferences = new ArrayList<Reference>(); List<Reference> manifestReferences = new ArrayList<Reference>();
addManifestReferences(manifestReferences); addManifestReferences(manifestReferences);
Manifest manifest = getSignatureFactory().newManifest(manifestReferences); Manifest manifest = getSignatureFactory().newManifest(manifestReferences);
String objectId = "idPackageObject"; // really has to be this value.
List<XMLStructure> objectContent = new ArrayList<XMLStructure>(); List<XMLStructure> objectContent = new ArrayList<XMLStructure>();
objectContent.add(manifest); objectContent.add(manifest);
addSignatureTime(document, objectContent); addSignatureTime(document, objectContent);
XMLObject xo = getSignatureFactory().newXMLObject(objectContent, objectId, null, null); XMLObject xo = getSignatureFactory().newXMLObject(objectContent, ID_PACKAGE_OBJECT, null, null);
objects.add(xo); objects.add(xo);
Reference reference = newReference("#" + objectId, null, XML_DIGSIG_NS+"Object", null, null); Reference reference = newReference("#"+ID_PACKAGE_OBJECT, null, XML_DIGSIG_NS+"Object", null, null);
references.add(reference); references.add(reference);
} }
@ -121,7 +123,7 @@ public class OOXMLSignatureFacet extends SignatureFacet {
Set<String> digestedPartNames = new HashSet<String>(); Set<String> digestedPartNames = new HashSet<String>();
for (PackagePart pp : relsEntryNames) { for (PackagePart pp : relsEntryNames) {
String baseUri = pp.getPartName().getName().replaceFirst("(.*)/_rels/.*", "$1"); final String baseUri = pp.getPartName().getName().replaceFirst("(.*)/_rels/.*", "$1");
PackageRelationshipCollection prc; PackageRelationshipCollection prc;
try { try {
@ -130,11 +132,11 @@ public class OOXMLSignatureFacet extends SignatureFacet {
} catch (InvalidFormatException e) { } catch (InvalidFormatException e) {
throw new XMLSignatureException("Invalid relationship descriptor: "+pp.getPartName().getName(), e); throw new XMLSignatureException("Invalid relationship descriptor: "+pp.getPartName().getName(), e);
} }
RelationshipTransformParameterSpec parameterSpec = new RelationshipTransformParameterSpec(); RelationshipTransformParameterSpec parameterSpec = new RelationshipTransformParameterSpec();
for (PackageRelationship relationship : prc) { for (PackageRelationship relationship : prc) {
String relationshipType = relationship.getRelationshipType(); String relationshipType = relationship.getRelationshipType();
/* /*
* ECMA-376 Part 2 - 3rd edition * ECMA-376 Part 2 - 3rd edition
* 13.2.4.16 Manifest Element * 13.2.4.16 Manifest Element
@ -144,22 +146,20 @@ public class OOXMLSignatureFacet extends SignatureFacet {
continue; continue;
} }
if (!isSignedRelationship(relationshipType)) continue; if (!isSignedRelationship(relationshipType)) {
continue;
}
parameterSpec.addRelationshipReference(relationship.getId()); parameterSpec.addRelationshipReference(relationship.getId());
// TODO: find a better way ... String partName = normalizePartName(relationship.getTargetURI(), baseUri);
String partName = relationship.getTargetURI().toString();
if (!partName.startsWith(baseUri)) { // We only digest a part once.
partName = baseUri + partName; if (digestedPartNames.contains(partName)) {
continue;
} }
try { digestedPartNames.add(partName);
partName = new URI(partName).normalize().getPath().replace('\\', '/');
LOG.log(POILogger.DEBUG, "part name: " + partName);
} catch (URISyntaxException e) {
throw new XMLSignatureException(e);
}
String contentType; String contentType;
try { try {
PackagePartName relName = PackagingURIHelper.createPartName(partName); PackagePartName relName = PackagingURIHelper.createPartName(partName);
@ -168,32 +168,52 @@ public class OOXMLSignatureFacet extends SignatureFacet {
} catch (InvalidFormatException e) { } catch (InvalidFormatException e) {
throw new XMLSignatureException(e); throw new XMLSignatureException(e);
} }
if (relationshipType.endsWith("customXml") if (relationshipType.endsWith("customXml")
&& !(contentType.equals("inkml+xml") || contentType.equals("text/xml"))) { && !(contentType.equals("inkml+xml") || contentType.equals("text/xml"))) {
LOG.log(POILogger.DEBUG, "skipping customXml with content type: " + contentType); LOG.log(POILogger.DEBUG, "skipping customXml with content type: " + contentType);
continue; continue;
} }
if (!digestedPartNames.contains(partName)) { String uri = partName + "?ContentType=" + contentType;
// We only digest a part once. Reference reference = newReference(uri, null, null, null, null);
String uri = partName + "?ContentType=" + contentType; manifestReferences.add(reference);
Reference reference = newReference(uri, null, null, null, null);
manifestReferences.add(reference);
digestedPartNames.add(partName);
}
} }
if (parameterSpec.hasSourceIds()) { if (parameterSpec.hasSourceIds()) {
List<Transform> transforms = new ArrayList<Transform>(); List<Transform> transforms = new ArrayList<Transform>();
transforms.add(newTransform(RelationshipTransformService.TRANSFORM_URI, parameterSpec)); transforms.add(newTransform(RelationshipTransformService.TRANSFORM_URI, parameterSpec));
transforms.add(newTransform(CanonicalizationMethod.INCLUSIVE)); transforms.add(newTransform(CanonicalizationMethod.INCLUSIVE));
String uri = pp.getPartName().getName() String uri = normalizePartName(pp.getPartName().getURI(), baseUri)
+ "?ContentType=application/vnd.openxmlformats-package.relationships+xml"; + "?ContentType=application/vnd.openxmlformats-package.relationships+xml";
Reference reference = newReference(uri, transforms, null, null, null); Reference reference = newReference(uri, transforms, null, null, null);
manifestReferences.add(reference); manifestReferences.add(reference);
} }
} }
Collections.sort(manifestReferences, new Comparator<Reference>() {
public int compare(Reference o1, Reference o2) {
return o1.getURI().compareTo(o2.getURI());
}
});
}
/**
* Normalize a URI/part name
* TODO: find a better way ...
*/
private static String normalizePartName(URI partName, String baseUri) throws XMLSignatureException {
String pn = partName.toASCIIString();
if (!pn.startsWith(baseUri)) {
pn = baseUri + pn;
}
try {
pn = new URI(pn).normalize().getPath().replace('\\', '/');
LOG.log(POILogger.DEBUG, "part name: " + pn);
} catch (URISyntaxException e) {
throw new XMLSignatureException(e);
}
return pn;
} }
@ -236,7 +256,7 @@ public class OOXMLSignatureFacet extends SignatureFacet {
ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri()); ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
Element n = (Element)document.importNode(ctSigV1.getDomNode(), true); Element n = (Element)document.importNode(ctSigV1.getDomNode(), true);
n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS); n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS);
List<XMLStructure> signatureInfoContent = new ArrayList<XMLStructure>(); List<XMLStructure> signatureInfoContent = new ArrayList<XMLStructure>();
signatureInfoContent.add(new DOMStructure(n)); signatureInfoContent.add(new DOMStructure(n));
SignatureProperty signatureInfoSignatureProperty = getSignatureFactory() SignatureProperty signatureInfoSignatureProperty = getSignatureFactory()
@ -268,208 +288,33 @@ public class OOXMLSignatureFacet extends SignatureFacet {
protected static boolean isSignedRelationship(String relationshipType) { protected static boolean isSignedRelationship(String relationshipType) {
LOG.log(POILogger.DEBUG, "relationship type: " + relationshipType); LOG.log(POILogger.DEBUG, "relationship type: " + relationshipType);
for (String signedTypeExtension : signed) { String rt = relationshipType.replaceFirst(".*/relationships/", "");
if (relationshipType.endsWith(signedTypeExtension)) { return (signed.contains(rt) || rt.endsWith("customXml"));
return true;
}
}
if (relationshipType.endsWith("customXml")) {
LOG.log(POILogger.DEBUG, "customXml relationship type");
return true;
}
return false;
} }
public static final String[] contentTypes = {
/*
* Word
*/
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
"application/vnd.openxmlformats-officedocument.theme+xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml",
"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
/*
* Word 2010
*/
"application/vnd.ms-word.stylesWithEffects+xml",
/*
* Excel
*/
"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
/*
* Powerpoint
*/
"application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml",
"application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml",
"application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml",
"application/vnd.openxmlformats-officedocument.presentationml.slide+xml",
"application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml",
/*
* Powerpoint 2010
*/
"application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml",
"application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"
};
/** /**
* Office 2010 list of signed types (extensions). * Office 2010 list of signed types (extensions).
*/ */
public static final String[] signed = { private static final Set<String> signed = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
"powerPivotData", // "activeXControlBinary","aFChunk","attachedTemplate","attachedToolbars","audio","calcChain","chart","chartColorStyle",
"activeXControlBinary", // "chartLayout","chartsheet","chartStyle","chartUserShapes","commentAuthors","comments","connections","connectorXml",
"attachedToolbars", // "control","ctrlProp","customData","customData","customProperty","customXml","diagram","diagramColors",
"connectorXml", // "diagramColorsHeader","diagramData","diagramDrawing","diagramLayout","diagramLayoutHeader","diagramQuickStyle",
"downRev", // "diagramQuickStyleHeader","dialogsheet","dictionary","documentParts","downRev","drawing","endnotes","externalLink",
"functionPrototypes", // "externalLinkPath","font","fontTable","footer","footnotes","functionPrototypes","glossaryDocument","graphicFrameDoc",
"graphicFrameDoc", // "groupShapeXml","handoutMaster","hdphoto","header","hyperlink","image","ink","inkXml","keyMapCustomizations",
"groupShapeXml", // "legacyDiagramText","legacyDocTextInfo","mailMergeHeaderSource","mailMergeRecipientData","mailMergeSource","media",
"ink", // "notesMaster","notesSlide","numbering","officeDocument","officeDocument","oleObject","package","pictureXml",
"keyMapCustomizations", // "pivotCacheDefinition","pivotCacheRecords","pivotTable","powerPivotData","presProps","printerSettings","queryTable",
"legacyDiagramText", // "recipientData","settings","shapeXml","sharedStrings","sheetMetadata","slicer","slicer","slicerCache","slicerCache",
"legacyDocTextInfo", // "slide","slideLayout","slideMaster","slideUpdateInfo","slideUpdateUrl","smartTags","styles","stylesWithEffects",
"officeDocument", // "table","tableSingleCells","tableStyles","tags","theme","themeOverride","timeline","timelineCache","transform",
"pictureXml", // "ui/altText","ui/buttonSize","ui/controlID","ui/description","ui/enabled","ui/extensibility","ui/extensibility",
"shapeXml", // "ui/helperText","ui/imageID","ui/imageMso","ui/keyTip","ui/label","ui/lcid","ui/loud","ui/pressed","ui/progID",
"smartTags", // "ui/ribbonID","ui/showImage","ui/showLabel","ui/supertip","ui/target","ui/text","ui/title","ui/tooltip",
"ui/altText", // "ui/userCustomization","ui/visible","userXmlData","vbaProject","video","viewProps","vmlDrawing",
"ui/buttonSize", // "volatileDependencies","webSettings","wordVbaData","worksheet","wsSortMap","xlBinaryIndex",
"ui/controlID", // "xlExternalLinkPath/xlAlternateStartup","xlExternalLinkPath/xlLibrary","xlExternalLinkPath/xlPathMissing",
"ui/description", // "xlExternalLinkPath/xlStartup","xlIntlMacrosheet","xlMacrosheet","xmlMaps"
"ui/enabled", // )));
"ui/extensibility", //
"ui/helperText", //
"ui/imageID", //
"ui/imageMso", //
"ui/keyTip", //
"ui/label", //
"ui/lcid", //
"ui/loud", //
"ui/pressed", //
"ui/progID", //
"ui/ribbonID", //
"ui/showImage", //
"ui/showLabel", //
"ui/supertip", //
"ui/target", //
"ui/text", //
"ui/title", //
"ui/tooltip", //
"ui/userCustomization", //
"ui/visible", //
"userXmlData", //
"vbaProject", //
"wordVbaData", //
"wsSortMap", //
"xlBinaryIndex", //
"xlExternalLinkPath/xlAlternateStartup", //
"xlExternalLinkPath/xlLibrary", //
"xlExternalLinkPath/xlPathMissing", //
"xlExternalLinkPath/xlStartup", //
"xlIntlMacrosheet", //
"xlMacrosheet", //
"customData", //
"diagramDrawing", //
"hdphoto", //
"inkXml", //
"media", //
"slicer", //
"slicerCache", //
"stylesWithEffects", //
"ui/extensibility", //
"chartColorStyle", //
"chartLayout", //
"chartStyle", //
"dictionary", //
"timeline", //
"timelineCache", //
"aFChunk", //
"attachedTemplate", //
"audio", //
"calcChain", //
"chart", //
"chartsheet", //
"chartUserShapes", //
"commentAuthors", //
"comments", //
"connections", //
"control", //
"customProperty", //
"customXml", //
"diagramColors", //
"diagramData", //
"diagramLayout", //
"diagramQuickStyle", //
"dialogsheet", //
"drawing", //
"endnotes", //
"externalLink", //
"externalLinkPath", //
"font", //
"fontTable", //
"footer", //
"footnotes", //
"glossaryDocument", //
"handoutMaster", //
"header", //
"hyperlink", //
"image", //
"mailMergeHeaderSource", //
"mailMergeRecipientData", //
"mailMergeSource", //
"notesMaster", //
"notesSlide", //
"numbering", //
"officeDocument", //
"oleObject", //
"package", //
"pivotCacheDefinition", //
"pivotCacheRecords", //
"pivotTable", //
"presProps", //
"printerSettings", //
"queryTable", //
"recipientData", //
"settings", //
"sharedStrings", //
"sheetMetadata", //
"slide", //
"slideLayout", //
"slideMaster", //
"slideUpdateInfo", //
"slideUpdateUrl", //
"styles", //
"table", //
"tableSingleCells", //
"tableStyles", //
"tags", //
"theme", //
"themeOverride", //
"transform", //
"video", //
"viewProps", //
"volatileDependencies", //
"webSettings", //
"worksheet", //
"xmlMaps", //
"ctrlProp", //
"customData", //
"diagram", //
"diagramColorsHeader", //
"diagramLayoutHeader", //
"diagramQuickStyleHeader", //
"documentParts", //
"slicer", //
"slicerCache", //
"vmlDrawing" //
};
} }

View File

@ -25,10 +25,9 @@
package org.apache.poi.poifs.crypt.dsig.services; package org.apache.poi.poifs.crypt.dsig.services;
import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.OO_DIGSIG_NS;
import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_NS;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
@ -36,9 +35,8 @@ import java.security.Provider;
import java.security.Security; import java.security.Security;
import java.security.spec.AlgorithmParameterSpec; import java.security.spec.AlgorithmParameterSpec;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.TreeMap;
import javax.xml.crypto.Data; import javax.xml.crypto.Data;
import javax.xml.crypto.MarshalException; import javax.xml.crypto.MarshalException;
@ -50,23 +48,20 @@ import javax.xml.crypto.dsig.TransformException;
import javax.xml.crypto.dsig.TransformService; import javax.xml.crypto.dsig.TransformService;
import javax.xml.crypto.dsig.spec.TransformParameterSpec; import javax.xml.crypto.dsig.spec.TransformParameterSpec;
import org.apache.jcp.xml.dsig.internal.dom.ApacheNodeSetData;
import org.apache.poi.util.DocumentHelper;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.poi.util.XmlSort; import org.apache.xml.security.signature.XMLSignatureInput;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException; import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject; import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.CTRelationshipReference; import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.CTRelationshipReference;
import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.RelationshipReferenceDocument; import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.RelationshipReferenceDocument;
import org.openxmlformats.schemas.xpackage.x2006.relationships.CTRelationship;
import org.openxmlformats.schemas.xpackage.x2006.relationships.CTRelationships;
import org.openxmlformats.schemas.xpackage.x2006.relationships.RelationshipsDocument;
import org.openxmlformats.schemas.xpackage.x2006.relationships.STTargetMode;
import org.w3.x2000.x09.xmldsig.TransformDocument; import org.w3.x2000.x09.xmldsig.TransformDocument;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/** /**
* JSR105 implementation of the RelationshipTransform transformation. * JSR105 implementation of the RelationshipTransform transformation.
@ -89,7 +84,7 @@ public class RelationshipTransformService extends TransformService {
public static class RelationshipTransformParameterSpec implements TransformParameterSpec { public static class RelationshipTransformParameterSpec implements TransformParameterSpec {
List<String> sourceIds = new ArrayList<String>(); List<String> sourceIds = new ArrayList<String>();
public void addRelationshipReference(String relationshipId) { public void addRelationshipReference(String relationshipId) {
sourceIds.add(relationshipId); sourceIds.add(relationshipId);
} }
public boolean hasSourceIds() { public boolean hasSourceIds() {
return !sourceIds.isEmpty(); return !sourceIds.isEmpty();
@ -163,15 +158,13 @@ public class RelationshipTransformService extends TransformService {
LOG.log(POILogger.DEBUG, "marshallParams(parent,context)"); LOG.log(POILogger.DEBUG, "marshallParams(parent,context)");
DOMStructure domParent = (DOMStructure) parent; DOMStructure domParent = (DOMStructure) parent;
Element parentNode = (Element)domParent.getNode(); Element parentNode = (Element)domParent.getNode();
// parentNode.setAttributeNS(XML_NS, "xmlns:mdssi", XML_DIGSIG_NS);
Document doc = parentNode.getOwnerDocument(); Document doc = parentNode.getOwnerDocument();
for (String sourceId : this.sourceIds) { for (String sourceId : this.sourceIds) {
RelationshipReferenceDocument relRef = RelationshipReferenceDocument.Factory.newInstance(); Element el = doc.createElementNS(OO_DIGSIG_NS, "mdssi:RelationshipReference");
relRef.addNewRelationshipReference().setSourceId(sourceId); el.setAttributeNS(XML_NS, "xmlns:mdssi", OO_DIGSIG_NS);
Node n = relRef.getRelationshipReference().getDomNode(); el.setAttribute("SourceId", sourceId);
n = doc.importNode(n, true); parentNode.appendChild(el);
parentNode.appendChild(n);
} }
} }
@ -180,6 +173,13 @@ public class RelationshipTransformService extends TransformService {
return null; return null;
} }
/**
* The relationships transform takes the XML document from the Relationships part
* and converts it to another XML document.
*
* @see <a href="https://www.ecma-international.org/activities/Office%20Open%20XML%20Formats/Draft%20ECMA-376%203rd%20edition,%20March%202011/Office%20Open%20XML%20Part%202%20-%20Open%20Packaging%20Conventions.pdf">13.2.4.24 Relationships Transform Algorithm</a>
* @see <a href="https://stackoverflow.com/questions/36063375">XML Relationship Transform Algorithm</a>
*/
public Data transform(Data data, XMLCryptoContext context) throws TransformException { public Data transform(Data data, XMLCryptoContext context) throws TransformException {
LOG.log(POILogger.DEBUG, "transform(data,context)"); LOG.log(POILogger.DEBUG, "transform(data,context)");
LOG.log(POILogger.DEBUG, "data java type: " + data.getClass().getName()); LOG.log(POILogger.DEBUG, "data java type: " + data.getClass().getName());
@ -187,53 +187,40 @@ public class RelationshipTransformService extends TransformService {
LOG.log(POILogger.DEBUG, "URI: " + octetStreamData.getURI()); LOG.log(POILogger.DEBUG, "URI: " + octetStreamData.getURI());
InputStream octetStream = octetStreamData.getOctetStream(); InputStream octetStream = octetStreamData.getOctetStream();
RelationshipsDocument relDoc; Document doc;
try { try {
relDoc = RelationshipsDocument.Factory.parse(octetStream, DEFAULT_XML_OPTIONS); doc = DocumentHelper.readDocument(octetStream);
} catch (Exception e) { } catch (Exception e) {
throw new TransformException(e.getMessage(), e); throw new TransformException(e.getMessage(), e);
} }
LOG.log(POILogger.DEBUG, "relationships document", relDoc);
CTRelationships rels = relDoc.getRelationships(); // keep only those relationships which id is registered in the sourceIds
List<CTRelationship> relList = rels.getRelationshipList(); Element root = doc.getDocumentElement();
Iterator<CTRelationship> relIter = rels.getRelationshipList().iterator(); NodeList nl = root.getChildNodes();
while (relIter.hasNext()) { TreeMap<String,Element> rsList = new TreeMap<String,Element>();
CTRelationship rel = relIter.next(); for (int i=nl.getLength()-1; i>=0; i--) {
/* Node n = nl.item(i);
* See: ISO/IEC 29500-2:2008(E) - 13.2.4.24 Relationships Transform if ("Relationship".equals(n.getLocalName())) {
* Algorithm. Element el = (Element)n;
*/ String id = el.getAttribute("Id");
if (!this.sourceIds.contains(rel.getId())) { if (sourceIds.contains(id)) {
LOG.log(POILogger.DEBUG, "removing element: " + rel.getId()); String targetMode = el.getAttribute("TargetMode");
relIter.remove(); if ("".equals(targetMode)) {
} else { el.setAttribute("TargetMode", "Internal");
if (!rel.isSetTargetMode()) { }
rel.setTargetMode(STTargetMode.INTERNAL); rsList.put(id, el);
} }
} }
root.removeChild(n);
} }
// TODO: remove non element nodes ???
LOG.log(POILogger.DEBUG, "# Relationship elements", relList.size());
XmlSort.sort(rels, new Comparator<XmlCursor>(){
public int compare(XmlCursor c1, XmlCursor c2) {
String id1 = ((CTRelationship)c1.getObject()).getId();
String id2 = ((CTRelationship)c2.getObject()).getId();
return id1.compareTo(id2);
}
});
try { for (Element el : rsList.values()) {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); root.appendChild(el);
XmlOptions xo = new XmlOptions();
xo.setSaveNoXmlDecl();
relDoc.save(bos, xo);
return new OctetStreamData(new ByteArrayInputStream(bos.toByteArray()));
} catch (IOException e) {
throw new TransformException(e.getMessage(), e);
} }
LOG.log(POILogger.DEBUG, "# Relationship elements: ", rsList.size());
return new ApacheNodeSetData(new XMLSignatureInput(root));
} }
public Data transform(Data data, XMLCryptoContext context, OutputStream os) throws TransformException { public Data transform(Data data, XMLCryptoContext context, OutputStream os) throws TransformException {

View File

@ -1,88 +0,0 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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 org.apache.poi.util;
import java.util.Comparator;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
public final class XmlSort {
/**
* Sorts the children of <code>element</code> according to the order indicated by the
* comparator.
* @param element the element whose content is to be sorted. Only element children are sorted,
* attributes are not touched. When elements are reordered, all the text, comments and PIs
* follow the element that they come immediately after.
* @param comp a comparator that is to be used when comparing the <code>QName</code>s of two
* elements.
* @throws IllegalArgumentException if the input <code>XmlObject</code> does not represent
* an element
*/
public static void sort(XmlObject element, Comparator<XmlCursor> comp) {
XmlCursor headCursor = element.newCursor();
if (!headCursor.isStart()) {
throw new IllegalStateException("The element parameter must point to a STARTDOC");
}
// We use insertion sort to minimize the number of swaps, because each swap means
// moving a part of the document
/* headCursor points to the beginning of the list of the already sorted items and
listCursor points to the beginning of the list of unsorted items
At the beginning, headCursor points to the first element and listCursor points to the
second element. The algorithm ends when listCursor cannot be moved to the "next"
element in the unsorted list, i.e. the unsorted list becomes empty */
boolean moved = headCursor.toFirstChild();
if (!moved) {
// Cursor was not moved, which means that the given element has no children and
// therefore there is nothing to sort
return;
}
XmlCursor listCursor = headCursor.newCursor();
boolean moreElements = listCursor.toNextSibling();
while (moreElements) {
moved = false;
// While we can move the head of the unsorted list, it means that there are still
// items (elements) that need to be sorted
while (headCursor.comparePosition(listCursor) < 0) {
if (comp.compare(headCursor, listCursor) > 0) {
// We have found the position in the sorted list, insert the element and the
// text following the element in the current position
// Move the element
listCursor.moveXml(headCursor);
// Move the text following the element
while (!listCursor.isStart() && !listCursor.isEnd())
listCursor.moveXml(headCursor);
moreElements = listCursor.isStart();
moved = true;
break;
}
headCursor.toNextSibling();
}
if (!moved) {
// Because during the move of a fragment of XML, the listCursor is also moved, in
// case we didn't need to move XML (the new element to be inserted happened to
// be the last one in order), we need to move this cursor
moreElements = listCursor.toNextSibling();
}
// Reposition the head of the sorted list
headCursor.toParent();
headCursor.toFirstChild();
}
}
}

View File

@ -72,6 +72,7 @@ import org.apache.poi.poifs.crypt.dsig.services.RevocationData;
import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService; import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService;
import org.apache.poi.poifs.crypt.dsig.services.TimeStampService; import org.apache.poi.poifs.crypt.dsig.services.TimeStampService;
import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator; import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator;
import org.apache.poi.poifs.storage.RawDataUtil;
import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.util.DocumentHelper; import org.apache.poi.util.DocumentHelper;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
@ -118,6 +119,70 @@ public class TestSignatureInfo {
additionalJar == null || additionalJar.trim().length() == 0); additionalJar == null || additionalJar.trim().length() == 0);
} }
@Test
public void bug61182() throws Exception {
String pfxInput =
"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";
Calendar cal = LocaleUtil.getLocaleCalendar(LocaleUtil.TIMEZONE_UTC);
cal.clear();
cal.set(2017, 6, 1);
SignatureConfig signatureConfig = prepareConfig("test", "CN=Test", pfxInput);
signatureConfig.setExecutionTime(cal.getTime());
SignatureInfo si = new SignatureInfo();
si.setSignatureConfig(signatureConfig);
XSSFWorkbook wb1 = new XSSFWorkbook();
wb1.createSheet().createRow(1).createCell(1).setCellValue("Test");
ByteArrayOutputStream bos = new ByteArrayOutputStream(100000);
wb1.write(bos);
wb1.close();
OPCPackage pkg1 = OPCPackage.open(new ByteArrayInputStream(bos.toByteArray()));
signatureConfig.setOpcPackage(pkg1);
si.confirmSignature();
assertTrue(si.verifySignature());
bos.reset();
pkg1.save(bos);
pkg1.close();
XSSFWorkbook wb2 = new XSSFWorkbook(new ByteArrayInputStream(bos.toByteArray()));
assertEquals("Test", wb2.getSheetAt(0).getRow(1).getCell(1).getStringCellValue());
OPCPackage pkg2 = wb2.getPackage();
signatureConfig.setOpcPackage(pkg2);
assertTrue(si.verifySignature());
String signExp =
"Lxp2LFa+0YWGOBL8zVdf7SWRQiNK/Tt85W+kmH1bunlua030BKbQc6yWIIk6gN6jCTtrJ1h2eMRbLwymygOUpM"+
"dd0MeQY3mMWRSO9qEW87SQvyDqBh71zXWW3ZYET+vJWr3BCNEtXCy8jZvgXqILBGk5vMJW/EYaUEhBcDGjCm0=";
String signAct = si.getSignatureParts().iterator().next().
getSignatureDocument().getSignature().getSignatureValue().getStringValue();
assertEquals(signExp, signAct);
pkg2.close();
wb2.close();
}
@Test @Test
public void office2007prettyPrintedRels() throws Exception { public void office2007prettyPrintedRels() throws Exception {
OPCPackage pkg = OPCPackage.open(testdata.getFile("office2007prettyPrintedRels.docx"), PackageAccess.READ); OPCPackage pkg = OPCPackage.open(testdata.getFile("office2007prettyPrintedRels.docx"), PackageAccess.READ);
@ -611,15 +676,21 @@ public class TestSignatureInfo {
pkg.close(); pkg.close();
} }
} }
private void sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception { private SignatureConfig prepareConfig(String alias, String signerDn, String pfxInput) throws Exception {
initKeyPair(alias, signerDn); initKeyPair(alias, signerDn, pfxInput);
SignatureConfig signatureConfig = new SignatureConfig(); SignatureConfig signatureConfig = new SignatureConfig();
signatureConfig.setKey(keyPair.getPrivate()); signatureConfig.setKey(keyPair.getPrivate());
signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); signatureConfig.setSigningCertificateChain(Collections.singletonList(x509));
signatureConfig.setExecutionTime(cal.getTime()); signatureConfig.setExecutionTime(cal.getTime());
signatureConfig.setDigestAlgo(HashAlgorithm.sha1); signatureConfig.setDigestAlgo(HashAlgorithm.sha1);
return signatureConfig;
}
private void sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception {
SignatureConfig signatureConfig = prepareConfig(alias, signerDn, null);
signatureConfig.setOpcPackage(pkgCopy); signatureConfig.setOpcPackage(pkgCopy);
SignatureInfo si = new SignatureInfo(); SignatureInfo si = new SignatureInfo();
@ -656,13 +727,21 @@ public class TestSignatureInfo {
} }
private void initKeyPair(String alias, String subjectDN) throws Exception { private void initKeyPair(String alias, String subjectDN) throws Exception {
initKeyPair(alias, subjectDN, null);
}
private void initKeyPair(String alias, String subjectDN, String pfxInput) throws Exception {
final char password[] = "test".toCharArray(); final char password[] = "test".toCharArray();
File file = new File("build/test.pfx"); File file = new File("build/test.pfx");
KeyStore keystore = KeyStore.getInstance("PKCS12"); KeyStore keystore = KeyStore.getInstance("PKCS12");
if (file.exists()) { if (pfxInput != null) {
FileInputStream fis = new FileInputStream(file); InputStream fis = new ByteArrayInputStream(RawDataUtil.decompress(pfxInput));
keystore.load(fis, password);
fis.close();
} else if (file.exists()) {
InputStream fis = new FileInputStream(file);
keystore.load(fis, password); keystore.load(fis, password);
fis.close(); fis.close();
} else { } else {
@ -685,9 +764,12 @@ public class TestSignatureInfo {
, notBefore, notAfter, null, keyPair.getPrivate(), true, 0, null, null, keyUsage); , notBefore, notAfter, null, keyPair.getPrivate(), true, 0, null, null, keyUsage);
keystore.setKeyEntry(alias, keyPair.getPrivate(), password, new Certificate[]{x509}); keystore.setKeyEntry(alias, keyPair.getPrivate(), password, new Certificate[]{x509});
FileOutputStream fos = new FileOutputStream(file);
keystore.store(fos, password); if (pfxInput == null) {
fos.close(); FileOutputStream fos = new FileOutputStream(file);
keystore.store(fos, password);
fos.close();
}
} }
} }
@ -701,8 +783,7 @@ public class TestSignatureInfo {
// in the Sonar Maven runs where we are at a different source directory // in the Sonar Maven runs where we are at a different source directory
File buildDir = new File("build"); File buildDir = new File("build");
if(!buildDir.exists()) { if(!buildDir.exists()) {
assertTrue("Failed to create " + buildDir.getAbsolutePath(), assertTrue("Failed to create " + buildDir.getAbsolutePath(), buildDir.mkdirs());
buildDir.mkdirs());
} }
File tmpFile = new File(buildDir, "sigtest"+extension); File tmpFile = new File(buildDir, "sigtest"+extension);