Author: kiwiwings Date: Wed Jun 28 21:38:23 2017 New Revision: 1800207 URL: http://svn.apache.org/viewvc?rev=1800207&view=rev Log: Bug 61182 - Invalid signature created for streamed xslx file
Removed: poi/trunk/src/ooxml/java/org/apache/poi/util/XmlSort.java Modified: poi/site/src/documentation/content/xdocs/encryption.xml poi/site/src/documentation/content/xdocs/status.xml poi/trunk/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java poi/trunk/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java Modified: poi/site/src/documentation/content/xdocs/encryption.xml URL: http://svn.apache.org/viewvc/poi/site/src/documentation/content/xdocs/encryption.xml?rev=1800207&r1=1800206&r2=1800207&view=diff ============================================================================== --- poi/site/src/documentation/content/xdocs/encryption.xml (original) +++ poi/site/src/documentation/content/xdocs/encryption.xml Wed Jun 28 21:38:23 2017 @@ -31,9 +31,9 @@ <body> <section><title>Overview</title> - <p>Apache POI contains support for reading few variants of encrypted office files: </p> - <ul> - <li>Binary formats (.xls, .ppt, .doc, ...)<br/> + <p>Apache POI contains support for reading few variants of encrypted office files: </p> + <ul> + <li>Binary formats (.xls, .ppt, .doc, ...)<br/> encryption is format-dependent and needs to be implemented per format differently.<br/> Use <link href="https://poi.apache.org/apidocs/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.html"> Biff8EncryptionKey</link>.<link href="https://poi.apache.org/apidocs/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.html#setCurrentUserPassword(java.lang.String)">setCurrentUserPassword</link>(String password) @@ -41,7 +41,7 @@ Setting a null password before saving removes the password protection.<br/> The password is set in a thread local variable. Do not forget to reset it to null after text extraction. </li> - <li>XML-based formats (.xlsx, .pptx, .docx, ...)<br/> + <li>XML-based formats (.xlsx, .pptx, .docx, ...)<br/> use the same encryption logic over all formats. When encrypted, the zipped files will be stored within an OLE file in the EncryptedPackage stream.<br/> If you plan to use POI to actually generate encrypted documents, be aware not to use anything less than @@ -54,13 +54,13 @@ <link href="http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html">JDK7</link>, <link href="http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html">JDK8</link>). </li> - </ul> + </ul> - <p>Some "write-protected" files are encrypted with the built-in password "VelvetSweatshop", POI can read that files too.</p> - </section> + <p>Some "write-protected" files are encrypted with the built-in password "VelvetSweatshop", POI can read that files too.</p> + </section> <section><title>Supported feature matrix</title> - + <table> <tr> <th>Encryption</th> @@ -87,7 +87,7 @@ <td class="feature-yes">Yes (since 3.17)</td> </tr> <tr> - <th/> + <th/> <th>XSSF</th> <th>XSLF</th> <th>XWPF</th> @@ -117,22 +117,22 @@ <td class="feature-yes">Yes</td> </tr> </table> - + <p>*) the xor encryption is flawed and works only for very small files - see <link href="https://bz.apache.org/bugzilla/show_bug.cgi?id=59857">#59857</link>. </p> <p>**) the <link href="https://msdn.microsoft.com/en-us/library/cc313071(v=office.12).aspx">MS-OFFCRYPTO</link> 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. </p> </section> <section><title>Binary formats</title> - <p>As mentioned above, use + <p>As mentioned above, use <link href="https://poi.apache.org/apidocs/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.html"> Biff8EncryptionKey</link>.<link href="https://poi.apache.org/apidocs/org/apache/poi/hssf/record/crypto/Biff8EncryptionKey.html#setCurrentUserPassword(java.lang.String)">setCurrentUserPassword</link>(String password) to specify the password.</p> - + <source> // XOR/RC4 decryption for xls Biff8EncryptionKey.setCurrentUserPassword("pass"); @@ -140,7 +140,7 @@ NPOIFSFileSystem fs = new NPOIFSFileSyst HSSFWorkbook hwb = new HSSFWorkbook(fs.getRoot(), true); Biff8EncryptionKey.setCurrentUserPassword(null); </source> - + <source> // RC4 CryptoApi support ppt - decryption Biff8EncryptionKey.setCurrentUserPassword("pass"); @@ -166,10 +166,10 @@ os.close(); </section> <section><title>XML-based formats - Decryption</title> - <p>XML-based formats are stored in OLE-package stream "EncryptedPackage". Use org.apache.poi.poifs.crypt.Decryptor - to decode file:</p> + <p>XML-based formats are stored in OLE-package stream "EncryptedPackage". Use org.apache.poi.poifs.crypt.Decryptor + to decode file:</p> - <source> + <source> EncryptionInfo info = new EncryptionInfo(filesystem); Decryptor d = Decryptor.getInstance(info); @@ -185,11 +185,11 @@ try { } catch (GeneralSecurityException ex) { throw new RuntimeException("Unable to process encrypted document", ex); } - </source> + </source> - <p>If you want to read file encrypted with build-in password, use Decryptor.DEFAULT_PASSWORD.</p> + <p>If you want to read file encrypted with build-in password, use Decryptor.DEFAULT_PASSWORD.</p> </section> - + <section><title>XML-based formats - Encryption</title> <p>Encrypting a file is similar to the above decryption process. Basically you'll need to choose between <link href="https://poi.apache.org/apidocs/org/apache/poi/poifs/crypt/EncryptionMode.html">binaryRC4, standard and agile encryption</link>, @@ -213,10 +213,10 @@ opc.close(); // Write out the encrypted version FileOutputStream fos = new FileOutputStream("..."); fs.writeFilesystem(fos); -fos.close(); +fos.close(); </source> </section> - + <section><title>XML-based formats - Signing (XML Signature)</title> <p>An Office document can be digital signed by a <link href="https://en.wikipedia.org/wiki/XML_Signature">XML Signature</link> to protect it from unauthorized modifications, i.e. modifications without having the original certificate. @@ -231,17 +231,17 @@ fos.close(); <ul> <li>BouncyCastle bcpkix and bcprov (tested against 1.53)</li> <li>Apache Santuario "xmlsec" (tested against 2.0.6)</li> - <li>and slf4j-api (tested against 1.7.12)</li> + <li>and slf4j-api (tested against 1.7.12)</li> </ul> <p>Depending on the <link href="https://poi.apache.org/apidocs/org/apache/poi/poifs/crypt/dsig/SignatureConfig.html">configuration</link> and the activated <link href="https://poi.apache.org/apidocs/org/apache/poi/poifs/crypt/dsig/facets/package-summary.html">facets</link> various <link href="https://en.wikipedia.org/wiki/XAdES">XAdES levels</link> 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. </p> <p>Further test examples can be found in the corresponding <link href="https://svn.apache.org/viewvc/poi/trunk/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java?view=markup">test class</link>.</p> </section> - + <section><title>Validating a signed office document</title> <source> @@ -254,9 +254,10 @@ boolean isValid = si.verifySignature(); ... </source> </section> - + <section><title>Signing an office document</title> - + + <section><title>Signing a file</title> <source> // 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 @@ -292,6 +293,38 @@ pkg.close(); </source> </section> + <section><title>Signing a stream - in-memory</title> + <p>When saving a OOXML document, POI creates missing relations on the fly. Therefore calling the signing method before + would result in an invalid signature. Instead of trying to fix all save invocations, the user is asked to save the stream + before in a intermediate byte array (stream) and process this stream instead.</p> + + <source> +// load the key and setup SignatureConfig ... - see "Signing a file" + +SignatureInfo si = new SignatureInfo(); +si.setSignatureConfig(signatureConfig); + +// populate sample object +XSSFWorkbook wb = new XSSFWorkbook(); +wb.createSheet().createRow(1).createCell(1).setCellValue("Test"); +ByteArrayOutputStream bos = new ByteArrayOutputStream(100000); +wb.write(bos); +wb.close(); + +// process the +OPCPackage pkg = OPCPackage.open(new ByteArrayInputStream(bos.toByteArray())); + +signatureConfig.setOpcPackage(pkg); +si.confirmSignature(); +bos.reset(); +pkg.save(bos); +pkg.close(); + +// bos now contains the signed ooxml document + </source> + </section> + </section> + <section><title>Encrypting temporary files created when unzipping an OOXML document</title> <p>For security-conscious environments where data at rest must be stored encrypted, the creation of plaintext temporary files is a grey area.</p> @@ -304,13 +337,137 @@ pkg.close(); and other <link href="https://svn.apache.org/viewvc?view=revision&revision=1768744">files</link> that are needed for this example.</p> </section> + + <section><title>Debugging XML signature issues</title> + <p>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.</p> + + <!-- TODO: find original source --> + <p>One of the tripping hazards are <link href="https://stackoverflow.com/questions/36063375">different + linebreaks in Windows/Unix</link>, therefore use the non-indent form of the xmls.</p> + + <p>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.</p> + + <p>The package relationsships (*.rels) will be specially handled, i.e. they will be filtered and only + a subset will be processed - see <link 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</link>.</p> + + <p>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:</p> + + <source> +-Djava.io.tmpdir=<em><custom temp directory></em> +-Xbootclasspath/p:<em><preload dir, which contains /org/apache/xml/security/utils/UnsyncBufferedOutputStream.class></em> +-Dorg.apache.poi.util.POILogger=org.apache.poi.util.CommonsLogger +-Djava.util.logging.config.file=<em><a dir containing ...></em>/logging.properties + </source> + + <section><title>UnsyncBufferedOutputStream:</title> + <source> +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(); + } + +} +</source> +</section> + + <section><title>logging.properties</title> + <source> +handlers = org.slf4j.bridge.SLF4JBridgeHandler +.level=ALL +org.slf4j.bridge.SLF4JBridgeHandler.level=ALL + </source> + </section> + </section> </body> <footer> <legal> Copyright (c) @year@ The Apache Software Foundation. All rights reserved. <br /> - Apache POI, POI, Apache, the Apache feather logo, and the Apache + Apache POI, POI, Apache, the Apache feather logo, and the Apache POI project logo are trademarks of The Apache Software Foundation. </legal> </footer> Modified: poi/site/src/documentation/content/xdocs/status.xml URL: http://svn.apache.org/viewvc/poi/site/src/documentation/content/xdocs/status.xml?rev=1800207&r1=1800206&r2=1800207&view=diff ============================================================================== --- poi/site/src/documentation/content/xdocs/status.xml (original) +++ poi/site/src/documentation/content/xdocs/status.xml Wed Jun 28 21:38:23 2017 @@ -56,8 +56,11 @@ when referring to both H??F and X??F formats. --> - <!-- release version="3.17-beta2" date="2017-09-??"> - </release --> + <release version="3.17-beta2" date="2017-09-??"> + <actions> + <action dev="PD" type="fix" fixes-bug="61182" module="OPC">Invalid signature created for streamed xslx file</action> + </actions> + </release> <release version="3.17-beta1" date="2017-07-01"> <summary> Modified: poi/trunk/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java?rev=1800207&r1=1800206&r2=1800207&view=diff ============================================================================== --- poi/trunk/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java (original) +++ poi/trunk/src/ooxml/java/org/apache/poi/openxml4j/opc/StreamHelper.java Wed Jun 28 21:38:23 2017 @@ -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) { Modified: poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java?rev=1800207&r1=1800206&r2=1800207&view=diff ============================================================================== --- poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java (original) +++ poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java Wed Jun 28 21:38:23 2017 @@ -40,24 +40,32 @@ public class SignatureMarshalListener im 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 im } } + @Override public void setSignatureConfig(SignatureConfig signatureConfig) { this.signatureConfig = signatureConfig; } Modified: poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java?rev=1800207&r1=1800206&r2=1800207&view=diff ============================================================================== --- poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java (original) +++ poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java Wed Jun 28 21:38:23 2017 @@ -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.x200 /** * 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 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 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 } 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 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 } 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 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 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" + ))); } \ No newline at end of file Modified: poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java?rev=1800207&r1=1800206&r2=1800207&view=diff ============================================================================== --- poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java (original) +++ poi/trunk/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java Wed Jun 28 21:38:23 2017 @@ -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.TransformEx 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 RelationshipTransformServic 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 RelationshipTransformServic 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 RelationshipTransformServic 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 RelationshipTransformServic 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 { Modified: poi/trunk/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java?rev=1800207&r1=1800206&r2=1800207&view=diff ============================================================================== --- poi/trunk/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java (original) +++ poi/trunk/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java Wed Jun 28 21:38:23 2017 @@ -72,6 +72,7 @@ import org.apache.poi.poifs.crypt.dsig.s 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; @@ -119,6 +120,70 @@ public class TestSignatureInfo { } @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); try { @@ -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); --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@poi.apache.org For additional commands, e-mail: commits-h...@poi.apache.org