git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1800207 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_17_FINAL
@@ -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) { |
@@ -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; | |||
} |
@@ -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 <a href="http://msdn.microsoft.com/en-us/library/cc313071.aspx">[MS-OFFCRYPTO]: Office Document Cryptography Structure</a> | |||
*/ | |||
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<Reference> manifestReferences = new ArrayList<Reference>(); | |||
addManifestReferences(manifestReferences); | |||
Manifest manifest = getSignatureFactory().newManifest(manifestReferences); | |||
String objectId = "idPackageObject"; // really has to be this value. | |||
List<XMLStructure> objectContent = new ArrayList<XMLStructure>(); | |||
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<String> digestedPartNames = new HashSet<String>(); | |||
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<Transform> transforms = new ArrayList<Transform>(); | |||
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<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()); | |||
Element n = (Element)document.importNode(ctSigV1.getDomNode(), true); | |||
n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS); | |||
List<XMLStructure> signatureInfoContent = new ArrayList<XMLStructure>(); | |||
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<String> signed = Collections.unmodifiableSet(new HashSet<String>(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" | |||
))); | |||
} |
@@ -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<String> sourceIds = new ArrayList<String>(); | |||
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 <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 { | |||
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<CTRelationship> relList = rels.getRelationshipList(); | |||
Iterator<CTRelationship> 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<String,Element> rsList = new TreeMap<String,Element>(); | |||
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<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 { | |||
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 { |
@@ -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(); | |||
} | |||
} | |||
} | |||
@@ -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); | |||