From: Andreas Beeker Date: Wed, 28 Jun 2017 21:38:23 +0000 (+0000) Subject: Bug 61182 - Invalid signature created for streamed xslx file X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=51b46a9e4eb69205cae46fed5bce3badaecce3df;p=poi.git 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 --- diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java index 860d711590..735408b415 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java @@ -74,8 +74,12 @@ public final class StreamHelper { out.flush(); // only flush, don't close! } }); + // xmlContent.setXmlStandalone(true); 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.transform(xmlSource, outputTarget); } catch (TransformerException e) { diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java index 3b21eb8658..338174fceb 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java @@ -40,24 +40,32 @@ public class SignatureMarshalListener implements EventListener, SignatureConfigu this.target.set(target); } + @Override public void handleEvent(Event e) { - if (!(e instanceof MutationEvent)) return; + if (!(e instanceof MutationEvent)) { + return; + } MutationEvent mutEvt = (MutationEvent)e; EventTarget et = mutEvt.getTarget(); - if (!(et instanceof Element)) return; + if (!(et instanceof Element)) { + return; + } handleElement((Element)et); } public void handleElement(Element el) { EventTarget target = this.target.get(); - String packageId = signatureConfig.getPackageSignatureId(); + if (el.hasAttribute("Id")) { el.setIdAttribute("Id", true); } setListener(target, this, false); - if (packageId.equals(el.getAttribute("Id"))) { - el.setAttributeNS(XML_NS, "xmlns:mdssi", OO_DIGSIG_NS); + if (OO_DIGSIG_NS.equals(el.getNamespaceURI())) { + 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); setListener(target, this, true); @@ -86,6 +94,7 @@ public class SignatureMarshalListener implements EventListener, SignatureConfigu } } + @Override public void setSignatureConfig(SignatureConfig signatureConfig) { this.signatureConfig = signatureConfig; } diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java index 5affefc07d..d8164cdc68 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java @@ -18,9 +18,9 @@ /* ==================================================================== This product contains an ASLv2 licensed version of the OOXML signer 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. - ================================================================= */ + ================================================================= */ package org.apache.poi.poifs.crypt.dsig.facets; @@ -29,6 +29,9 @@ import java.net.URISyntaxException; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -70,13 +73,13 @@ import com.microsoft.schemas.office.x2006.digsig.SignatureInfoV1Document; /** * Office OpenXML Signature Facet implementation. - * - * @author fcorneli + * * @see [MS-OFFCRYPTO]: Office Document Cryptography Structure */ public class OOXMLSignatureFacet extends SignatureFacet { private static final POILogger LOG = POILogFactory.getLogger(OOXMLSignatureFacet.class); + private static final String ID_PACKAGE_OBJECT = "idPackageObject"; @Override public void preSign( @@ -98,17 +101,16 @@ public class OOXMLSignatureFacet extends SignatureFacet { List manifestReferences = new ArrayList(); addManifestReferences(manifestReferences); Manifest manifest = getSignatureFactory().newManifest(manifestReferences); - - String objectId = "idPackageObject"; // really has to be this value. + List objectContent = new ArrayList(); objectContent.add(manifest); addSignatureTime(document, objectContent); - XMLObject xo = getSignatureFactory().newXMLObject(objectContent, objectId, null, null); + XMLObject xo = getSignatureFactory().newXMLObject(objectContent, ID_PACKAGE_OBJECT, null, null); 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); } @@ -121,7 +123,7 @@ public class OOXMLSignatureFacet extends SignatureFacet { Set digestedPartNames = new HashSet(); for (PackagePart pp : relsEntryNames) { - String baseUri = pp.getPartName().getName().replaceFirst("(.*)/_rels/.*", "$1"); + final String baseUri = pp.getPartName().getName().replaceFirst("(.*)/_rels/.*", "$1"); PackageRelationshipCollection prc; try { @@ -130,11 +132,11 @@ public class OOXMLSignatureFacet extends SignatureFacet { } catch (InvalidFormatException e) { throw new XMLSignatureException("Invalid relationship descriptor: "+pp.getPartName().getName(), e); } - + RelationshipTransformParameterSpec parameterSpec = new RelationshipTransformParameterSpec(); for (PackageRelationship relationship : prc) { String relationshipType = relationship.getRelationshipType(); - + /* * ECMA-376 Part 2 - 3rd edition * 13.2.4.16 Manifest Element @@ -144,22 +146,20 @@ public class OOXMLSignatureFacet extends SignatureFacet { continue; } - if (!isSignedRelationship(relationshipType)) continue; + if (!isSignedRelationship(relationshipType)) { + continue; + } parameterSpec.addRelationshipReference(relationship.getId()); - // TODO: find a better way ... - String partName = relationship.getTargetURI().toString(); - if (!partName.startsWith(baseUri)) { - partName = baseUri + partName; - } - try { - partName = new URI(partName).normalize().getPath().replace('\\', '/'); - LOG.log(POILogger.DEBUG, "part name: " + partName); - } catch (URISyntaxException e) { - throw new XMLSignatureException(e); + String partName = normalizePartName(relationship.getTargetURI(), baseUri); + + // We only digest a part once. + if (digestedPartNames.contains(partName)) { + continue; } - + digestedPartNames.add(partName); + String contentType; try { PackagePartName relName = PackagingURIHelper.createPartName(partName); @@ -168,32 +168,52 @@ public class OOXMLSignatureFacet extends SignatureFacet { } catch (InvalidFormatException e) { throw new XMLSignatureException(e); } - + if (relationshipType.endsWith("customXml") && !(contentType.equals("inkml+xml") || contentType.equals("text/xml"))) { LOG.log(POILogger.DEBUG, "skipping customXml with content type: " + contentType); continue; } - - if (!digestedPartNames.contains(partName)) { - // We only digest a part once. - String uri = partName + "?ContentType=" + contentType; - Reference reference = newReference(uri, null, null, null, null); - manifestReferences.add(reference); - digestedPartNames.add(partName); - } + + String uri = partName + "?ContentType=" + contentType; + Reference reference = newReference(uri, null, null, null, null); + manifestReferences.add(reference); } - + if (parameterSpec.hasSourceIds()) { List transforms = new ArrayList(); transforms.add(newTransform(RelationshipTransformService.TRANSFORM_URI, parameterSpec)); transforms.add(newTransform(CanonicalizationMethod.INCLUSIVE)); - String uri = pp.getPartName().getName() + String uri = normalizePartName(pp.getPartName().getURI(), baseUri) + "?ContentType=application/vnd.openxmlformats-package.relationships+xml"; Reference reference = newReference(uri, transforms, null, null, null); manifestReferences.add(reference); } } + + Collections.sort(manifestReferences, new Comparator() { + 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()); Element n = (Element)document.importNode(ctSigV1.getDomNode(), true); n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS); - + List signatureInfoContent = new ArrayList(); signatureInfoContent.add(new DOMStructure(n)); SignatureProperty signatureInfoSignatureProperty = getSignatureFactory() @@ -268,208 +288,33 @@ public class OOXMLSignatureFacet extends SignatureFacet { protected static boolean isSignedRelationship(String relationshipType) { LOG.log(POILogger.DEBUG, "relationship type: " + relationshipType); - for (String signedTypeExtension : signed) { - if (relationshipType.endsWith(signedTypeExtension)) { - return true; - } - } - if (relationshipType.endsWith("customXml")) { - LOG.log(POILogger.DEBUG, "customXml relationship type"); - return true; - } - return false; + String rt = relationshipType.replaceFirst(".*/relationships/", ""); + return (signed.contains(rt) || rt.endsWith("customXml")); } - - 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). */ - public static final String[] signed = { - "powerPivotData", // - "activeXControlBinary", // - "attachedToolbars", // - "connectorXml", // - "downRev", // - "functionPrototypes", // - "graphicFrameDoc", // - "groupShapeXml", // - "ink", // - "keyMapCustomizations", // - "legacyDiagramText", // - "legacyDocTextInfo", // - "officeDocument", // - "pictureXml", // - "shapeXml", // - "smartTags", // - "ui/altText", // - "ui/buttonSize", // - "ui/controlID", // - "ui/description", // - "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" // - }; + private static final Set signed = Collections.unmodifiableSet(new HashSet(Arrays.asList( + "activeXControlBinary","aFChunk","attachedTemplate","attachedToolbars","audio","calcChain","chart","chartColorStyle", + "chartLayout","chartsheet","chartStyle","chartUserShapes","commentAuthors","comments","connections","connectorXml", + "control","ctrlProp","customData","customData","customProperty","customXml","diagram","diagramColors", + "diagramColorsHeader","diagramData","diagramDrawing","diagramLayout","diagramLayoutHeader","diagramQuickStyle", + "diagramQuickStyleHeader","dialogsheet","dictionary","documentParts","downRev","drawing","endnotes","externalLink", + "externalLinkPath","font","fontTable","footer","footnotes","functionPrototypes","glossaryDocument","graphicFrameDoc", + "groupShapeXml","handoutMaster","hdphoto","header","hyperlink","image","ink","inkXml","keyMapCustomizations", + "legacyDiagramText","legacyDocTextInfo","mailMergeHeaderSource","mailMergeRecipientData","mailMergeSource","media", + "notesMaster","notesSlide","numbering","officeDocument","officeDocument","oleObject","package","pictureXml", + "pivotCacheDefinition","pivotCacheRecords","pivotTable","powerPivotData","presProps","printerSettings","queryTable", + "recipientData","settings","shapeXml","sharedStrings","sheetMetadata","slicer","slicer","slicerCache","slicerCache", + "slide","slideLayout","slideMaster","slideUpdateInfo","slideUpdateUrl","smartTags","styles","stylesWithEffects", + "table","tableSingleCells","tableStyles","tags","theme","themeOverride","timeline","timelineCache","transform", + "ui/altText","ui/buttonSize","ui/controlID","ui/description","ui/enabled","ui/extensibility","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","video","viewProps","vmlDrawing", + "volatileDependencies","webSettings","wordVbaData","worksheet","wsSortMap","xlBinaryIndex", + "xlExternalLinkPath/xlAlternateStartup","xlExternalLinkPath/xlLibrary","xlExternalLinkPath/xlPathMissing", + "xlExternalLinkPath/xlStartup","xlIntlMacrosheet","xlMacrosheet","xmlMaps" + ))); } \ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java index 55518772fa..ff0564bbac 100644 --- a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java @@ -25,10 +25,9 @@ package org.apache.poi.poifs.crypt.dsig.services; 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.OutputStream; import java.security.InvalidAlgorithmParameterException; @@ -36,9 +35,8 @@ import java.security.Provider; import java.security.Security; import java.security.spec.AlgorithmParameterSpec; import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; import java.util.List; +import java.util.TreeMap; import javax.xml.crypto.Data; 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.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.POILogger; -import org.apache.poi.util.XmlSort; -import org.apache.xmlbeans.XmlCursor; +import org.apache.xml.security.signature.XMLSignatureInput; import org.apache.xmlbeans.XmlException; 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.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.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; /** * JSR105 implementation of the RelationshipTransform transformation. @@ -89,7 +84,7 @@ public class RelationshipTransformService extends TransformService { public static class RelationshipTransformParameterSpec implements TransformParameterSpec { List sourceIds = new ArrayList(); public void addRelationshipReference(String relationshipId) { - sourceIds.add(relationshipId); + sourceIds.add(relationshipId); } public boolean hasSourceIds() { return !sourceIds.isEmpty(); @@ -163,15 +158,13 @@ public class RelationshipTransformService extends TransformService { LOG.log(POILogger.DEBUG, "marshallParams(parent,context)"); DOMStructure domParent = (DOMStructure) parent; Element parentNode = (Element)domParent.getNode(); - // parentNode.setAttributeNS(XML_NS, "xmlns:mdssi", XML_DIGSIG_NS); Document doc = parentNode.getOwnerDocument(); for (String sourceId : this.sourceIds) { - RelationshipReferenceDocument relRef = RelationshipReferenceDocument.Factory.newInstance(); - relRef.addNewRelationshipReference().setSourceId(sourceId); - Node n = relRef.getRelationshipReference().getDomNode(); - n = doc.importNode(n, true); - parentNode.appendChild(n); + Element el = doc.createElementNS(OO_DIGSIG_NS, "mdssi:RelationshipReference"); + el.setAttributeNS(XML_NS, "xmlns:mdssi", OO_DIGSIG_NS); + el.setAttribute("SourceId", sourceId); + parentNode.appendChild(el); } } @@ -180,6 +173,13 @@ public class RelationshipTransformService extends TransformService { return null; } + /** + * The relationships transform takes the XML document from the Relationships part + * and converts it to another XML document. + * + * @see 13.2.4.24 Relationships Transform Algorithm + * @see XML Relationship Transform Algorithm + */ public Data transform(Data data, XMLCryptoContext context) throws TransformException { LOG.log(POILogger.DEBUG, "transform(data,context)"); 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()); InputStream octetStream = octetStreamData.getOctetStream(); - RelationshipsDocument relDoc; + Document doc; try { - relDoc = RelationshipsDocument.Factory.parse(octetStream, DEFAULT_XML_OPTIONS); + doc = DocumentHelper.readDocument(octetStream); } catch (Exception e) { throw new TransformException(e.getMessage(), e); } - LOG.log(POILogger.DEBUG, "relationships document", relDoc); - CTRelationships rels = relDoc.getRelationships(); - List relList = rels.getRelationshipList(); - Iterator relIter = rels.getRelationshipList().iterator(); - while (relIter.hasNext()) { - CTRelationship rel = relIter.next(); - /* - * See: ISO/IEC 29500-2:2008(E) - 13.2.4.24 Relationships Transform - * Algorithm. - */ - if (!this.sourceIds.contains(rel.getId())) { - LOG.log(POILogger.DEBUG, "removing element: " + rel.getId()); - relIter.remove(); - } else { - if (!rel.isSetTargetMode()) { - rel.setTargetMode(STTargetMode.INTERNAL); + // keep only those relationships which id is registered in the sourceIds + Element root = doc.getDocumentElement(); + NodeList nl = root.getChildNodes(); + TreeMap rsList = new TreeMap(); + for (int i=nl.getLength()-1; i>=0; i--) { + Node n = nl.item(i); + if ("Relationship".equals(n.getLocalName())) { + Element el = (Element)n; + String id = el.getAttribute("Id"); + if (sourceIds.contains(id)) { + String targetMode = el.getAttribute("TargetMode"); + if ("".equals(targetMode)) { + el.setAttribute("TargetMode", "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(){ - public int compare(XmlCursor c1, XmlCursor c2) { - String id1 = ((CTRelationship)c1.getObject()).getId(); - String id2 = ((CTRelationship)c2.getObject()).getId(); - return id1.compareTo(id2); - } - }); - try { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - 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); + for (Element el : rsList.values()) { + root.appendChild(el); } + + 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 { diff --git a/src/ooxml/java/org/apache/poi/util/XmlSort.java b/src/ooxml/java/org/apache/poi/util/XmlSort.java deleted file mode 100644 index 09f05fb463..0000000000 --- a/src/ooxml/java/org/apache/poi/util/XmlSort.java +++ /dev/null @@ -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 element 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 QNames of two - * elements. - * @throws IllegalArgumentException if the input XmlObject does not represent - * an element - */ - public static void sort(XmlObject element, Comparator 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(); - } - } -} - \ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java index 3c172f385d..93a58950ca 100644 --- a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java +++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java @@ -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.TimeStampService; 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.util.DocumentHelper; import org.apache.poi.util.IOUtils; @@ -118,6 +119,70 @@ public class TestSignatureInfo { 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 public void office2007prettyPrintedRels() throws Exception { OPCPackage pkg = OPCPackage.open(testdata.getFile("office2007prettyPrintedRels.docx"), PackageAccess.READ); @@ -611,15 +676,21 @@ public class TestSignatureInfo { pkg.close(); } } - - private void sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception { - initKeyPair(alias, signerDn); + + private SignatureConfig prepareConfig(String alias, String signerDn, String pfxInput) throws Exception { + initKeyPair(alias, signerDn, pfxInput); SignatureConfig signatureConfig = new SignatureConfig(); signatureConfig.setKey(keyPair.getPrivate()); signatureConfig.setSigningCertificateChain(Collections.singletonList(x509)); signatureConfig.setExecutionTime(cal.getTime()); 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); SignatureInfo si = new SignatureInfo(); @@ -656,13 +727,21 @@ public class TestSignatureInfo { } 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(); File file = new File("build/test.pfx"); KeyStore keystore = KeyStore.getInstance("PKCS12"); - if (file.exists()) { - FileInputStream fis = new FileInputStream(file); + if (pfxInput != null) { + 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); fis.close(); } else { @@ -685,9 +764,12 @@ public class TestSignatureInfo { , notBefore, notAfter, null, keyPair.getPrivate(), true, 0, null, null, keyUsage); keystore.setKeyEntry(alias, keyPair.getPrivate(), password, new Certificate[]{x509}); - FileOutputStream fos = new FileOutputStream(file); - keystore.store(fos, password); - fos.close(); + + if (pfxInput == null) { + 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 File buildDir = new File("build"); if(!buildDir.exists()) { - assertTrue("Failed to create " + buildDir.getAbsolutePath(), - buildDir.mkdirs()); + assertTrue("Failed to create " + buildDir.getAbsolutePath(), buildDir.mkdirs()); } File tmpFile = new File(buildDir, "sigtest"+extension);