Apache POI contains support for reading few variants of encrypted office files:
-
-
Binary formats (.xls, .ppt, .doc, ...)
+
Apache POI contains support for reading few variants of encrypted office files:
+
+
Binary formats (.xls, .ppt, .doc, ...)
encryption is format-dependent and needs to be implemented per format differently.
Use
Biff8EncryptionKey.setCurrentUserPassword(String password)
@@ -41,7 +41,7 @@
Setting a null password before saving removes the password protection.
The password is set in a thread local variable. Do not forget to reset it to null after text extraction.
-
XML-based formats (.xlsx, .pptx, .docx, ...)
+
XML-based formats (.xlsx, .pptx, .docx, ...)
use the same encryption logic over all formats. When encrypted, the zipped files will be
stored within an OLE file in the EncryptedPackage stream.
If you plan to use POI to actually generate encrypted documents, be aware not to use anything less than
@@ -54,13 +54,13 @@
JDK7,
JDK8).
-
+
-
Some "write-protected" files are encrypted with the built-in password "VelvetSweatshop", POI can read that files too.
-
+
Some "write-protected" files are encrypted with the built-in password "VelvetSweatshop", POI can read that files too.
+
Supported feature matrix
-
+
Encryption
@@ -87,7 +87,7 @@
Yes (since 3.17)
-
+
XSSF
XSLF
XWPF
@@ -117,22 +117,22 @@
Yes
-
+
*) the xor encryption is flawed and works only for very small files - see #59857.
**) the MS-OFFCRYPTO
documentation only mentions the RC4 (without CryptoAPI) encryption as a "in place" encryption, but
- apparently there's also a container based method with that key generation logic.
+ apparently there's also a container based method with that key generation logic.
Binary formats
-
As mentioned above, use
+
As mentioned above, use
Biff8EncryptionKey.setCurrentUserPassword(String password)
to specify the password.
-
+
-
+
XML-based formats - Decryption
-
XML-based formats are stored in OLE-package stream "EncryptedPackage". Use org.apache.poi.poifs.crypt.Decryptor
- to decode file:
+
XML-based formats are stored in OLE-package stream "EncryptedPackage". Use org.apache.poi.poifs.crypt.Decryptor
+ to decode file:
-
-
If you want to read file encrypted with build-in password, use Decryptor.DEFAULT_PASSWORD.
+
If you want to read file encrypted with build-in password, use Decryptor.DEFAULT_PASSWORD.
-
+
XML-based formats - Encryption
Encrypting a file is similar to the above decryption process. Basically you'll need to choose between
binaryRC4, standard and agile encryption,
@@ -213,10 +213,10 @@
// Write out the encrypted version
FileOutputStream fos = new FileOutputStream("...");
fs.writeFilesystem(fos);
-fos.close();
+fos.close();
-
+
XML-based formats - Signing (XML Signature)
An Office document can be digital signed by a XML Signature
to protect it from unauthorized modifications, i.e. modifications without having the original certificate.
@@ -231,17 +231,17 @@
BouncyCastle bcpkix and bcprov (tested against 1.53)
Apache Santuario "xmlsec" (tested against 2.0.6)
-
and slf4j-api (tested against 1.7.12)
+
and slf4j-api (tested against 1.7.12)
Depending on the configuration
and the activated facets
various XAdES levels are supported - the support for higher levels (XAdES-T+)
depend on supporting services and although the code is adopted, the integration is not well tested ... please support us on
- integration (testing) with timestamp and revocation (OCSP) services.
+ integration (testing) with timestamp and revocation (OCSP) services.
Further test examples can be found in the corresponding test class.
-
+
Validating a signed office document
@@ -254,9 +254,9 @@
...
-
+
Signing an office document
-
+
// loading the keystore - pkcs12 is used here, but of course jks & co are also valid
// the keystore needs to contain a private key and it's certificate having a
@@ -304,6 +304,130 @@
and other files
that are needed for this example.
+
+ Debugging XML signature issues
+
Finding the source of a XML signature problem can be sometimes a pain in the ... neck, because
+ the hashing of the canonicalized form is more or less intransparent done in the background.
+
+
+
One of the tripping hazards are different
+ linebreaks in Windows/Unix, therefore use the non-indent form of the xmls.
+
+
The next thing is to compare successful signed documents from Office vs. POIs generated signature,
+ i.e. unzip both files and look for differences. Usually the package relations (*.rels) will be different,
+ and the sig1.xml, core.xml and [Content_Types].xml due to different order of the references.
+
+
The package relationsships (*.rels) will be specially handled, i.e. they will be filtered and only
+ a subset will be processed - see 13.2.4.24 Relationships Transform Algorithm.
+
+
To check the processed files in the canonicalized form, the below UnsyncBufferedOutputStream class needs
+ to be injected/replaced. Put the .class file in separate directory and add the following JVM parameters:
+
+
+-Djava.io.tmpdir=<custom temp directory>
+-Xbootclasspath/p:<preload dir, which contains /org/apache/xml/security/utils/UnsyncBufferedOutputStream.class>
+-Dorg.apache.poi.util.POILogger=org.apache.poi.util.CommonsLogger
+-Djava.util.logging.config.file=<a dir containing ...>/logging.properties
+
+
+ UnsyncBufferedOutputStream:
+
+package org.apache.xml.security.utils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public class UnsyncBufferedOutputStream extends OutputStream {
+ static final int size = 8*1024;
+ static int filecnt = 0;
+
+ private int pointer = 0;
+ private final OutputStream out;
+ private final FileOutputStream out2;
+
+ private final byte[] buf;
+
+ public UnsyncBufferedOutputStream(OutputStream out) {
+ buf = new byte[size];
+ this.out = out;
+ synchronized(UnsyncBufferedOutputStream.class) {
+ try {
+ String tmpDir = System.getProperty("java.io.tmpdir");
+ if (tmpDir == null) {
+ tmpDir = "build";
+ }
+ File f = new File(tmpDir, "unsync-"+filecnt+".xml");
+ out2 = new FileOutputStream(f);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ filecnt++;
+ }
+ }
+ }
+
+ public void write(byte[] arg0) throws IOException {
+ write(arg0, 0, arg0.length);
+ }
+
+ public void write(byte[] arg0, int arg1, int len) throws IOException {
+ int newLen = pointer+len;
+ if (newLen > size) {
+ flushBuffer();
+ if (len > size) {
+ out.write(arg0, arg1,len);
+ out2.write(arg0, arg1,len);
+ return;
+ }
+ newLen = len;
+ }
+ System.arraycopy(arg0, arg1, buf, pointer, len);
+ pointer = newLen;
+ }
+
+ private void flushBuffer() throws IOException {
+ if (pointer > 0) {
+ out.write(buf, 0, pointer);
+ out2.write(buf, 0, pointer);
+ }
+ pointer = 0;
+
+ }
+
+ public void write(int arg0) throws IOException {
+ if (pointer >= size) {
+ flushBuffer();
+ }
+ buf[pointer++] = (byte)arg0;
+
+ }
+
+ public void flush() throws IOException {
+ flushBuffer();
+ out.flush();
+ out2.flush();
+ }
+
+ public void close() throws IOException {
+ flush();
+ out.close();
+ out2.close();
+ }
+
+}
+
+
+
+ logging.properties
+
+handlers = org.slf4j.bridge.SLF4JBridgeHandler
+.level=ALL
+org.slf4j.bridge.SLF4JBridgeHandler.level=ALL
+
+
+
--- src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java (revision 1799756)
+++ src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java (working copy)
@@ -74,8 +74,12 @@
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) {
--- src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java (revision 1799756)
+++ src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java (working copy)
@@ -40,24 +40,32 @@
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 @@
}
}
+ @Override
public void setSignatureConfig(SignatureConfig signatureConfig) {
this.signatureConfig = signatureConfig;
}
--- src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java (revision 1799756)
+++ src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java (working copy)
@@ -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.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 @@
/**
* 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 @@
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 @@
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 @@
} 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 @@
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;
+ String partName = normalizePartName(relationship.getTargetURI(), baseUri);
+
+ // We only digest a part once.
+ if (digestedPartNames.contains(partName)) {
+ continue;
}
- try {
- partName = new URI(partName).normalize().getPath().replace('\\', '/');
- LOG.log(POILogger.DEBUG, "part name: " + partName);
- } catch (URISyntaxException e) {
- throw new XMLSignatureException(e);
- }
-
+ digestedPartNames.add(partName);
+
String contentType;
try {
PackagePartName relName = PackagingURIHelper.createPartName(partName);
@@ -168,35 +168,55 @@
} 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;
+ }
+
protected void addSignatureTime(Document document, List objectContent) {
/*
* SignatureTime
@@ -236,7 +256,7 @@
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 @@
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"
+ )));
}
--- src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java (revision 1799756)
+++ src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java (working copy)
@@ -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.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.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 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 @@
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 @@
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 @@
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);
}
+
+ for (Element el : rsList.values()) {
+ root.appendChild(el);
+ }
- // TODO: remove non element nodes ???
- LOG.log(POILogger.DEBUG, "# Relationship elements", relList.size());
+ LOG.log(POILogger.DEBUG, "# Relationship elements: ", rsList.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);
- }
+ return new ApacheNodeSetData(new XMLSignatureInput(root));
}
public Data transform(Data data, XMLCryptoContext context, OutputStream os) throws TransformException {
--- src/ooxml/java/org/apache/poi/util/XmlSort.java (revision 1799756)
+++ src/ooxml/java/org/apache/poi/util/XmlSort.java (nonexistent)
@@ -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();
- }
- }
-}
-
-native
--- src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java (revision 1799756)
+++ src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java (working copy)
@@ -72,6 +72,7 @@
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTOleObject;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTOleObjects;
import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet;
+import org.openxmlformats.schemas.spreadsheetml.x2006.main.STDvAspect;
/**
* Represents a SpreadsheetML drawing
@@ -426,7 +427,8 @@
CTOleObjects oo = cwb.isSetOleObjects() ? cwb.getOleObjects() : cwb.addNewOleObjects();
CTOleObject ole1 = oo.addNewOleObject();
- ole1.setProgId("Package");
+ ole1.setProgId("Packager Shell Object");
+ ole1.setDvAspect(STDvAspect.DVASPECT_ICON);
ole1.setShapeId(shapeId);
ole1.setId(olePR.getId());
--- src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java (revision 1799756)
+++ src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java (working copy)
@@ -119,6 +119,37 @@
}
@Test
+ public void bug61182() throws Exception {
+ SignatureConfig signatureConfig = prepareConfig("test", "CN=Test");
+
+ 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());
+ pkg2.close();
+ wb2.close();
+ }
+
+ @Test
public void office2007prettyPrintedRels() throws Exception {
OPCPackage pkg = OPCPackage.open(testdata.getFile("office2007prettyPrintedRels.docx"), PackageAccess.READ);
try {
@@ -611,8 +642,8 @@
pkg.close();
}
}
-
- private void sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception {
+
+ private SignatureConfig prepareConfig(String alias, String signerDn) throws Exception {
initKeyPair(alias, signerDn);
SignatureConfig signatureConfig = new SignatureConfig();
@@ -620,6 +651,12 @@
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);
signatureConfig.setOpcPackage(pkgCopy);
SignatureInfo si = new SignatureInfo();