aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.classpath2
-rw-r--r--build.xml116
-rw-r--r--src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java2
-rw-r--r--src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java5
-rw-r--r--src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java28
-rw-r--r--src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java2
-rw-r--r--src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java2
-rw-r--r--src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java7
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/CertificateSecurityException.java38
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/ExpiredCertificateSecurityException.java38
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxies.java375
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxy.java249
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/KeyInfoKeySelector.java101
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/OOXMLURIDereferencer.java114
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/RevokedCertificateSecurityException.java38
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java283
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/TrustCertificateSecurityException.java38
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java194
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java541
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java101
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java83
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignaturePolicyService.java65
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java385
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java492
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java249
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationData.java131
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationDataService.java47
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignatureService.java101
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TSPTimeStampService.java392
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampService.java52
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampServiceValidator.java51
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java610
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java51
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/Constants.java30
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java56
-rw-r--r--src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java75
-rw-r--r--src/ooxml/java/org/apache/poi/util/MethodUtils.java1334
-rw-r--r--src/ooxml/java/org/apache/poi/util/SAXHelper.java71
-rw-r--r--src/ooxml/java/org/apache/poi/util/XmlSort.java221
-rw-r--r--src/ooxml/resources/org/apache/poi/poifs/crypt/XAdES.xsd466
-rw-r--r--src/ooxml/resources/org/apache/poi/poifs/crypt/XAdESv141.xsd15
-rw-r--r--src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd103
-rw-r--r--src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java328
-rw-r--r--src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java266
-rw-r--r--src/testcases/org/apache/poi/POIDataSamples.java7
-rw-r--r--test-data/xmldsign/Office2010-SP1-XAdES-X-L.docxbin0 -> 23649 bytes
-rw-r--r--test-data/xmldsign/bcprov-ext-jdk15on-1.49.jarbin0 -> 2552449 bytes
-rw-r--r--test-data/xmldsign/hello-world-office-2010-technical-preview-unsigned.docxbin0 -> 14447 bytes
-rw-r--r--test-data/xmldsign/hello-world-office-2010-technical-preview.docxbin0 -> 17044 bytes
-rw-r--r--test-data/xmldsign/hello-world-signed-twice.docxbin0 -> 16099 bytes
-rw-r--r--test-data/xmldsign/hello-world-signed.docxbin0 -> 13244 bytes
-rw-r--r--test-data/xmldsign/hello-world-signed.pptxbin0 -> 36107 bytes
-rw-r--r--test-data/xmldsign/hello-world-signed.xlsxbin0 -> 13072 bytes
-rw-r--r--test-data/xmldsign/hello-world-unsigned.docxbin0 -> 9898 bytes
-rw-r--r--test-data/xmldsign/hello-world-unsigned.pptxbin0 -> 32220 bytes
-rw-r--r--test-data/xmldsign/hello-world-unsigned.xlsxbin0 -> 9748 bytes
-rw-r--r--test-data/xmldsign/hyperlink-example-signed.docxbin0 -> 24070 bytes
-rw-r--r--test-data/xmldsign/ms-office-2010-signed.docxbin0 -> 16142 bytes
-rw-r--r--test-data/xmldsign/ms-office-2010-signed.pptxbin0 -> 38874 bytes
-rw-r--r--test-data/xmldsign/ms-office-2010-signed.xlsxbin0 -> 12180 bytes
-rw-r--r--test-data/xmldsign/signed.docxbin0 -> 23428 bytes
61 files changed, 7882 insertions, 73 deletions
diff --git a/.classpath b/.classpath
index 286f8badad..8df184abf0 100644
--- a/.classpath
+++ b/.classpath
@@ -24,7 +24,7 @@
<classpathentry kind="lib" path="lib/hamcrest-core-1.3.jar"/>
<classpathentry kind="lib" path="lib/junit-4.11.jar"/>
<classpathentry kind="lib" path="ooxml-lib/ooxml-schemas-1.1.jar" sourcepath="ooxml-lib/ooxml-schemas-src-1.1.jar"/>
- <classpathentry kind="lib" path="ooxml-lib/ooxml-encryption-1.1.jar" sourcepath="ooxml-lib/ooxml-encryption-src-1.1.jar"/>
+ <classpathentry kind="lib" path="ooxml-lib/ooxml-encryption-1.2.jar" sourcepath="ooxml-lib/ooxml-encryption-src-1.2.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="build/eclipse"/>
</classpath>
diff --git a/build.xml b/build.xml
index 6704bd03fd..c16fe44e9e 100644
--- a/build.xml
+++ b/build.xml
@@ -118,7 +118,6 @@ under the License.
<property name="ooxml.output.test.dir" location="build/ooxml-test-classes"/>
<property name="ooxml.testokfile" location="build/ooxml-testokfile.txt"/>
<property name="ooxml.lite.output.dir" location="build/ooxml-lite-classes"/>
- <property name="ooxml.encryption.xsd.dir" location="src/ooxml/resources/org/apache/poi/poifs/crypt"/>
<!-- Excelant: -->
<property name="excelant.resource.dir" value="src/excelant/resources"/>
@@ -169,17 +168,28 @@ under the License.
<!-- See http://www.ecma-international.org/publications/standards/Ecma-376.htm -->
<!-- "Copy these file(s), free of charge" -->
- <property name="ooxml.xsds.ozip" location="${ooxml.lib}/OfficeOpenXML-Part4.zip"/>
- <property name="ooxml.xsds.izip" location="${ooxml.lib}/OfficeOpenXML-XMLSchema.zip"/>
- <property name="ooxml.xsds.url"
+ <property name="ooxml.xsds.ozip.1" value="OfficeOpenXML-Part4.zip"/>
+ <property name="ooxml.xsds.izip.1" value="OfficeOpenXML-XMLSchema.zip"/>
+ <property name="ooxml.xsds.url.1"
value="http://www.ecma-international.org/publications/files/ECMA-ST/Office%20Open%20XML%201st%20edition%20Part%204%20(PDF).zip"/>
<property name="ooxml.xsds.src.dir" location="build/ooxml-xsds-src"/>
<property name="ooxml.xsds.src.jar" location="${ooxml.lib}/ooxml-schemas-src-1.1.jar"/>
<property name="ooxml.xsds.jar" location="${ooxml.lib}/ooxml-schemas-1.1.jar"/>
+ <!-- additional schemas are packed into the poi schemas jar, -->
+ <!-- so we don't have to care about a seperate versioning of the original ooxml schemas -->
+ <property name="ooxml.xsds.dc.1" value="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dc.xsd"/>
+ <property name="ooxml.xsds.dc.2" value="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcterms.xsd"/>
+ <property name="ooxml.xsds.dc.3" value="http://dublincore.org/schemas/xmls/qdc/2003/04/02/dcmitype.xsd"/>
+ <property name="ooxml.xsds.dsig" value="http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd"/>
+ <property name="ooxml.xsds.ozip.2" value="OfficeOpenXML-Part2.zip"/>
+ <property name="ooxml.xsds.izip.2" value="OpenPackagingConventions-XMLSchema.zip"/>
+ <property name="ooxml.xsds.url.2"
+ value="http://www.ecma-international.org/publications/files/ECMA-ST/Office%20Open%20XML%201st%20edition%20Part%202%20(PDF).zip"/>
<property name="ooxml.encryption.src.dir" location="build/ooxml-encryption-src"/>
- <property name="ooxml.encryption.src.jar" location="${ooxml.lib}/ooxml-encryption-src-1.1.jar"/>
- <property name="ooxml.encryption.jar" location="${ooxml.lib}/ooxml-encryption-1.1.jar"/>
+ <property name="ooxml.encryption.src.jar" location="${ooxml.lib}/ooxml-encryption-src-1.2.jar"/>
+ <property name="ooxml.encryption.jar" location="${ooxml.lib}/ooxml-encryption-1.2.jar"/>
+ <property name="ooxml.encryption.xsd.dir" location="src/ooxml/resources/org/apache/poi/poifs/crypt"/>
<property name="maven.ooxml.xsds.version.id" value="1.0"/>
<property name="maven.ooxml.xsds.jar" value="ooxml-schemas-${maven.ooxml.xsds.version.id}.jar"/>
@@ -356,7 +366,7 @@ under the License.
</fileset>
</delete>
- <condition property="jars.present">
+ <condition property="jars.present">
<or>
<and>
<available file="${main.commons-logging.jar}"/>
@@ -458,19 +468,35 @@ under the License.
<condition property="ooxml-xsds.present">
<or>
<and>
- <available file="${ooxml.xsds.izip}"/>
+ <available file="${ooxml.lib}/${ooxml.xsds.izip.1}"/>
</and>
<isset property="disconnected"/>
</or>
</condition>
</target>
<target name="fetch-ooxml-xsds" unless="ooxml-xsds.present"
- depends="check-ooxml-xsds"
+ depends="check-ooxml-xsds"
description="Fetches needed OOXML xsd files from the Internet">
- <get src="${ooxml.xsds.url}" dest="${ooxml.xsds.ozip}"/>
- <unzip src="${ooxml.xsds.ozip}" dest="${ooxml.lib}">
+ <get dest="${ooxml.lib}" skipexisting="true">
+ <url url="${ooxml.xsds.url.1}"/>
+ <url url="${ooxml.xsds.url.2}"/>
+ <url url="${ooxml.xsds.dc.1}"/>
+ <url url="${ooxml.xsds.dc.2}"/>
+ <url url="${ooxml.xsds.dc.3}"/>
+ <url url="${ooxml.xsds.dsig}"/>
+ <chainedmapper>
+ <flattenmapper/>
+ <firstmatchmapper>
+ <globmapper from="Office%20Open%20XML%201st%20edition%20Part%20*%20(PDF).zip" to="OfficeOpenXML-Part*.zip"/>
+ <identitymapper/>
+ </firstmatchmapper>
+ </chainedmapper>
+ </get>
+ <unzip src="${ooxml.lib}/${ooxml.xsds.ozip.1}" dest="${ooxml.lib}">
+ <fileset dir="${ooxml.lib}" includes="OfficeOpenXML-Part*.zip"/>
<patternset>
- <include name="OfficeOpenXML-XMLSchema.zip"/>
+ <include name="${ooxml.xsds.izip.1}"/>
+ <include name="${ooxml.xsds.izip.2}"/>
</patternset>
</unzip>
</target>
@@ -481,19 +507,10 @@ under the License.
<isset property="disconnected"/>
</or>
</condition>
- <condition property="ooxml-compiled-encryption-xsds.present">
- <or>
- <available file="${ooxml.encryption.jar}"/>
- <isset property="disconnected"/>
- </or>
- </condition>
</target>
<target name="compile-ooxml-xsds" unless="ooxml-compiled-xsds.present"
- depends="check-jars,fetch-jars,check-compiled-ooxml-xsds"
- description="Unpacks the OOXML xsd files, and compiles them into XmlBeans">
- <property name="ooxml.xsds.tmp.dir" location="build/ooxml-xsds"/>
- <mkdir dir="${ooxml.xsds.tmp.dir}"/>
-
+ depends="check-jars,fetch-jars,check-compiled-ooxml-xsds"
+ description="Unpacks the OOXML xsd files, and compiles them into XmlBeans">
<taskdef name="xmlbean"
classname="org.apache.xmlbeans.impl.tool.XMLBean"
classpath="${ooxml.xmlbeans23.jar}"/>
@@ -505,11 +522,9 @@ under the License.
<equals arg1="${sun.arch.data.model}" arg2="64" />
</condition>
- <unzip src="${ooxml.xsds.izip}" dest="${ooxml.xsds.tmp.dir}"/>
- <!--
- schema="build/ooxml-xsds/"
- schema="build/ooxml-xsds/sml-workbook.xsd"
- -->
+ <property name="ooxml.xsds.tmp.dir" location="build/ooxml-xsds"/>
+ <mkdir dir="${ooxml.xsds.tmp.dir}"/>
+ <unzip src="${ooxml.lib}/${ooxml.xsds.izip.1}" dest="${ooxml.xsds.tmp.dir}"/>
<xmlbean
schema="${ooxml.xsds.tmp.dir}"
srcgendir="${ooxml.xsds.src.dir}"
@@ -523,41 +538,40 @@ under the License.
<classpath refid="ooxml.classpath"/>
</xmlbean>
- <!-- Now make a jar of the schema sources -->
- <jar
+ <!-- Now make a jar of the schema sources -->
+ <jar
basedir="${ooxml.xsds.src.dir}"
destfile="${ooxml.xsds.src.jar}"
/>
- </target>
-
- <target name="compile-ooxml-encryption-xsds" unless="ooxml-compiled-encryption-xsds.present"
- depends="check-jars,fetch-jars,check-compiled-ooxml-xsds"
- description="Compiles the OOXML encryption xsd files into XmlBeans">
- <taskdef name="xmlbean"
- classname="org.apache.xmlbeans.impl.tool.XMLBean"
- classpath="${ooxml.xmlbeans23.jar}"/>
-
- <!-- We need a fair amount of memory to compile the xml schema, -->
- <!-- but limit it in case it goes wrong! -->
- <!-- Pick the right amount based on 32 vs 64 bit jvm -->
- <condition property="ooxml.memory" value="768m" else="512m">
- <equals arg1="${sun.arch.data.model}" arg2="64" />
- </condition>
+ <!-- Now do the same for the encryption and supporting schemas -->
+ <property name="ooxml.enc.xsds.tmp.dir" location="build/ooxml-encryption-xsds"/>
+ <mkdir dir="${ooxml.enc.xsds.tmp.dir}"/>
+ <unzip src="${ooxml.lib}/${ooxml.xsds.izip.2}" dest="${ooxml.enc.xsds.tmp.dir}"/>
+
+ <copy todir="${ooxml.enc.xsds.tmp.dir}">
+ <fileset dir="${ooxml.lib}" includes="dc*.xsd,xmldsig*.xsd"/>
+ <fileset dir="${ooxml.encryption.xsd.dir}"/>
+ </copy>
+
+ <!-- noupa/nopvr is set because of the dublincore schemas -->
+ <!-- https://issues.apache.org/jira/browse/XMLBEANS-340 -->
+ <!-- javasource > 1.5 will not generate all array accessor -->
<xmlbean
- schema="${ooxml.encryption.xsd.dir}"
+ schema="${ooxml.enc.xsds.tmp.dir}"
srcgendir="${ooxml.encryption.src.dir}"
optimize="yes"
destfile="${ooxml.encryption.jar}"
- javasource="1.5"
+ javasource="1.5"
failonerror="true"
fork="true"
memoryMaximumSize="${ooxml.memory}"
+ noupa="true"
+ nopvr="true"
>
<classpath refid="ooxml.classpath"/>
</xmlbean>
- <!-- Now make a jar of the schema sources -->
<jar
basedir="${ooxml.encryption.src.dir}"
destfile="${ooxml.encryption.src.jar}"
@@ -650,7 +664,7 @@ under the License.
</copy>
</target>
- <target name="compile-ooxml" depends="compile-main,compile-scratchpad,compile-ooxml-xsds,compile-ooxml-encryption-xsds">
+ <target name="compile-ooxml" depends="compile-main,compile-scratchpad,compile-ooxml-xsds">
<javac target="${jdk.version.class}"
source="${jdk.version.source}"
destdir="${ooxml.output.dir}"
@@ -1351,7 +1365,7 @@ under the License.
<target name="gump" depends="compile-all, test-all, jar"/>
<target name="jenkins" depends="compile-all, test-all, jar, javadocs, assemble, findbugs, release-notes, rat-check"/>
-
+
<available property="maven.ant.tasks.present" classname="org.apache.maven.artifact.ant.Pom"/>
<target name="maven.ant.tasks-check">
<fail unless="maven.ant.tasks.present">
@@ -1447,7 +1461,7 @@ under the License.
<exclude name="poi-*${version.id}-sources-*.jar"/>
</fileset>
<auxClasspath path="ooxml-lib/ooxml-schemas-1.1.jar" />
- <auxClasspath path="ooxml-lib/ooxml-encryption-1.1.jar" />
+ <auxClasspath path="ooxml-lib/ooxml-encryption-1.2.jar" />
<auxClasspath path="ooxml-lib/xmlbeans-2.6.0.jar" />
<auxClasspath path="ooxml-lib/dom4j-1.6.1.jar" />
<auxClasspath path="lib/commons-codec-1.9.jar" />
diff --git a/src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java b/src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java
index be507a6660..68682f496c 100644
--- a/src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java
+++ b/src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java
@@ -34,6 +34,8 @@ public enum CipherAlgorithm {
// need bouncycastle provider for this one ...
// see http://stackoverflow.com/questions/4436397/3des-des-encryption-using-the-jce-generating-an-acceptable-key
des3_112(null, "DESede", -1, 128, new int[]{128}, 8, 32, "3DES_112", true),
+ // only for digital signatures
+ rsa(null, "RSA", -1, 1024, new int[]{1024, 2048, 3072, 4096}, -1, -1, "", false);
;
public final CipherProvider provider;
diff --git a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
index f9f970ade9..85d3419cbc 100644
--- a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
+++ b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
@@ -19,6 +19,7 @@ package org.apache.poi.poifs.crypt;
import java.nio.charset.Charset;
import java.security.DigestException;
import java.security.GeneralSecurityException;
+import java.security.Key;
import java.security.MessageDigest;
import java.security.Provider;
import java.security.Security;
@@ -189,7 +190,7 @@ public class CryptoFunctions {
* @return the requested cipher
* @throws GeneralSecurityException
*/
- public static Cipher getCipher(SecretKey key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode, String padding) {
+ public static Cipher getCipher(Key key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode, String padding) {
int keySizeInBytes = key.getEncoded().length;
if (padding == null) padding = "NoPadding";
@@ -274,7 +275,7 @@ public class CryptoFunctions {
}
@SuppressWarnings("unchecked")
- private static void registerBouncyCastle() {
+ public static void registerBouncyCastle() {
if (Security.getProvider("BC") != null) return;
try {
Class<Provider> clazz = (Class<Provider>)Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
diff --git a/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java b/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java
index 51217184ba..e69f8f0736 100644
--- a/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java
+++ b/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java
@@ -17,22 +17,24 @@
package org.apache.poi.poifs.crypt;
+import javax.xml.crypto.dsig.DigestMethod;
+
import org.apache.poi.EncryptedDocumentException;
public enum HashAlgorithm {
- none ( "", 0x0000, "", 0, "", false),
- sha1 ( "SHA-1", 0x8004, "SHA1", 20, "HmacSHA1", false),
- sha256 ( "SHA-256", 0x800C, "SHA256", 32, "HmacSHA256", false),
- sha384 ( "SHA-384", 0x800D, "SHA384", 48, "HmacSHA384", false),
- sha512 ( "SHA-512", 0x800E, "SHA512", 64, "HmacSHA512", false),
+ none ( "", 0x0000, "", 0, "", null, false),
+ sha1 ( "SHA-1", 0x8004, "SHA1", 20, "HmacSHA1", DigestMethod.SHA1, false),
+ sha256 ( "SHA-256", 0x800C, "SHA256", 32, "HmacSHA256", DigestMethod.SHA256, false),
+ sha384 ( "SHA-384", 0x800D, "SHA384", 48, "HmacSHA384", null, false),
+ sha512 ( "SHA-512", 0x800E, "SHA512", 64, "HmacSHA512", DigestMethod.SHA512, false),
/* only for agile encryption */
- md5 ( "MD5", -1, "MD5", 16, "HmacMD5", false),
+ md5 ( "MD5", -1, "MD5", 16, "HmacMD5", null, false),
// although sunjc2 supports md2, hmac-md2 is only supported by bouncycastle
- md2 ( "MD2", -1, "MD2", 16, "Hmac-MD2", true),
- md4 ( "MD4", -1, "MD4", 16, "Hmac-MD4", true),
- ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", true),
- ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", true),
- whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", true),
+ md2 ( "MD2", -1, "MD2", 16, "Hmac-MD2", null, true),
+ md4 ( "MD4", -1, "MD4", 16, "Hmac-MD4", null, true),
+ ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", null, true),
+ ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", DigestMethod.RIPEMD160, true),
+ whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", null, true),
;
public final String jceId;
@@ -40,14 +42,16 @@ public enum HashAlgorithm {
public final String ecmaString;
public final int hashSize;
public final String jceHmacId;
+ public final String xmlSignUri;
public final boolean needsBouncyCastle;
- HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, boolean needsBouncyCastle) {
+ HashAlgorithm(String jceId, int ecmaId, String ecmaString, int hashSize, String jceHmacId, String xmlSignUri, boolean needsBouncyCastle) {
this.jceId = jceId;
this.ecmaId = ecmaId;
this.ecmaString = ecmaString;
this.hashSize = hashSize;
this.jceHmacId = jceHmacId;
+ this.xmlSignUri = xmlSignUri;
this.needsBouncyCastle = needsBouncyCastle;
}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java
index 2a5ade28d6..7d4741b219 100644
--- a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java
@@ -182,8 +182,6 @@ public final class PackageRelationship {
}
/**
- * public URI getSourceUri(){ }
- *
* @return the targetMode
*/
public TargetMode getTargetMode() {
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java
index 6adc737926..bef53f4a6e 100644
--- a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java
@@ -306,7 +306,7 @@ public final class PackageRelationshipCollection implements
* @throws InvalidFormatException
* Throws if the relationship part is invalid.
*/
- private void parseRelationshipsPart(PackagePart relPart)
+ public void parseRelationshipsPart(PackagePart relPart)
throws InvalidFormatException {
try {
logger.log(POILogger.DEBUG, "Parsing relationship: " + relPart.getPartName());
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java
index 2c3b97a7f4..381d26b9c8 100644
--- a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java
+++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java
@@ -148,11 +148,10 @@ public abstract class ContentTypeManager {
* </p>
*/
public void addContentType(PackagePartName partName, String contentType) {
- boolean defaultCTExists = false;
+ boolean defaultCTExists = this.defaultContentType.containsValue(contentType);
String extension = partName.getExtension().toLowerCase();
if ((extension.length() == 0)
- || (this.defaultContentType.containsKey(extension) && !(defaultCTExists = this.defaultContentType
- .containsValue(contentType))))
+ || (this.defaultContentType.containsKey(extension) && !defaultCTExists))
this.addOverrideContentType(partName, contentType);
else if (!defaultCTExists)
this.addDefaultContentType(extension, contentType);
@@ -461,7 +460,7 @@ public abstract class ContentTypeManager {
}
/**
- * Use to append default types XML elements, use by the save() metid.
+ * Use to append default types XML elements, use by the save() method.
*
* @param root
* XML parent element use to append this default type element.
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/CertificateSecurityException.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/CertificateSecurityException.java
new file mode 100644
index 0000000000..707ad758a8
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/CertificateSecurityException.java
@@ -0,0 +1,38 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+/**
+ * Exception thrown in case there is something wrong with the incoming eID
+ * certificate.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class CertificateSecurityException extends SecurityException {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/ExpiredCertificateSecurityException.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/ExpiredCertificateSecurityException.java
new file mode 100644
index 0000000000..adbcfdb35e
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/ExpiredCertificateSecurityException.java
@@ -0,0 +1,38 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+/**
+ * Exception thrown in case the incoming eID certificate is expired.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class ExpiredCertificateSecurityException extends
+ CertificateSecurityException {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxies.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxies.java
new file mode 100644
index 0000000000..54a5aad325
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxies.java
@@ -0,0 +1,375 @@
+package org.apache.poi.poifs.crypt.dsig;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.Date;
+
+import javax.security.auth.x500.X500Principal;
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.dom.DOMCryptoContext;
+import javax.xml.crypto.dsig.XMLSignContext;
+import javax.xml.crypto.dsig.XMLSignatureException;
+
+import org.apache.poi.poifs.crypt.dsig.HorribleProxy.ProxyIf;
+import org.w3c.dom.Node;
+
+public interface HorribleProxies {
+ public static final String xmlSecBase = "org.jcp.xml.dsig.internal.dom";
+ // public static final String xmlSecBase = "org.apache.jcp.xml.dsig.internal.dom";
+
+ public interface ASN1InputStreamIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.ASN1InputStream";
+
+ ASN1OctetStringIf readObject$ASNString() throws IOException;
+ DEROctetStringIf readObject$DERString() throws IOException;
+ DERIntegerIf readObject$Integer() throws IOException;
+ ASN1SequenceIf readObject$Sequence() throws IOException;
+ Object readObject$Object() throws IOException;
+ }
+
+ public interface ASN1ObjectIdentifierIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.ASN1ObjectIdentifier";
+ }
+
+ public interface ASN1OctetStringIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.ASN1OctetString";
+ byte[] getOctets();
+ }
+
+ public interface ASN1SequenceIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.ASN1Sequence";
+ }
+
+ public interface AuthorityInformationAccessIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.AuthorityInformationAccess";
+ }
+
+ public interface AuthorityKeyIdentifierIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.AuthorityKeyIdentifier";
+ byte[] getKeyIdentifier();
+ }
+
+ public interface BasicConstraintsIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.BasicConstraints";
+ }
+
+ public interface BasicOCSPRespIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cert.ocsp.BasicOCSPResp";
+ Date getProducedAt();
+ RespIDIf getResponderId();
+ }
+
+ public interface BcDigestCalculatorProviderIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.operator.bc.BcDigestCalculatorProvider";
+ }
+
+ public interface BcRSASignerInfoVerifierBuilderIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder";
+ SignerInformationVerifierIf build(X509CertificateHolderIf holder);
+ }
+
+ public interface CanonicalizerIf extends ProxyIf {
+ String delegateClass = "com.sun.org.apache.xml.internal.security.c14n.Canonicalizer";
+ byte[] canonicalizeSubtree(Node node) throws Exception;
+ }
+
+ public interface CRLNumberIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.CRLNumber";
+ }
+
+ public interface DefaultDigestAlgorithmIdentifierFinderIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder";
+ }
+
+ public interface DistributionPointNameIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.DistributionPointName";
+ }
+
+ public interface DistributionPointIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.DistributionPoint";
+ }
+
+ public interface DERIA5StringIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.DERIA5String";
+ }
+
+ public interface DERIntegerIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.DERInteger";
+ BigInteger getPositiveValue();
+ }
+
+ public interface DEROctetStringIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.DEROctetString";
+ byte[] getOctets();
+ }
+
+ public interface DERTaggedObjectIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.DERTaggedObject";
+ int getTagNo();
+ ASN1OctetStringIf getObject$String();
+ Object getObject$Object();
+ }
+
+ public interface DERSequenceIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.DERSequence";
+ }
+
+ public interface DOMKeyInfoIf extends ProxyIf {
+ String delegateClass = xmlSecBase+".DOMKeyInfo";
+ void marshal(Node parent, Node nextSibling, String dsPrefix, DOMCryptoContext context) throws MarshalException;
+ }
+
+ public interface DOMReferenceIf extends ProxyIf {
+ String delegateClass = xmlSecBase+".DOMReference";
+ void digest(XMLSignContext paramXMLSignContext) throws XMLSignatureException;
+ byte[] getDigestValue();
+ }
+
+ public interface DOMSignedInfoIf extends ProxyIf {
+ String delegateClass = xmlSecBase+".DOMSignedInfo";
+ void canonicalize(XMLCryptoContext paramXMLCryptoContext, ByteArrayOutputStream paramByteArrayOutputStream);
+ }
+
+ public interface XMLSignatureIf extends ProxyIf {
+ String delegateClass = "com.sun.org.apache.xml.internal.security.signature.XMLSignature";
+ String ALGO_ID_SIGNATURE_RSA_SHA1();
+ String ALGO_ID_SIGNATURE_RSA_SHA256();
+ String ALGO_ID_SIGNATURE_RSA_SHA384();
+ String ALGO_ID_SIGNATURE_RSA_SHA512();
+ String ALGO_ID_MAC_HMAC_RIPEMD160();
+ }
+
+ public interface DOMXMLSignatureIf extends ProxyIf {
+ String delegateClass = xmlSecBase+".DOMXMLSignature";
+ void marshal(Node node, String prefix, DOMCryptoContext context) throws MarshalException;
+ }
+
+ public interface GeneralNameIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.GeneralName";
+
+ int uniformResourceIdentifier();
+
+ }
+
+ public interface GeneralNamesIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.GeneralNames";
+ }
+
+ public interface InitIf extends ProxyIf {
+ String delegateClass = "com.sun.org.apache.xml.internal.security.Init";
+ void init();
+ }
+
+ public interface KeyUsageIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.KeyUsage";
+ int digitalSignature();
+ }
+
+ public interface OCSPRespIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cert.ocsp.OCSPResp";
+ BasicOCSPRespIf getResponseObject();
+ }
+
+ public interface PKIFailureInfoIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.cmp.PKIFailureInfo";
+ int intValue();
+ }
+
+ public interface RespIDIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cert.ocsp.RespID";
+ ResponderIDIf toASN1Object();
+ }
+
+ public interface ResponderIDIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.ocsp.ResponderID";
+ DERTaggedObjectIf toASN1Object();
+ }
+
+ public interface SignerIdIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cms.SignerId";
+ BigInteger getSerialNumber();
+ X500Principal getIssuer();
+ }
+
+ public interface SignerInformationVerifierIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cms.SignerInformationVerifier";
+ }
+
+ public interface StoreIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.util.Store";
+ Collection<Certificate> getMatches(Object selector) throws Exception;
+ }
+
+ public interface SubjectKeyIdentifierIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.SubjectKeyIdentifier";
+ byte[] getKeyIdentifier();
+ }
+
+ public interface SubjectPublicKeyInfoIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.SubjectPublicKeyInfo";
+ }
+
+ public interface TimeStampRequestGeneratorIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.tsp.TimeStampRequestGenerator";
+ void setCertReq(boolean certReq);
+ void setReqPolicy(String reqPolicy);
+ TimeStampRequestIf generate(String igestAlgorithmOID, byte[] digest, BigInteger nonce);
+ }
+
+ public interface TimeStampRequestIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.tsp.TimeStampRequest";
+ byte[] getEncoded() throws IOException;
+ }
+
+ public interface TimeStampResponseIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.tsp.TimeStampResponse";
+ void validate(TimeStampRequestIf request) throws Exception;
+ int getStatus();
+ String getStatusString();
+ PKIFailureInfoIf getFailInfo();
+ TimeStampTokenIf getTimeStampToken();
+ }
+
+ public interface TimeStampTokenIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.tsp.TimeStampToken";
+ SignerIdIf getSID();
+ StoreIf getCertificates();
+ StoreIf getCRLs();
+ TimeStampTokenInfoIf getTimeStampInfo();
+ byte[] getEncoded() throws IOException;
+ void validate(SignerInformationVerifierIf verifier) throws Exception;
+ }
+
+ public interface TimeStampTokenInfoIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.tsp.TimeStampTokenInfo";
+ Date getGenTime();
+ }
+
+ public interface X509CertificateHolderIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cert.X509CertificateHolder";
+ }
+
+ public interface X509NameIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.X509Name";
+ String toString$delegate();
+ }
+
+ public interface X509PrincipalIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.jce.X509Principal";
+ String getName();
+ }
+
+ public interface X509V3CertificateGeneratorIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.x509.X509V3CertificateGenerator";
+
+ void reset();
+ void setPublicKey(PublicKey key);
+ void setSignatureAlgorithm(String signatureAlgorithm);
+ void setNotBefore(Date date);
+ void setNotAfter(Date date);
+ void setIssuerDN(X509PrincipalIf issuerDN);
+ void setSubjectDN(X509PrincipalIf issuerDN);
+ void setSerialNumber(BigInteger serialNumber);
+
+ void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, SubjectKeyIdentifierIf value);
+ void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, AuthorityKeyIdentifierIf value);
+ void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, BasicConstraintsIf value);
+ void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, DERSequenceIf value);
+ void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, AuthorityInformationAccessIf value);
+ void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, KeyUsageIf value);
+
+ X509Certificate generate(PrivateKey issuerPrivateKey);
+ }
+
+ public interface OCSPReqIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cert.ocsp.OCSPReq";
+
+ ReqIf[] getRequestList();
+ }
+
+ public interface OCSPReqGeneratorIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.ocsp.OCSPReqGenerator";
+
+ void addRequest(CertificateIDIf certId);
+ OCSPReqIf generate();
+ }
+
+ public interface BasicOCSPRespGeneratorIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.ocsp.BasicOCSPRespGenerator";
+
+ void addResponse(CertificateIDIf certificateID, CertificateStatusIf certificateStatus);
+ BasicOCSPRespIf generate(String signatureAlgorithm, PrivateKey ocspResponderPrivateKey,
+ X509Certificate chain[], Date date, String provider);
+ }
+
+ public interface CertificateIDIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cert.ocsp.CertificateID";
+
+ String HASH_SHA1();
+ }
+
+ public interface X509ExtensionsIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.X509Extensions";
+
+ ASN1ObjectIdentifierIf AuthorityKeyIdentifier();
+ ASN1ObjectIdentifierIf SubjectKeyIdentifier();
+ ASN1ObjectIdentifierIf BasicConstraints();
+ ASN1ObjectIdentifierIf CRLDistributionPoints();
+ ASN1ObjectIdentifierIf AuthorityInfoAccess();
+ ASN1ObjectIdentifierIf KeyUsage();
+ ASN1ObjectIdentifierIf CRLNumber();
+ }
+
+ public interface X509ObjectIdentifiersIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.X509ObjectIdentifiers";
+
+ ASN1ObjectIdentifierIf ocspAccessMethod();
+ }
+
+ public interface X509V2CRLGeneratorIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.X509V2CRLGenerator";
+
+ void setIssuerDN(X500Principal issuerDN);
+ void setThisUpdate(Date date);
+ void setNextUpdate(Date date);
+ void setSignatureAlgorithm(String algorithm);
+
+ void addExtension(ASN1ObjectIdentifierIf oid, boolean critical, CRLNumberIf value);
+ X509CRL generate(PrivateKey privateKey);
+ }
+
+ public interface ReqIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cert.ocsp.Req";
+
+ CertificateIDIf getCertID();
+ }
+
+ public interface CertificateStatusIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cert.ocsp.CertificateStatus";
+
+ CertificateStatusIf GOOD();
+ }
+
+ public interface RevokedStatusIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.cert.ocsp.RevokedStatus";
+ }
+
+ public interface CRLReasonIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.asn1.x509.CRLReason";
+ int unspecified();
+ }
+
+ public interface OCSPRespGeneratorIf extends ProxyIf {
+ String delegateClass = "org.bouncycastle.ocsp.OCSPRespGenerator";
+ int SUCCESSFUL();
+ OCSPRespIf generate(int status, BasicOCSPRespIf basicOCSPResp);
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxy.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxy.java
new file mode 100644
index 0000000000..2ac5128ee6
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxy.java
@@ -0,0 +1,249 @@
+package org.apache.poi.poifs.crypt.dsig;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+
+import org.apache.poi.util.MethodUtils;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+public class HorribleProxy implements InvocationHandler {
+
+ private static final POILogger LOG = POILogFactory.getLogger(HorribleProxy.class);
+
+ protected static interface ProxyIf {
+ Object getDelegate();
+ void setInitDeferred(boolean initDeferred);
+ };
+
+ private final Class<?> delegateClass;
+ private Object delegateRef;
+ private boolean initDeferred = true;
+
+ protected HorribleProxy(Class<?> delegateClass, Object delegateRef) {
+ this.delegateClass = delegateClass;
+ // delegateRef can be null, then we have to deal with deferred initialisation
+ this.delegateRef = delegateRef;
+ }
+
+ /**
+ * Create new instance by constructor
+ *
+ * @param proxyClass
+ * @param initargs
+ * @return
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ * @throws NoSuchMethodException
+ * @throws ClassNotFoundException
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends ProxyIf> T newProxy(Class<T> proxyClass, Object ... initargs)
+ throws InvocationTargetException, IllegalAccessException, InstantiationException
+ , NoSuchMethodException, ClassNotFoundException, NoSuchFieldException {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+
+ Class<?> delegateClass = getDelegateClass(proxyClass);
+ Object delegateRef;
+ if (initargs.length == 0) {
+ delegateRef = null;
+ } else if (initargs.length == 1 && delegateClass.isAssignableFrom(initargs[0].getClass())) {
+ delegateRef = initargs[0];
+ } else {
+ Class<?> paramTypes[] = updateMethodArgs(null, initargs);
+ Constructor<?> cons = null;
+ try {
+ cons = delegateClass.getConstructor(paramTypes);
+ } catch (Exception e) {
+ // fallback - find constructor with same amount of parameters
+ // horrible et al. ...
+ cons = MethodUtils.getMatchingAccessibleConstructor(delegateClass, paramTypes);
+
+ if (cons == null) {
+ throw new RuntimeException("There's no constructor for the given arguments.");
+ }
+ }
+
+ delegateRef = cons.newInstance(initargs);
+ }
+
+ HorribleProxy hp = new HorribleProxy(delegateClass, delegateRef);
+ return (T)Proxy.newProxyInstance(cl, new Class<?>[]{proxyClass}, hp);
+ }
+
+ /**
+ * Create new instance by factory method
+ *
+ * @param proxyClass
+ * @param factoryMethod
+ * @param initargs
+ * @return
+ * @throws InvocationTargetException
+ * @throws IllegalAccessException
+ * @throws InstantiationException
+ * @throws NoSuchMethodException
+ * @throws ClassNotFoundException
+ */
+ @SuppressWarnings("unchecked")
+ public static <T extends ProxyIf> T createProxy(Class<T> proxyClass, String factoryMethod, Object ... initargs)
+ throws InvocationTargetException, IllegalAccessException, InstantiationException
+ , NoSuchMethodException, ClassNotFoundException, NoSuchFieldException {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+
+ Class<?> delegateClass = getDelegateClass(proxyClass);
+ Class<?> paramTypes[] = updateMethodArgs(null, initargs);
+ Method facMethod = delegateClass.getMethod(factoryMethod, paramTypes);
+ Object delegateRef = facMethod.invoke(null, initargs);
+
+ if (delegateRef == null) {
+ return null;
+ }
+
+ HorribleProxy hp = new HorribleProxy(delegateClass, delegateRef);
+ return (T)Proxy.newProxyInstance(cl, new Class<?>[]{proxyClass}, hp);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Exception {
+ String methodName = method.getName().replaceFirst("\\$.*", "");
+ if (Object.class == method.getDeclaringClass()) {
+ if ("equals".equals(methodName)) {
+ return proxy == args[0];
+ } else if ("hashCode".equals(methodName)) {
+ return System.identityHashCode(proxy);
+ } else if ("toString".equals(methodName)) {
+ return proxy.getClass().getName() + "@"
+ + Integer.toHexString(System.identityHashCode(proxy))
+ + ", with InvocationHandler " + this;
+ } else {
+ throw new IllegalStateException(String.valueOf(method));
+ }
+ }
+
+ if ("getDelegate".equals(methodName)) {
+ initDeferred();
+ return delegateRef;
+ } else if ("setInitDeferred".equals(methodName)) {
+ initDeferred = (Boolean)args[0];
+ return null;
+ }
+
+ Class<?> methodParams[] = updateMethodArgs(method.getParameterTypes(), args);
+
+ Object ret = null;
+ boolean isStaticField = false;
+ if (methodParams.length == 0) {
+ // check for static fields first
+ try {
+ Field f = delegateClass.getDeclaredField(methodName);
+ ret = f.get(delegateRef);
+ isStaticField = true;
+ } catch (NoSuchFieldException e) {
+ LOG.log(POILogger.DEBUG, "No static field '"+methodName+"' in class '"+delegateClass.getCanonicalName()+"' - trying method now.");
+ }
+ }
+
+ if (!isStaticField) {
+ Method methodImpl = null;
+ try {
+ methodImpl = delegateClass.getMethod(methodName, methodParams);
+ } catch (Exception e) {
+ // fallback - if methodName is distinct, try to use it
+ // in case we can't provide method declaration in the Proxy interface
+ // ... and of course, this is horrible ...
+ methodImpl = MethodUtils.getMatchingAccessibleMethod(delegateClass, methodName, methodParams);
+
+ if (methodImpl == null) {
+ throw new RuntimeException("There's no method '"+methodName+"' for the given arguments.");
+ }
+ }
+
+ if (!Modifier.isStatic(methodImpl.getModifiers())) {
+ initDeferred();
+ }
+ ret = methodImpl.invoke(delegateRef, args);
+ }
+
+ Class<?> retType = method.getReturnType();
+ if (retType.isArray()) {
+ if (ProxyIf.class.isAssignableFrom(retType.getComponentType())) {
+ Class<? extends ProxyIf> cType = (Class<? extends ProxyIf>)retType.getComponentType();
+ ProxyIf paRet[] = (ProxyIf[])Array.newInstance(cType, ((Object[])ret).length);
+ for (int i=0; i<((Object[])ret).length; i++) {
+ paRet[i] = newProxy(cType, ((Object[])ret)[i]);
+ paRet[i].setInitDeferred(false);
+ }
+ ret = paRet;
+ }
+ } else if (ProxyIf.class.isAssignableFrom(retType)) {
+ ProxyIf pRet = newProxy((Class<? extends ProxyIf>)retType, ret);
+ pRet.setInitDeferred(false);
+ ret = pRet;
+ }
+
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Class<?>[] updateMethodArgs(Class<?> types[], Object args[])
+ throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
+ if (args == null) return new Class<?>[0];
+ if (types == null) types = new Class<?>[args.length];
+ if (types.length != args.length) {
+ throw new IllegalArgumentException();
+ }
+
+ for (int i=0; i<types.length; i++) {
+ if (types[i] == null) {
+ if (args[i] == null) {
+ throw new IllegalArgumentException();
+ }
+ types[i] = args[i].getClass();
+ }
+
+ if (ProxyIf.class.isAssignableFrom(types[i])) {
+ types[i] = getDelegateClass((Class<? extends ProxyIf>)types[i]);
+ if (args[i] != null) {
+ args[i] = ((ProxyIf)args[i]).getDelegate();
+ }
+ }
+ }
+ return types;
+ }
+
+ private void initDeferred() throws Exception {
+ if (delegateRef != null || !initDeferred) return;
+ // currently works only for empty constructor
+ delegateRef = delegateClass.getConstructor().newInstance();
+ }
+
+ private static Class<?> getDelegateClass(Class<? extends ProxyIf> proxyClass)
+ throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
+ Field delegateField;
+ try {
+ delegateField = proxyClass.getDeclaredField("delegateClass");
+ } catch (NoSuchFieldException e) {
+ // sometimes a proxy interface is returned as proxyClass
+ // this has to be asked for the real ProxyIf interface
+ Class<?> ifs[] = proxyClass.getInterfaces();
+ if (ifs == null || ifs.length != 1) {
+ throw new IllegalArgumentException();
+ }
+ delegateField = ifs[0].getDeclaredField("delegateClass");
+ }
+
+ String delegateClassName = (String)delegateField.get(null);
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ Class<?> delegateClass = Class.forName(delegateClassName, true, cl);
+ return delegateClass;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/KeyInfoKeySelector.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/KeyInfoKeySelector.java
new file mode 100644
index 0000000000..c24c36fc49
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/KeyInfoKeySelector.java
@@ -0,0 +1,101 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import java.security.Key;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import javax.xml.crypto.AlgorithmMethod;
+import javax.xml.crypto.KeySelector;
+import javax.xml.crypto.KeySelectorException;
+import javax.xml.crypto.KeySelectorResult;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.X509Data;
+
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * JSR105 key selector implementation using the ds:KeyInfo data of the signature
+ * itself.
+ */
+public class KeyInfoKeySelector extends KeySelector implements KeySelectorResult {
+
+ private static final POILogger LOG = POILogFactory.getLogger(KeyInfoKeySelector.class);
+
+ private X509Certificate certificate;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public KeySelectorResult select(KeyInfo keyInfo, Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
+ LOG.log(POILogger.DEBUG, "select key");
+ if (null == keyInfo) {
+ throw new KeySelectorException("no ds:KeyInfo present");
+ }
+ List<XMLStructure> keyInfoContent = keyInfo.getContent();
+ this.certificate = null;
+ for (XMLStructure keyInfoStructure : keyInfoContent) {
+ if (false == (keyInfoStructure instanceof X509Data)) {
+ continue;
+ }
+ X509Data x509Data = (X509Data) keyInfoStructure;
+ List<Object> x509DataList = x509Data.getContent();
+ for (Object x509DataObject : x509DataList) {
+ if (false == (x509DataObject instanceof X509Certificate)) {
+ continue;
+ }
+ X509Certificate certificate = (X509Certificate) x509DataObject;
+ LOG.log(POILogger.DEBUG, "certificate", certificate.getSubjectX500Principal());
+ if (null == this.certificate) {
+ /*
+ * The first certificate is presumably the signer.
+ */
+ this.certificate = certificate;
+ }
+ }
+ if (null != this.certificate) {
+ return this;
+ }
+ }
+ throw new KeySelectorException("No key found!");
+ }
+
+ public Key getKey() {
+ return this.certificate.getPublicKey();
+ }
+
+ /**
+ * Gives back the X509 certificate used during the last signature
+ * verification operation.
+ *
+ * @return
+ */
+ public X509Certificate getCertificate() {
+ return this.certificate;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/OOXMLURIDereferencer.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/OOXMLURIDereferencer.java
new file mode 100644
index 0000000000..53b2d9fe38
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/OOXMLURIDereferencer.java
@@ -0,0 +1,114 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.xml.crypto.Data;
+import javax.xml.crypto.OctetStreamData;
+import javax.xml.crypto.URIDereferencer;
+import javax.xml.crypto.URIReference;
+import javax.xml.crypto.URIReferenceException;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackagePartName;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * JSR105 URI dereferencer for Office Open XML documents.
+ */
+public class OOXMLURIDereferencer implements URIDereferencer {
+
+ private static final POILogger LOG = POILogFactory.getLogger(OOXMLURIDereferencer.class);
+
+ private final OPCPackage pkg;
+
+ private final URIDereferencer baseUriDereferencer;
+
+ public OOXMLURIDereferencer(OPCPackage pkg) {
+ if (null == pkg) {
+ throw new IllegalArgumentException("OPCPackage is null");
+ }
+ this.pkg = pkg;
+ XMLSignatureFactory xmlSignatureFactory = SignatureInfo.getSignatureFactory();
+ this.baseUriDereferencer = xmlSignatureFactory.getURIDereferencer();
+ }
+
+ public Data dereference(URIReference uriReference, XMLCryptoContext context) throws URIReferenceException {
+ if (null == uriReference) {
+ throw new NullPointerException("URIReference cannot be null");
+ }
+ if (null == context) {
+ throw new NullPointerException("XMLCrytoContext cannot be null");
+ }
+
+ URI uri;
+ try {
+ uri = new URI(uriReference.getURI());
+ } catch (URISyntaxException e) {
+ throw new URIReferenceException("could not URL decode the uri: "+uriReference.getURI(), e);
+ }
+
+ PackagePart part = findPart(uri);
+ if (part == null) {
+ LOG.log(POILogger.DEBUG, "cannot resolve, delegating to base DOM URI dereferencer", uri);
+ return this.baseUriDereferencer.dereference(uriReference, context);
+ }
+
+ try {
+ return new OctetStreamData(part.getInputStream(), uri.toString(), null);
+ } catch (IOException e) {
+ throw new URIReferenceException("I/O error: " + e.getMessage(), e);
+ }
+ }
+
+ private PackagePart findPart(URI uri) {
+ LOG.log(POILogger.DEBUG, "dereference", uri);
+
+ String path = uri.getPath();
+ if (path == null || "".equals(path)) {
+ LOG.log(POILogger.DEBUG, "illegal part name (expected)", uri);
+ return null;
+ }
+
+ PackagePartName ppn;
+ try {
+ ppn = PackagingURIHelper.createPartName(path);
+ } catch (InvalidFormatException e) {
+ LOG.log(POILogger.WARN, "illegal part name (not expected)", uri);
+ return null;
+ }
+
+ return pkg.getPart(ppn);
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/RevokedCertificateSecurityException.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/RevokedCertificateSecurityException.java
new file mode 100644
index 0000000000..fa4fee7370
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/RevokedCertificateSecurityException.java
@@ -0,0 +1,38 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+/**
+ * Exception thrown in case the incoming eID certificate has been revoked.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class RevokedCertificateSecurityException extends
+ CertificateSecurityException {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java
new file mode 100644
index 0000000000..9e945840c3
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java
@@ -0,0 +1,283 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.Key;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.crypto.Cipher;
+import javax.xml.crypto.dsig.XMLSignature;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.dom.DOMValidateContext;
+import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
+import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.InitIf;
+import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
+import org.apache.poi.poifs.crypt.dsig.services.XmlSignatureService;
+import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.util.SAXHelper;
+import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlObject;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class SignatureInfo {
+
+ public static final byte[] SHA1_DIGEST_INFO_PREFIX = new byte[]
+ { 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14 };
+
+ public static final byte[] SHA224_DIGEST_INFO_PREFIX = new byte[]
+ { 0x30, 0x2b, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
+ , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x04, 0x1c };
+
+ public static final byte[] SHA256_DIGEST_INFO_PREFIX = new byte[]
+ { 0x30, 0x2f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
+ , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x04, 0x20 };
+
+ public static final byte[] SHA384_DIGEST_INFO_PREFIX = new byte[]
+ { 0x30, 0x3f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
+ , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x04, 0x30 };
+
+ public static final byte[] SHA512_DIGEST_INFO_PREFIX = new byte[]
+ { 0x30, 0x4f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
+ , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x04, 0x40 };
+
+ public static final byte[] RIPEMD128_DIGEST_INFO_PREFIX = new byte[]
+ { 0x30, 0x1b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x02, 0x04, 0x10 };
+
+ public static final byte[] RIPEMD160_DIGEST_INFO_PREFIX = new byte[]
+ { 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x01, 0x04, 0x14 };
+
+ public static final byte[] RIPEMD256_DIGEST_INFO_PREFIX = new byte[]
+ { 0x30, 0x2b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24, 0x03, 0x02, 0x03, 0x04, 0x20 };
+
+
+ private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class);
+ private static boolean isInitialized = false;
+
+ private final OPCPackage pkg;
+
+ public SignatureInfo(OPCPackage pkg) {
+ this.pkg = pkg;
+ }
+
+ public boolean verifySignature() {
+ initXmlProvider();
+ // http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html
+ List<X509Certificate> signers = new LinkedList<X509Certificate>();
+ return getSignersAndValidate(signers, true);
+ }
+
+ public void confirmSignature(Key key, X509Certificate x509)
+ throws NoSuchAlgorithmException, IOException {
+ confirmSignature(key, x509, HashAlgorithm.sha1);
+ }
+
+ public void confirmSignature(Key key, X509Certificate x509, HashAlgorithm hashAlgo)
+ throws NoSuchAlgorithmException, IOException {
+ XmlSignatureService signatureService = createSignatureService(hashAlgo, pkg);
+
+ // operate
+ List<X509Certificate> x509Chain = Collections.singletonList(x509);
+ DigestInfo digestInfo = signatureService.preSign(null, x509Chain, null, null, null);
+
+ // setup: key material, signature value
+
+ Cipher cipher = CryptoFunctions.getCipher(key, CipherAlgorithm.rsa
+ , ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding");
+
+ byte[] signatureValue;
+ try {
+ ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream();
+ digestInfoValueBuf.write(SHA1_DIGEST_INFO_PREFIX);
+ digestInfoValueBuf.write(digestInfo.digestValue);
+ byte[] digestInfoValue = digestInfoValueBuf.toByteArray();
+ signatureValue = cipher.doFinal(digestInfoValue);
+ } catch (Exception e) {
+ throw new EncryptedDocumentException(e);
+ }
+
+
+ // operate: postSign
+ signatureService.postSign(signatureValue, Collections.singletonList(x509));
+ }
+
+ public XmlSignatureService createSignatureService(HashAlgorithm hashAlgo, OPCPackage pkg) {
+ XmlSignatureService signatureService = new XmlSignatureService(hashAlgo, pkg);
+ signatureService.initFacets(new Date());
+ return signatureService;
+ }
+
+ public List<X509Certificate> getSigners() {
+ initXmlProvider();
+ List<X509Certificate> signers = new LinkedList<X509Certificate>();
+ getSignersAndValidate(signers, false);
+ return signers;
+ }
+
+ protected boolean getSignersAndValidate(List<X509Certificate> signers, boolean onlyFirst) {
+ boolean allValid = true;
+ List<PackagePart> signatureParts = getSignatureParts(onlyFirst);
+ if (signatureParts.isEmpty()) {
+ LOG.log(POILogger.DEBUG, "no signature resources");
+ allValid = false;
+ }
+
+ for (PackagePart signaturePart : signatureParts) {
+ KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
+
+ try {
+ Document doc = SAXHelper.readSAXDocumentW3C(signaturePart.getInputStream());
+ // dummy call to createSignatureService to tweak document afterwards
+ createSignatureService(HashAlgorithm.sha1, pkg).registerIds(doc);
+
+ DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc);
+ domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE);
+ OOXMLURIDereferencer dereferencer = new OOXMLURIDereferencer(pkg);
+ domValidateContext.setURIDereferencer(dereferencer);
+
+ XMLSignatureFactory xmlSignatureFactory = getSignatureFactory();
+ XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
+ boolean validity = xmlSignature.validate(domValidateContext);
+ allValid &= validity;
+ if (!validity) continue;
+ // TODO: check what has been signed.
+ } catch (Exception e) {
+ LOG.log(POILogger.ERROR, "error in marshalling and validating the signature", e);
+ continue;
+ }
+
+ X509Certificate signer = keySelector.getCertificate();
+ signers.add(signer);
+ }
+
+ return allValid;
+ }
+
+ protected List<PackagePart> getSignatureParts(boolean onlyFirst) {
+ List<PackagePart> packageParts = new LinkedList<PackagePart>();
+
+ PackageRelationshipCollection sigOrigRels = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
+ for (PackageRelationship rel : sigOrigRels) {
+ PackagePart sigPart = pkg.getPart(rel);
+ LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart);
+
+ try {
+ PackageRelationshipCollection sigRels = sigPart.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE);
+ for (PackageRelationship sigRel : sigRels) {
+ PackagePart sigRelPart = sigPart.getRelatedPart(sigRel);
+ LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart);
+ packageParts.add(sigRelPart);
+ if (onlyFirst) break;
+ }
+ } catch (InvalidFormatException e) {
+ LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);
+ }
+
+ if (onlyFirst && !packageParts.isEmpty()) break;
+ }
+
+ return packageParts;
+ }
+
+ public static XMLSignatureFactory getSignatureFactory() {
+ Provider p = Security.getProvider("XMLDSig");
+ assert(p != null);
+ return XMLSignatureFactory.getInstance("DOM", p);
+ }
+
+ public static KeyInfoFactory getKeyInfoFactory() {
+ Provider p = Security.getProvider("XMLDSig");
+ assert(p != null);
+ return KeyInfoFactory.getInstance("DOM", p);
+ }
+
+ public static void insertXChild(XmlObject root, XmlObject child) {
+ XmlCursor rootCursor = root.newCursor();
+ insertXChild(rootCursor, child);
+ rootCursor.dispose();
+ }
+
+ public static void insertXChild(XmlCursor rootCursor, XmlObject child) {
+ rootCursor.toEndToken();
+ XmlCursor childCursor = child.newCursor();
+ childCursor.toNextToken();
+ childCursor.moveXml(rootCursor);
+ childCursor.dispose();
+ }
+
+ public static void setPrefix(XmlObject xobj, String ns, String prefix) {
+ for (XmlCursor cur = xobj.newCursor(); cur.hasNextToken(); cur.toNextToken()) {
+ if (cur.isStart()) {
+ Element el = (Element)cur.getDomNode();
+ if (ns.equals(el.getNamespaceURI())) el.setPrefix(prefix);
+ }
+ }
+ }
+
+ public static synchronized void initXmlProvider() {
+ if (isInitialized) return;
+ isInitialized = true;
+
+ try {
+ InitIf init = HorribleProxy.newProxy(InitIf.class);
+ init.init();
+
+ RelationshipTransformService.registerDsigProvider();
+
+ Provider bcProv = Security.getProvider("BC");
+ if (bcProv == null) {
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ Class<?> c = cl.loadClass("org.bouncycastle.jce.provider.BouncyCastleProvider");
+ bcProv = (Provider)c.newInstance();
+ Security.addProvider(bcProv);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Xml & BouncyCastle-Provider initialization failed", e);
+ }
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/TrustCertificateSecurityException.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/TrustCertificateSecurityException.java
new file mode 100644
index 0000000000..b99b5ebb99
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/TrustCertificateSecurityException.java
@@ -0,0 +1,38 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+/**
+ * Exception thrown in case the incoming eID certificate is not trusted.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class TrustCertificateSecurityException extends
+ CertificateSecurityException {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java
new file mode 100644
index 0000000000..4cbcdcc54b
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java
@@ -0,0 +1,194 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.KeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Provider;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.dom.DOMCryptoContext;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.dom.DOMSignContext;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
+import javax.xml.crypto.dsig.keyinfo.KeyValue;
+import javax.xml.crypto.dsig.keyinfo.X509Data;
+
+import org.apache.poi.poifs.crypt.dsig.HorribleProxy;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DOMKeyInfoIf;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.w3.x2000.x09.xmldsig.ObjectType;
+import org.w3.x2000.x09.xmldsig.SignatureType;
+import org.w3c.dom.Node;
+
+/**
+ * Signature Facet implementation that adds ds:KeyInfo to the XML signature.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class KeyInfoSignatureFacet implements SignatureFacet {
+
+ private static final POILogger LOG = POILogFactory.getLogger(KeyInfoSignatureFacet.class);
+
+ private final boolean includeEntireCertificateChain;
+
+ private final boolean includeIssuerSerial;
+
+ private final boolean includeKeyValue;
+
+ /**
+ * Main constructor.
+ *
+ * @param includeEntireCertificateChain
+ * @param includeIssuerSerial
+ * @param includeKeyValue
+ */
+ public KeyInfoSignatureFacet(boolean includeEntireCertificateChain,
+ boolean includeIssuerSerial, boolean includeKeyValue) {
+ this.includeEntireCertificateChain = includeEntireCertificateChain;
+ this.includeIssuerSerial = includeIssuerSerial;
+ this.includeKeyValue = includeKeyValue;
+ }
+
+ public void postSign(SignatureType signatureElement,
+ List<X509Certificate> signingCertificateChain) {
+ LOG.log(POILogger.DEBUG, "postSign");
+
+ List<ObjectType> objList = signatureElement.getObjectList();
+
+ /*
+ * Make sure we insert right after the ds:SignatureValue element, just
+ * before the first ds:Object element.
+ */
+ Node nextSibling = (objList.isEmpty()) ? null : objList.get(0).getDomNode();
+
+ /*
+ * Construct the ds:KeyInfo element using JSR 105.
+ */
+ String providerName = System.getProperty("jsr105Provider", "org.jcp.xml.dsig.internal.dom.XMLDSigRI");
+ Provider xmlDSigProv;
+ try {
+ xmlDSigProv = (Provider) Class.forName(providerName).newInstance();
+ } catch (Exception e) {
+ throw new RuntimeException("JRE doesn't support default xml signature provider - set jsr105Provider system property!", e);
+ }
+
+ KeyInfoFactory keyInfoFactory = KeyInfoFactory.getInstance("DOM", xmlDSigProv);
+ List<Object> x509DataObjects = new LinkedList<Object>();
+ X509Certificate signingCertificate = signingCertificateChain.get(0);
+
+ List<Object> keyInfoContent = new LinkedList<Object>();
+
+ if (this.includeKeyValue) {
+ KeyValue keyValue;
+ try {
+ keyValue = keyInfoFactory.newKeyValue(signingCertificate.getPublicKey());
+ } catch (KeyException e) {
+ throw new RuntimeException("key exception: " + e.getMessage(), e);
+ }
+ keyInfoContent.add(keyValue);
+ }
+
+ if (this.includeIssuerSerial) {
+ x509DataObjects.add(keyInfoFactory.newX509IssuerSerial(
+ signingCertificate.getIssuerX500Principal().toString(),
+ signingCertificate.getSerialNumber()));
+ }
+
+ if (this.includeEntireCertificateChain) {
+ for (X509Certificate certificate : signingCertificateChain) {
+ x509DataObjects.add(certificate);
+ }
+ } else {
+ x509DataObjects.add(signingCertificate);
+ }
+
+ if (false == x509DataObjects.isEmpty()) {
+ X509Data x509Data = keyInfoFactory.newX509Data(x509DataObjects);
+ keyInfoContent.add(x509Data);
+ }
+ KeyInfo keyInfo = keyInfoFactory.newKeyInfo(keyInfoContent);
+ DOMKeyInfoIf domKeyInfo;
+ try {
+ domKeyInfo = HorribleProxy.newProxy(DOMKeyInfoIf.class, keyInfo);
+ } catch (Exception e) {
+ throw new RuntimeException("DOMKeyInfo instance error: " + e.getMessage(), e);
+ }
+
+ Key key = new Key() {
+ private static final long serialVersionUID = 1L;
+
+ public String getAlgorithm() {
+ return null;
+ }
+
+ public byte[] getEncoded() {
+ return null;
+ }
+
+ public String getFormat() {
+ return null;
+ }
+ };
+
+ DOMSignContext domSignContext = new DOMSignContext(key, signatureElement.getDomNode());
+ DOMCryptoContext domCryptoContext = domSignContext;
+ String signatureNamespacePrefix = "xd";
+ try {
+ domKeyInfo.marshal(signatureElement.getDomNode(), nextSibling,
+ signatureNamespacePrefix, domCryptoContext);
+ } catch (MarshalException e) {
+ throw new RuntimeException("marshall error: " + e.getMessage(), e);
+ }
+ }
+
+ public void preSign(XMLSignatureFactory signatureFactory,
+ String signatureId,
+ List<X509Certificate> signingCertificateChain,
+ List<Reference> references,
+ List<XMLObject> objects
+ ) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ // empty
+ }
+
+ public Map<String,String> getNamespacePrefixMapping() {
+ Map<String,String> map = new HashMap<String,String>();
+ // map.put("xd", "http://www.w3.org/2000/09/xmldsig#");
+ return map;
+ }
+
+} \ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java
new file mode 100644
index 0000000000..f7978f4e71
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java
@@ -0,0 +1,541 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import static org.apache.poi.poifs.crypt.dsig.SignatureInfo.setPrefix;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.Manifest;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.SignatureProperties;
+import javax.xml.crypto.dsig.SignatureProperty;
+import javax.xml.crypto.dsig.Transform;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.spec.TransformParameterSpec;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.ContentTypes;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackageNamespaces;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackagePartName;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
+import org.apache.poi.openxml4j.opc.TargetMode;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
+import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService.RelationshipTransformParameterSpec;
+import org.apache.poi.poifs.crypt.dsig.services.XmlSignatureService;
+import org.apache.poi.poifs.crypt.dsig.spi.Constants;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xmlbeans.XmlException;
+import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.CTSignatureTime;
+import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.SignatureTimeDocument;
+import org.w3.x2000.x09.xmldsig.SignatureType;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import com.microsoft.schemas.office.x2006.digsig.CTSignatureInfoV1;
+import com.microsoft.schemas.office.x2006.digsig.SignatureInfoV1Document;
+
+/**
+ * Office OpenXML Signature Facet implementation.
+ *
+ * @author fcorneli
+ * @see http://msdn.microsoft.com/en-us/library/cc313071.aspx
+ */
+public class OOXMLSignatureFacet implements SignatureFacet {
+
+ private static final POILogger LOG = POILogFactory.getLogger(OOXMLSignatureFacet.class);
+
+ public static final String OOXML_DIGSIG_NS = "http://schemas.openxmlformats.org/package/2006/digital-signature";
+ public static final String OFFICE_DIGSIG_NS = "http://schemas.microsoft.com/office/2006/digsig";
+
+ private final XmlSignatureService signatureService;
+
+ private final Date clock;
+
+ private final HashAlgorithm hashAlgo;
+
+ /**
+ * Main constructor.
+ */
+ public OOXMLSignatureFacet(XmlSignatureService signatureService, Date clock, HashAlgorithm hashAlgo) {
+ this.signatureService = signatureService;
+ this.clock = (clock == null ? new Date() : clock);
+ this.hashAlgo = (hashAlgo == null ? HashAlgorithm.sha1 : hashAlgo);
+ }
+
+ public void preSign(XMLSignatureFactory signatureFactory,
+ String signatureId,
+ List<X509Certificate> signingCertificateChain,
+ List<Reference> references, List<XMLObject> objects)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ LOG.log(POILogger.DEBUG, "pre sign");
+ addManifestObject(signatureFactory, signatureId, references, objects);
+ addSignatureInfo(signatureFactory, signatureId, references, objects);
+ }
+
+ private void addManifestObject(XMLSignatureFactory signatureFactory,
+ String signatureId, List<Reference> references,
+ List<XMLObject> objects) throws NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException {
+ Manifest manifest = constructManifest(signatureFactory);
+ String objectId = "idPackageObject"; // really has to be this value.
+ List<XMLStructure> objectContent = new LinkedList<XMLStructure>();
+ objectContent.add(manifest);
+
+ addSignatureTime(signatureFactory, signatureId, objectContent);
+
+ objects.add(signatureFactory.newXMLObject(objectContent, objectId,
+ null, null));
+
+ DigestMethod digestMethod = signatureFactory.newDigestMethod(this.hashAlgo.xmlSignUri, null);
+ Reference reference = signatureFactory.newReference("#" + objectId,
+ digestMethod, null, "http://www.w3.org/2000/09/xmldsig#Object",
+ null);
+ references.add(reference);
+ }
+
+ private Manifest constructManifest(XMLSignatureFactory signatureFactory)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ List<Reference> manifestReferences = new ArrayList<Reference>();
+
+ try {
+ addManifestReferences(signatureFactory, manifestReferences);
+ } catch (Exception e) {
+ throw new RuntimeException("error: " + e.getMessage(), e);
+ }
+
+ return signatureFactory.newManifest(manifestReferences);
+ }
+
+ private void addManifestReferences(XMLSignatureFactory signatureFactory, List<Reference> manifestReferences)
+ throws IOException, NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException, URISyntaxException, XmlException {
+
+ OPCPackage ooxml = this.signatureService.getOfficeOpenXMLDocument();
+ List<PackagePart> relsEntryNames = ooxml.getPartsByContentType(ContentTypes.RELATIONSHIPS_PART);
+
+
+ DigestMethod digestMethod = signatureFactory.newDigestMethod(this.hashAlgo.xmlSignUri, null);
+ Set<String> digestedPartNames = new HashSet<String>();
+ for (PackagePart pp : relsEntryNames) {
+ String baseUri = pp.getPartName().getName().replaceFirst("(.*)/_rels/.*", "$1");
+
+ PackageRelationshipCollection prc;
+ try {
+ prc = new PackageRelationshipCollection(ooxml);
+ prc.parseRelationshipsPart(pp);
+ } catch (InvalidFormatException e) {
+ throw new IOException("Invalid relationship descriptor: "+pp.getPartName().getName(), e);
+ }
+
+ RelationshipTransformParameterSpec parameterSpec = new RelationshipTransformParameterSpec();
+ for (PackageRelationship relationship : prc) {
+ String relationshipType = relationship.getRelationshipType();
+
+ if (TargetMode.EXTERNAL == relationship.getTargetMode()) {
+ /*
+ * ECMA-376 Part 2 - 3rd edition
+ * 13.2.4.16 Manifest Element
+ * "The producer shall not create a Manifest element that references any data outside of the package."
+ */
+ continue;
+ }
+
+ if (!isSignedRelationship(relationshipType)) continue;
+
+ parameterSpec.addRelationshipReference(relationship.getId());
+
+ // TODO: find a better way ...
+ String partName = baseUri + relationship.getTargetURI().toString();
+ partName = new URI(partName).normalize().getPath().replace('\\', '/');
+ LOG.log(POILogger.DEBUG, "part name: " + partName);
+
+ String contentType;
+ try {
+ PackagePartName relName = PackagingURIHelper.createPartName(partName);
+ PackagePart pp2 = ooxml.getPart(relName);
+ contentType = pp2.getContentType();
+ } catch (InvalidFormatException e) {
+ throw new IOException(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 = signatureFactory.newReference(uri, digestMethod);
+ manifestReferences.add(reference);
+ digestedPartNames.add(partName);
+ }
+ }
+
+ if (parameterSpec.hasSourceIds()) {
+ List<Transform> transforms = new LinkedList<Transform>();
+ transforms.add(signatureFactory.newTransform(
+ RelationshipTransformService.TRANSFORM_URI,
+ parameterSpec));
+ transforms.add(signatureFactory.newTransform(
+ CanonicalizationMethod.INCLUSIVE,
+ (TransformParameterSpec) null));
+ String uri = pp.getPartName().getName()
+ + "?ContentType=application/vnd.openxmlformats-package.relationships+xml";
+ Reference reference = signatureFactory.newReference(uri, digestMethod, transforms, null, null);
+ manifestReferences.add(reference);
+ }
+ }
+ }
+
+
+ private void addSignatureTime(XMLSignatureFactory signatureFactory,
+ String signatureId,
+ List<XMLStructure> objectContent) {
+ /*
+ * SignatureTime
+ */
+ DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String nowStr = fmt.format(this.clock);
+ LOG.log(POILogger.DEBUG, "now: " + nowStr);
+
+ SignatureTimeDocument sigTime = SignatureTimeDocument.Factory.newInstance();
+ CTSignatureTime ctTime = sigTime.addNewSignatureTime();
+ ctTime.setFormat("YYYY-MM-DDThh:mm:ssTZD");
+ ctTime.setValue(nowStr);
+
+ // TODO: find better method to have xmlbeans + export the prefix
+ Node n = ctTime.getDomNode();
+ setPrefix(ctTime, PackageNamespaces.DIGITAL_SIGNATURE, "mdssi");
+
+ List<XMLStructure> signatureTimeContent = new LinkedList<XMLStructure>();
+ signatureTimeContent.add(new DOMStructure(n));
+ SignatureProperty signatureTimeSignatureProperty = signatureFactory
+ .newSignatureProperty(signatureTimeContent, "#" + signatureId,
+ "idSignatureTime");
+ List<SignatureProperty> signaturePropertyContent = new LinkedList<SignatureProperty>();
+ signaturePropertyContent.add(signatureTimeSignatureProperty);
+ SignatureProperties signatureProperties = signatureFactory
+ .newSignatureProperties(signaturePropertyContent,
+ "id-signature-time-" + this.clock.getTime());
+ objectContent.add(signatureProperties);
+ }
+
+ private void addSignatureInfo(XMLSignatureFactory signatureFactory,
+ String signatureId, List<Reference> references,
+ List<XMLObject> objects) throws NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException {
+ List<XMLStructure> objectContent = new LinkedList<XMLStructure>();
+
+ SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance();
+ CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1();
+ ctSigV1.setManifestHashAlgorithm("http://www.w3.org/2000/09/xmldsig#sha1");
+ Node n = ctSigV1.getDomNode();
+ ((Element)n).setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "http://schemas.microsoft.com/office/2006/digsig");
+
+ List<XMLStructure> signatureInfoContent = new LinkedList<XMLStructure>();
+ signatureInfoContent.add(new DOMStructure(n));
+ SignatureProperty signatureInfoSignatureProperty = signatureFactory
+ .newSignatureProperty(signatureInfoContent, "#" + signatureId,
+ "idOfficeV1Details");
+
+ List<SignatureProperty> signaturePropertyContent = new LinkedList<SignatureProperty>();
+ signaturePropertyContent.add(signatureInfoSignatureProperty);
+ SignatureProperties signatureProperties = signatureFactory
+ .newSignatureProperties(signaturePropertyContent, null);
+ objectContent.add(signatureProperties);
+
+ String objectId = "idOfficeObject";
+ objects.add(signatureFactory.newXMLObject(objectContent, objectId,
+ null, null));
+
+ DigestMethod digestMethod = signatureFactory.newDigestMethod(this.hashAlgo.xmlSignUri, null);
+ Reference reference = signatureFactory.newReference("#" + objectId,
+ digestMethod, null, "http://www.w3.org/2000/09/xmldsig#Object",
+ null);
+ references.add(reference);
+ }
+
+ public void postSign(SignatureType signatureElement,
+ List<X509Certificate> signingCertificateChain) {
+ // empty
+ }
+
+ public static String getRelationshipReferenceURI(String zipEntryName) {
+
+ return "/"
+ + zipEntryName
+ + "?ContentType=application/vnd.openxmlformats-package.relationships+xml";
+ }
+
+ public static String getResourceReferenceURI(String resourceName,
+ String contentType) {
+
+ return "/" + resourceName + "?ContentType=" + contentType;
+ }
+
+ public static 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" };
+
+ public 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;
+ }
+
+ public Map<String,String> getNamespacePrefixMapping() {
+ Map<String,String> m = new HashMap<String,String>();
+ m.put("mdssi", OOXML_DIGSIG_NS);
+ m.put("xd", "http://uri.etsi.org/01903/v1.3.2#");
+ return m;
+ }
+
+
+ /**
+ * Office 2010 list of signed types (extensions).
+ */
+ public static 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" //
+ };
+} \ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java
new file mode 100644
index 0000000000..ead9d2f2ce
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java
@@ -0,0 +1,101 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import static org.apache.poi.poifs.crypt.dsig.facets.XAdESXLSignatureFacet.XADES_NAMESPACE;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.namespace.QName;
+
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlObject;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesType;
+import org.etsi.uri.x01903.v13.UnsignedPropertiesType;
+import org.etsi.uri.x01903.v13.UnsignedSignaturePropertiesType;
+import org.w3.x2000.x09.xmldsig.ObjectType;
+import org.w3.x2000.x09.xmldsig.SignatureType;
+
+/**
+ * Work-around for Office2010 to accept the XAdES-BES/EPES signature.
+ *
+ * xades:UnsignedProperties/xades:UnsignedSignatureProperties needs to be
+ * present.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class Office2010SignatureFacet implements SignatureFacet {
+
+ public void preSign(XMLSignatureFactory signatureFactory,
+ String signatureId,
+ List<X509Certificate> signingCertificateChain,
+ List<Reference> references,
+ List<XMLObject> objects
+ ) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ }
+
+ public void postSign(SignatureType signatureElement, List<X509Certificate> signingCertificateChain) {
+ QualifyingPropertiesType qualProps = null;
+
+ try {
+ // check for XAdES-BES
+ for (ObjectType ot : signatureElement.getObjectList()) {
+ XmlObject xo[] = ot.selectChildren(new QName(XADES_NAMESPACE, "QualifyingProperties"));
+ if (xo != null && xo.length > 0) {
+ qualProps = QualifyingPropertiesType.Factory.parse(xo[0].getDomNode());
+ break;
+ }
+ }
+ } catch (XmlException e) {
+ throw new RuntimeException("signature decoding error", e);
+ }
+
+ if (qualProps == null) {
+ throw new IllegalArgumentException("no XAdES-BES extension present");
+ }
+
+ // create basic XML container structure
+ UnsignedPropertiesType unsignedProps = qualProps.getUnsignedProperties();
+ if (unsignedProps == null) {
+ unsignedProps = qualProps.addNewUnsignedProperties();
+ }
+ UnsignedSignaturePropertiesType unsignedSigProps = unsignedProps.getUnsignedSignatureProperties();
+ if (unsignedSigProps == null) {
+ unsignedSigProps = unsignedProps.addNewUnsignedSignatureProperties();
+ }
+ }
+
+ public Map<String,String> getNamespacePrefixMapping() {
+ return null;
+ }
+} \ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java
new file mode 100644
index 0000000000..70f7d911a6
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java
@@ -0,0 +1,83 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+
+import org.w3.x2000.x09.xmldsig.SignatureType;
+
+/**
+ * JSR105 Signature Facet interface.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface SignatureFacet {
+
+ /**
+ * This method is being invoked by the XML signature service engine during
+ * pre-sign phase. Via this method a signature facet implementation can add
+ * signature facets to an XML signature.
+ *
+ * @param signatureFactory
+ * @param document
+ * @param signatureId
+ * @param signingCertificateChain
+ * the optional signing certificate chain
+ * @param references
+ * @param objects
+ * @throws InvalidAlgorithmParameterException
+ * @throws NoSuchAlgorithmException
+ */
+ void preSign(
+ XMLSignatureFactory signatureFactory
+ , String signatureId
+ , List<X509Certificate> signingCertificateChain
+ , List<Reference> references
+ , List<XMLObject> objects
+ ) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException;
+
+ /**
+ * This method is being invoked by the XML signature service engine during
+ * the post-sign phase. Via this method a signature facet can extend the XML
+ * signatures with for example key information.
+ *
+ * @param signatureElement
+ * @param signingCertificateChain
+ */
+ void postSign(
+ SignatureType signatureElement
+ , List<X509Certificate> signingCertificateChain);
+
+ Map<String,String> getNamespacePrefixMapping();
+} \ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignaturePolicyService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignaturePolicyService.java
new file mode 100644
index 0000000000..d19fb8dfe2
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignaturePolicyService.java
@@ -0,0 +1,65 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+/**
+ * Interface for the signature policy service.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface SignaturePolicyService {
+
+ /**
+ * Gives back the signature policy identifier URI.
+ *
+ * @return
+ */
+ String getSignaturePolicyIdentifier();
+
+ /**
+ * Gives back the short description of the signature policy or
+ * <code>null</code> if a description is not available.
+ *
+ * @return the description, or <code>null</code>.
+ */
+ String getSignaturePolicyDescription();
+
+ /**
+ * Gives back the download URL where the signature policy document can be
+ * found. Can be <code>null</code> in case such a download location does not
+ * exist.
+ *
+ * @return the download URL, or <code>null</code>.
+ */
+ String getSignaturePolicyDownloadUrl();
+
+ /**
+ * Gives back the signature policy document.
+ *
+ * @return the bytes of the signature policy document.
+ */
+ byte[] getSignaturePolicyDocument();
+} \ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java
new file mode 100644
index 0000000000..9a197aa6ee
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java
@@ -0,0 +1,385 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import static org.apache.poi.poifs.crypt.dsig.SignatureInfo.setPrefix;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.Transform;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.spec.TransformParameterSpec;
+
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
+import org.apache.poi.poifs.crypt.dsig.spi.Constants;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xmlbeans.XmlString;
+import org.etsi.uri.x01903.v13.AnyType;
+import org.etsi.uri.x01903.v13.CertIDListType;
+import org.etsi.uri.x01903.v13.CertIDType;
+import org.etsi.uri.x01903.v13.ClaimedRolesListType;
+import org.etsi.uri.x01903.v13.DataObjectFormatType;
+import org.etsi.uri.x01903.v13.DigestAlgAndValueType;
+import org.etsi.uri.x01903.v13.IdentifierType;
+import org.etsi.uri.x01903.v13.ObjectIdentifierType;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesDocument;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesType;
+import org.etsi.uri.x01903.v13.SigPolicyQualifiersListType;
+import org.etsi.uri.x01903.v13.SignaturePolicyIdType;
+import org.etsi.uri.x01903.v13.SignaturePolicyIdentifierType;
+import org.etsi.uri.x01903.v13.SignedDataObjectPropertiesType;
+import org.etsi.uri.x01903.v13.SignedPropertiesType;
+import org.etsi.uri.x01903.v13.SignedSignaturePropertiesType;
+import org.etsi.uri.x01903.v13.SignerRoleType;
+import org.w3.x2000.x09.xmldsig.DigestMethodType;
+import org.w3.x2000.x09.xmldsig.SignatureType;
+import org.w3.x2000.x09.xmldsig.X509IssuerSerialType;
+import org.w3c.dom.Element;
+
+/**
+ * XAdES Signature Facet. Implements XAdES v1.4.1 which is compatible with XAdES
+ * v1.3.2. The implemented XAdES format is XAdES-BES/EPES. It's up to another
+ * part of the signature service to upgrade the XAdES-BES to a XAdES-X-L.
+ *
+ * This implementation has been tested against an implementation that
+ * participated multiple ETSI XAdES plugtests.
+ *
+ * @author Frank Cornelis
+ * @see http://en.wikipedia.org/wiki/XAdES
+ *
+ */
+public class XAdESSignatureFacet implements SignatureFacet {
+
+ private static final POILogger LOG = POILogFactory.getLogger(XAdESSignatureFacet.class);
+
+ private static final String XADES_TYPE = "http://uri.etsi.org/01903#SignedProperties";
+
+ private final Date clock;
+
+ private final HashAlgorithm hashAlgo;
+
+ private final SignaturePolicyService signaturePolicyService;
+
+ private String idSignedProperties;
+
+ private boolean signaturePolicyImplied;
+
+ private String role;
+
+ private boolean issuerNameNoReverseOrder = false;
+
+ private Map<String, String> dataObjectFormatMimeTypes;
+
+ /**
+ * Main constructor.
+ *
+ * @param clock
+ * the clock to be used for determining the xades:SigningTime,
+ * defaults to now when null
+ * @param hashAlgo
+ * the digest algorithm to be used for all required XAdES digest
+ * operations. Possible values: "SHA-1", "SHA-256", or "SHA-512",
+ * defaults to SHA-1 when null
+ * @param signaturePolicyService
+ * the optional signature policy service used for XAdES-EPES.
+ */
+ public XAdESSignatureFacet(Date clock, HashAlgorithm hashAlgo,
+ SignaturePolicyService signaturePolicyService) {
+ this.clock = (clock == null ? new Date() : clock);
+ this.hashAlgo = (hashAlgo == null ? HashAlgorithm.sha1 : hashAlgo);
+ this.signaturePolicyService = signaturePolicyService;
+ this.dataObjectFormatMimeTypes = new HashMap<String, String>();
+ }
+
+ public void postSign(SignatureType signatureElement,
+ List<X509Certificate> signingCertificateChain) {
+ LOG.log(POILogger.DEBUG, "postSign");
+ }
+
+ public void preSign(XMLSignatureFactory signatureFactory,
+ String signatureId,
+ List<X509Certificate> signingCertificateChain,
+ List<Reference> references, List<XMLObject> objects)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ LOG.log(POILogger.DEBUG, "preSign");
+
+ // QualifyingProperties
+ QualifyingPropertiesDocument qualDoc = QualifyingPropertiesDocument.Factory.newInstance();
+ QualifyingPropertiesType qualifyingProperties = qualDoc.addNewQualifyingProperties();
+ qualifyingProperties.setTarget("#" + signatureId);
+
+ // SignedProperties
+ SignedPropertiesType signedProperties = qualifyingProperties.addNewSignedProperties();
+ String signedPropertiesId;
+ if (null != this.idSignedProperties) {
+ signedPropertiesId = this.idSignedProperties;
+ } else {
+ signedPropertiesId = signatureId + "-xades";
+ }
+ signedProperties.setId(signedPropertiesId);
+
+ // SignedSignatureProperties
+ SignedSignaturePropertiesType signedSignatureProperties = signedProperties.addNewSignedSignatureProperties();
+
+ // SigningTime
+ Calendar xmlGregorianCalendar = Calendar.getInstance();
+ xmlGregorianCalendar.setTimeZone(TimeZone.getTimeZone("Z"));
+ xmlGregorianCalendar.setTime(this.clock);
+ xmlGregorianCalendar.clear(Calendar.MILLISECOND);
+ signedSignatureProperties.setSigningTime(xmlGregorianCalendar);
+
+ // SigningCertificate
+ if (null == signingCertificateChain
+ || signingCertificateChain.isEmpty()) {
+ throw new RuntimeException("no signing certificate chain available");
+ }
+ CertIDListType signingCertificates = signedSignatureProperties.addNewSigningCertificate();
+ CertIDType certId = signingCertificates.addNewCert();
+ X509Certificate signingCertificate = signingCertificateChain.get(0);
+ setCertID(certId, signingCertificate, this.hashAlgo, this.issuerNameNoReverseOrder);
+
+ // ClaimedRole
+ if (null != this.role && false == this.role.isEmpty()) {
+ SignerRoleType signerRole = signedSignatureProperties.addNewSignerRole();
+ signedSignatureProperties.setSignerRole(signerRole);
+ ClaimedRolesListType claimedRolesList = signerRole.addNewClaimedRoles();
+ AnyType claimedRole = claimedRolesList.addNewClaimedRole();
+ XmlString roleString = XmlString.Factory.newInstance();
+ roleString.setStringValue(this.role);
+ SignatureInfo.insertXChild(claimedRole, roleString);
+ }
+
+ // XAdES-EPES
+ if (null != this.signaturePolicyService) {
+ SignaturePolicyIdentifierType signaturePolicyIdentifier =
+ signedSignatureProperties.addNewSignaturePolicyIdentifier();
+
+ SignaturePolicyIdType signaturePolicyId = signaturePolicyIdentifier.addNewSignaturePolicyId();
+
+ ObjectIdentifierType objectIdentifier = signaturePolicyId.addNewSigPolicyId();
+ objectIdentifier.setDescription(this.signaturePolicyService.getSignaturePolicyDescription());
+
+ IdentifierType identifier = objectIdentifier.addNewIdentifier();
+ identifier.setStringValue(this.signaturePolicyService.getSignaturePolicyIdentifier());
+
+ byte[] signaturePolicyDocumentData = this.signaturePolicyService.getSignaturePolicyDocument();
+ DigestAlgAndValueType sigPolicyHash = signaturePolicyId.addNewSigPolicyHash();
+ setDigestAlgAndValue(sigPolicyHash, signaturePolicyDocumentData, this.hashAlgo);
+
+ String signaturePolicyDownloadUrl = this.signaturePolicyService
+ .getSignaturePolicyDownloadUrl();
+ if (null != signaturePolicyDownloadUrl) {
+ SigPolicyQualifiersListType sigPolicyQualifiers = signaturePolicyId.addNewSigPolicyQualifiers();
+ AnyType sigPolicyQualifier = sigPolicyQualifiers.addNewSigPolicyQualifier();
+ XmlString spUriElement = XmlString.Factory.newInstance();
+ spUriElement.setStringValue(signaturePolicyDownloadUrl);
+ SignatureInfo.insertXChild(sigPolicyQualifier, spUriElement);
+ }
+ } else if (this.signaturePolicyImplied) {
+ SignaturePolicyIdentifierType signaturePolicyIdentifier =
+ signedSignatureProperties.addNewSignaturePolicyIdentifier();
+ signaturePolicyIdentifier.addNewSignaturePolicyImplied();
+ }
+
+ // DataObjectFormat
+ if (false == this.dataObjectFormatMimeTypes.isEmpty()) {
+ SignedDataObjectPropertiesType signedDataObjectProperties =
+ signedProperties.addNewSignedDataObjectProperties();
+
+ List<DataObjectFormatType> dataObjectFormats = signedDataObjectProperties
+ .getDataObjectFormatList();
+ for (Map.Entry<String, String> dataObjectFormatMimeType : this.dataObjectFormatMimeTypes
+ .entrySet()) {
+ DataObjectFormatType dataObjectFormat = DataObjectFormatType.Factory.newInstance();
+ dataObjectFormat.setObjectReference("#" + dataObjectFormatMimeType.getKey());
+ dataObjectFormat.setMimeType(dataObjectFormatMimeType.getValue());
+ dataObjectFormats.add(dataObjectFormat);
+ }
+ }
+
+ // marshall XAdES QualifyingProperties
+ // ((Element)qualifyingProperties.getSignedProperties().getDomNode()).setIdAttribute("Id", true);
+
+ // add XAdES ds:Object
+ List<XMLStructure> xadesObjectContent = new LinkedList<XMLStructure>();
+ Element qualDocEl = (Element)qualifyingProperties.getDomNode();
+ qualDocEl.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:xd", "http://uri.etsi.org/01903/v1.3.2#");
+ setPrefix(qualifyingProperties, "http://uri.etsi.org/01903/v1.3.2#", "xd");
+ xadesObjectContent.add(new DOMStructure(qualDocEl));
+ XMLObject xadesObject = signatureFactory.newXMLObject(xadesObjectContent, null, null, null);
+ objects.add(xadesObject);
+
+ // add XAdES ds:Reference
+ DigestMethod digestMethod = signatureFactory.newDigestMethod(hashAlgo.xmlSignUri, null);
+ List<Transform> transforms = new LinkedList<Transform>();
+ Transform exclusiveTransform = signatureFactory
+ .newTransform(CanonicalizationMethod.INCLUSIVE,
+ (TransformParameterSpec) null);
+ transforms.add(exclusiveTransform);
+ Reference reference = signatureFactory.newReference("#"
+ + signedPropertiesId, digestMethod, transforms, XADES_TYPE,
+ null);
+ references.add(reference);
+ }
+
+ /**
+ * Gives back the JAXB DigestAlgAndValue data structure.
+ *
+ * @param data
+ * @param xadesObjectFactory
+ * @param xmldsigObjectFactory
+ * @param hashAlgo
+ * @return
+ */
+ protected static void setDigestAlgAndValue(
+ DigestAlgAndValueType digestAlgAndValue,
+ byte[] data,
+ HashAlgorithm hashAlgo) {
+ DigestMethodType digestMethod = digestAlgAndValue.addNewDigestMethod();
+ digestMethod.setAlgorithm(hashAlgo.xmlSignUri);
+
+ MessageDigest messageDigest = CryptoFunctions.getMessageDigest(hashAlgo);
+ byte[] digestValue = messageDigest.digest(data);
+ digestAlgAndValue.setDigestValue(digestValue);
+ }
+
+ /**
+ * Gives back the JAXB CertID data structure.
+ *
+ * @param certificate
+ * @param xadesObjectFactory
+ * @param xmldsigObjectFactory
+ * @param digestAlgorithm
+ * @return
+ */
+ protected static void setCertID(
+ CertIDType certId,
+ X509Certificate certificate,
+ HashAlgorithm digestAlgorithm, boolean issuerNameNoReverseOrder) {
+ X509IssuerSerialType issuerSerial = certId.addNewIssuerSerial();
+ String issuerName;
+ if (issuerNameNoReverseOrder) {
+ /*
+ * Make sure the DN is encoded using the same order as present
+ * within the certificate. This is an Office2010 work-around.
+ * Should be reverted back.
+ *
+ * XXX: not correct according to RFC 4514.
+ */
+ // TODO: check if issuerName is different on getTBSCertificate
+ // issuerName = PrincipalUtil.getIssuerX509Principal(certificate).getName().replace(",", ", ");
+ issuerName = certificate.getIssuerDN().getName().replace(",", ", ");
+ } else {
+ issuerName = certificate.getIssuerX500Principal().toString();
+ }
+ issuerSerial.setX509IssuerName(issuerName);
+ issuerSerial.setX509SerialNumber(certificate.getSerialNumber());
+
+ byte[] encodedCertificate;
+ try {
+ encodedCertificate = certificate.getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw new RuntimeException("certificate encoding error: "
+ + e.getMessage(), e);
+ }
+ DigestAlgAndValueType certDigest = certId.addNewCertDigest();
+ setDigestAlgAndValue(certDigest, encodedCertificate, digestAlgorithm);
+ }
+
+ /**
+ * Adds a mime-type for the given ds:Reference (referred via its @URI). This
+ * information is added via the xades:DataObjectFormat element.
+ *
+ * @param dsReferenceUri
+ * @param mimetype
+ */
+ public void addMimeType(String dsReferenceUri, String mimetype) {
+ this.dataObjectFormatMimeTypes.put(dsReferenceUri, mimetype);
+ }
+
+ /**
+ * Sets the Id that will be used on the SignedProperties element;
+ *
+ * @param idSignedProperties
+ */
+ public void setIdSignedProperties(String idSignedProperties) {
+ this.idSignedProperties = idSignedProperties;
+ }
+
+ /**
+ * Sets the signature policy to implied.
+ *
+ * @param signaturePolicyImplied
+ */
+ public void setSignaturePolicyImplied(boolean signaturePolicyImplied) {
+ this.signaturePolicyImplied = signaturePolicyImplied;
+ }
+
+ /**
+ * Sets the XAdES claimed role.
+ *
+ * @param role
+ */
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ /**
+ * Work-around for Office 2010 IssuerName encoding.
+ *
+ * @param reverseOrder
+ */
+ public void setIssuerNameNoReverseOrder(boolean reverseOrder) {
+ this.issuerNameNoReverseOrder = reverseOrder;
+ }
+
+
+ public Map<String,String> getNamespacePrefixMapping() {
+ Map<String,String> map = new HashMap<String,String>();
+ map.put("xd", "http://uri.etsi.org/01903/v1.3.2#");
+ return map;
+ }
+
+} \ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java
new file mode 100644
index 0000000000..ade2494f06
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java
@@ -0,0 +1,492 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.namespace.QName;
+
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxy;
+import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.ASN1InputStreamIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.ASN1OctetStringIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.BasicOCSPRespIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.CanonicalizerIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DERIntegerIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DERTaggedObjectIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.InitIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPRespIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.RespIDIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.ResponderIDIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509NameIf;
+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.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlObject;
+import org.etsi.uri.x01903.v13.CRLIdentifierType;
+import org.etsi.uri.x01903.v13.CRLRefType;
+import org.etsi.uri.x01903.v13.CRLRefsType;
+import org.etsi.uri.x01903.v13.CRLValuesType;
+import org.etsi.uri.x01903.v13.CertIDListType;
+import org.etsi.uri.x01903.v13.CertIDType;
+import org.etsi.uri.x01903.v13.CertificateValuesType;
+import org.etsi.uri.x01903.v13.CompleteCertificateRefsType;
+import org.etsi.uri.x01903.v13.CompleteRevocationRefsType;
+import org.etsi.uri.x01903.v13.DigestAlgAndValueType;
+import org.etsi.uri.x01903.v13.EncapsulatedPKIDataType;
+import org.etsi.uri.x01903.v13.OCSPIdentifierType;
+import org.etsi.uri.x01903.v13.OCSPRefType;
+import org.etsi.uri.x01903.v13.OCSPRefsType;
+import org.etsi.uri.x01903.v13.OCSPValuesType;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesType;
+import org.etsi.uri.x01903.v13.ResponderIDType;
+import org.etsi.uri.x01903.v13.RevocationValuesType;
+import org.etsi.uri.x01903.v13.UnsignedPropertiesType;
+import org.etsi.uri.x01903.v13.UnsignedSignaturePropertiesType;
+import org.etsi.uri.x01903.v13.XAdESTimeStampType;
+import org.etsi.uri.x01903.v14.ValidationDataType;
+import org.w3.x2000.x09.xmldsig.CanonicalizationMethodType;
+import org.w3.x2000.x09.xmldsig.ObjectType;
+import org.w3.x2000.x09.xmldsig.SignatureType;
+import org.w3.x2000.x09.xmldsig.SignatureValueType;
+import org.w3c.dom.Node;
+
+/**
+ * XAdES-X-L v1.4.1 signature facet. This signature facet implementation will
+ * upgrade a given XAdES-BES/EPES signature to XAdES-X-L.
+ *
+ * We don't inherit from XAdESSignatureFacet as we also want to be able to use
+ * this facet out of the context of a signature creation. This signature facet
+ * assumes that the signature is already XAdES-BES/EPES compliant.
+ *
+ * This implementation has been tested against an implementation that
+ * participated multiple ETSI XAdES plugtests.
+ *
+ * @author Frank Cornelis
+ * @see XAdESSignatureFacet
+ */
+public class XAdESXLSignatureFacet implements SignatureFacet {
+
+ private static final POILogger LOG = POILogFactory.getLogger(XAdESXLSignatureFacet.class);
+
+ public static final String XADES_NAMESPACE = "http://uri.etsi.org/01903/v1.3.2#";
+
+ public static final String XADES141_NAMESPACE = "http://uri.etsi.org/01903/v1.4.1#";
+
+ private final TimeStampService timeStampService;
+
+ private String c14nAlgoId;
+
+ private final RevocationDataService revocationDataService;
+
+ private final CertificateFactory certificateFactory;
+
+ private final HashAlgorithm hashAlgo;
+
+ static {
+ try {
+ HorribleProxy.createProxy(InitIf.class, "init");
+ } catch (Exception e) {
+ throw new RuntimeException("Can't initialize JDK xml signature classes - feature unsupported by the this JDK?!", e);
+ }
+ }
+
+ /**
+ * Convenience constructor.
+ *
+ * @param timeStampService
+ * the time-stamp service used for XAdES-T and XAdES-X.
+ * @param revocationDataService
+ * the optional revocation data service used for XAdES-C and
+ * XAdES-X-L. When <code>null</code> the signature will be
+ * limited to XAdES-T only.
+ */
+ public XAdESXLSignatureFacet(TimeStampService timeStampService,
+ RevocationDataService revocationDataService) {
+ this(timeStampService, revocationDataService, HashAlgorithm.sha1);
+ }
+
+ /**
+ * Main constructor.
+ *
+ * @param timeStampService
+ * the time-stamp service used for XAdES-T and XAdES-X.
+ * @param revocationDataService
+ * the optional revocation data service used for XAdES-C and
+ * XAdES-X-L. When <code>null</code> the signature will be
+ * limited to XAdES-T only.
+ * @param digestAlgorithm
+ * the digest algorithm to be used for construction of the
+ * XAdES-X-L elements.
+ */
+ public XAdESXLSignatureFacet(TimeStampService timeStampService,
+ RevocationDataService revocationDataService,
+ HashAlgorithm digestAlgorithm) {
+ this.c14nAlgoId = CanonicalizationMethod.EXCLUSIVE;
+ this.hashAlgo = digestAlgorithm;
+ this.timeStampService = timeStampService;
+ this.revocationDataService = revocationDataService;
+
+ try {
+ this.certificateFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ throw new RuntimeException("X509 JCA error: " + e.getMessage(), e);
+ }
+ }
+
+ public void setCanonicalizerAlgorithm(String c14nAlgoId) {
+ this.c14nAlgoId = c14nAlgoId;
+ }
+
+ public void postSign(SignatureType signatureElement,
+ List<X509Certificate> signingCertificateChain) {
+ LOG.log(POILogger.DEBUG, "XAdES-X-L post sign phase");
+
+ QualifyingPropertiesType qualProps = null;
+
+ try {
+ // check for XAdES-BES
+ for (ObjectType ot : signatureElement.getObjectList()) {
+ XmlObject xo[] = ot.selectChildren(new QName(XADES_NAMESPACE, "QualifyingProperties"));
+ if (xo != null && xo.length > 0) {
+ qualProps = QualifyingPropertiesType.Factory.parse(xo[0].getDomNode());
+ break;
+ }
+ }
+ } catch (XmlException e) {
+ throw new RuntimeException("signature decoding error", e);
+ }
+
+ if (qualProps == null) {
+ throw new IllegalArgumentException("no XAdES-BES extension present");
+ }
+
+ // create basic XML container structure
+ UnsignedPropertiesType unsignedProps = qualProps.getUnsignedProperties();
+ if (unsignedProps == null) {
+ unsignedProps = qualProps.addNewUnsignedProperties();
+ }
+ UnsignedSignaturePropertiesType unsignedSigProps = unsignedProps.getUnsignedSignatureProperties();
+ if (unsignedSigProps == null) {
+ unsignedSigProps = unsignedProps.addNewUnsignedSignatureProperties();
+ }
+
+
+ // create the XAdES-T time-stamp
+ SignatureValueType svt = signatureElement.getSignatureValue();
+
+ RevocationData tsaRevocationDataXadesT = new RevocationData();
+ LOG.log(POILogger.DEBUG, "creating XAdES-T time-stamp");
+ XAdESTimeStampType signatureTimeStamp = createXAdESTimeStamp(
+ Collections.singletonList(svt.getDomNode()),
+ tsaRevocationDataXadesT, this.c14nAlgoId,
+ this.timeStampService);
+
+ // marshal the XAdES-T extension
+ unsignedSigProps.addNewSignatureTimeStamp().set(signatureTimeStamp);
+
+ // xadesv141::TimeStampValidationData
+ if (tsaRevocationDataXadesT.hasRevocationDataEntries()) {
+ ValidationDataType validationData = createValidationData(tsaRevocationDataXadesT);
+ SignatureInfo.insertXChild(unsignedSigProps, validationData);
+ }
+
+ if (null == this.revocationDataService) {
+ /*
+ * Without revocation data service we cannot construct the XAdES-C
+ * extension.
+ */
+ return;
+ }
+
+ // XAdES-C: complete certificate refs
+ CompleteCertificateRefsType completeCertificateRefs =
+ unsignedSigProps.addNewCompleteCertificateRefs();
+
+ CertIDListType certIdList = completeCertificateRefs.addNewCertRefs();
+ for (int certIdx = 1; certIdx < signingCertificateChain.size(); certIdx++) {
+ /*
+ * We skip the signing certificate itself according to section
+ * 4.4.3.2 of the XAdES 1.4.1 specification.
+ */
+ X509Certificate certificate = signingCertificateChain.get(certIdx);
+ CertIDType certId = certIdList.addNewCert();
+ XAdESSignatureFacet.setCertID(certId, certificate, this.hashAlgo, false);
+ }
+
+ // XAdES-C: complete revocation refs
+ CompleteRevocationRefsType completeRevocationRefs =
+ unsignedSigProps.addNewCompleteRevocationRefs();
+ RevocationData revocationData = this.revocationDataService
+ .getRevocationData(signingCertificateChain);
+ if (revocationData.hasCRLs()) {
+ CRLRefsType crlRefs = completeRevocationRefs.addNewCRLRefs();
+ completeRevocationRefs.setCRLRefs(crlRefs);
+
+ for (byte[] encodedCrl : revocationData.getCRLs()) {
+ CRLRefType crlRef = crlRefs.addNewCRLRef();
+ X509CRL crl;
+ try {
+ crl = (X509CRL) this.certificateFactory
+ .generateCRL(new ByteArrayInputStream(encodedCrl));
+ } catch (CRLException e) {
+ throw new RuntimeException("CRL parse error: "
+ + e.getMessage(), e);
+ }
+
+ CRLIdentifierType crlIdentifier = crlRef.addNewCRLIdentifier();
+ String issuerName = crl.getIssuerDN().getName().replace(",", ", ");
+ crlIdentifier.setIssuer(issuerName);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(crl.getThisUpdate());
+ crlIdentifier.setIssueTime(cal);
+ crlIdentifier.setNumber(getCrlNumber(crl));
+
+ DigestAlgAndValueType digestAlgAndValue = crlRef.addNewDigestAlgAndValue();
+ XAdESSignatureFacet.setDigestAlgAndValue(digestAlgAndValue, encodedCrl, this.hashAlgo);
+ }
+ }
+ if (revocationData.hasOCSPs()) {
+ OCSPRefsType ocspRefs = completeRevocationRefs.addNewOCSPRefs();
+ for (byte[] ocsp : revocationData.getOCSPs()) {
+ try {
+ OCSPRefType ocspRef = ocspRefs.addNewOCSPRef();
+
+ DigestAlgAndValueType digestAlgAndValue = ocspRef.addNewDigestAlgAndValue();
+ XAdESSignatureFacet.setDigestAlgAndValue(digestAlgAndValue, ocsp, this.hashAlgo);
+
+ OCSPIdentifierType ocspIdentifier = ocspRef.addNewOCSPIdentifier();
+
+ OCSPRespIf ocspResp = HorribleProxy.newProxy(OCSPRespIf.class, ocsp);
+
+ BasicOCSPRespIf basicOcspResp = ocspResp.getResponseObject();
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(basicOcspResp.getProducedAt());
+ ocspIdentifier.setProducedAt(cal);
+
+ ResponderIDType responderId = ocspIdentifier.addNewResponderID();
+
+ RespIDIf respId = basicOcspResp.getResponderId();
+ ResponderIDIf ocspResponderId = respId.toASN1Object();
+ DERTaggedObjectIf derTaggedObject = ocspResponderId.toASN1Object();
+ if (2 == derTaggedObject.getTagNo()) {
+ ASN1OctetStringIf keyHashOctetString = derTaggedObject.getObject$String();
+ byte key[] = keyHashOctetString.getOctets();
+ responderId.setByKey(key);
+ } else {
+ X509NameIf name = HorribleProxy.createProxy(X509NameIf.class, "getInstance", derTaggedObject.getObject$Object());
+ String nameStr = name.toString$delegate();
+ responderId.setByName(nameStr);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("OCSP decoding error: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ // marshal XAdES-C
+
+ // XAdES-X Type 1 timestamp
+
+
+
+ List<Node> timeStampNodesXadesX1 = new LinkedList<Node>();
+ timeStampNodesXadesX1.add(signatureElement.getDomNode());
+ timeStampNodesXadesX1.add(signatureTimeStamp.getDomNode());
+ timeStampNodesXadesX1.add(completeCertificateRefs.getDomNode());
+ timeStampNodesXadesX1.add(completeRevocationRefs.getDomNode());
+
+ RevocationData tsaRevocationDataXadesX1 = new RevocationData();
+ LOG.log(POILogger.DEBUG, "creating XAdES-X time-stamp");
+ XAdESTimeStampType timeStampXadesX1 = createXAdESTimeStamp(
+ timeStampNodesXadesX1, tsaRevocationDataXadesX1,
+ this.c14nAlgoId, this.timeStampService);
+ if (tsaRevocationDataXadesX1.hasRevocationDataEntries()) {
+ ValidationDataType timeStampXadesX1ValidationData = createValidationData(tsaRevocationDataXadesX1);
+ SignatureInfo.insertXChild(unsignedSigProps, timeStampXadesX1ValidationData);
+ }
+
+ // marshal XAdES-X
+
+ // XAdES-X-L
+ CertificateValuesType certificateValues = unsignedSigProps.addNewCertificateValues();
+ for (X509Certificate certificate : signingCertificateChain) {
+ EncapsulatedPKIDataType encapsulatedPKIDataType = certificateValues.addNewEncapsulatedX509Certificate();
+ try {
+ encapsulatedPKIDataType.setByteArrayValue(certificate.getEncoded());
+ } catch (CertificateEncodingException e) {
+ throw new RuntimeException("certificate encoding error: " + e.getMessage(), e);
+ }
+ }
+
+ RevocationValuesType revocationValues = unsignedSigProps.addNewRevocationValues();
+ createRevocationValues(revocationValues, revocationData);
+
+ // marshal XAdES-X-L
+ }
+
+ public static byte[] getC14nValue(List<Node> nodeList, String c14nAlgoId) {
+ byte[] c14nValue = null;
+ try {
+ for (Node node : nodeList) {
+ /*
+ * Re-initialize the c14n else the namespaces will get cached
+ * and will be missing from the c14n resulting nodes.
+ */
+ CanonicalizerIf c14n = HorribleProxy.createProxy(CanonicalizerIf.class, "newInstance", c14nAlgoId);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ bos.write(c14nValue);
+ bos.write(c14n.canonicalizeSubtree(node));
+ c14nValue = bos.toByteArray();
+ }
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException("c14n error: " + e.getMessage(), e);
+ }
+ return c14nValue;
+ }
+
+ public void preSign(XMLSignatureFactory signatureFactory,
+ String signatureId,
+ List<X509Certificate> signingCertificateChain,
+ List<Reference> references, List<XMLObject> objects)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ // nothing to do here
+ }
+
+ private BigInteger getCrlNumber(X509CRL crl) {
+ byte[] crlNumberExtensionValue = crl.getExtensionValue("2.5.29.20" /*CRLNumber*/);
+ if (null == crlNumberExtensionValue) {
+ return null;
+ }
+ try {
+ ASN1InputStreamIf asn1InputStream = HorribleProxy.newProxy(ASN1InputStreamIf.class, crlNumberExtensionValue);
+ ASN1OctetStringIf octetString = asn1InputStream.readObject$ASNString();
+ byte[] octets = octetString.getOctets();
+ asn1InputStream = HorribleProxy.newProxy(ASN1InputStreamIf.class, octets);
+ DERIntegerIf integer = asn1InputStream.readObject$Integer();
+ BigInteger crlNumber = integer.getPositiveValue();
+ return crlNumber;
+ } catch (Exception e) {
+ throw new RuntimeException("I/O error: " + e.getMessage(), e);
+ }
+ }
+
+ public static XAdESTimeStampType createXAdESTimeStamp(
+ List<Node> nodeList,
+ RevocationData revocationData,
+ String c14nAlgoId,
+ TimeStampService timeStampService) {
+ byte[] c14nSignatureValueElement = getC14nValue(nodeList, c14nAlgoId);
+
+ return createXAdESTimeStamp(c14nSignatureValueElement, revocationData,
+ c14nAlgoId, timeStampService);
+ }
+
+ public static XAdESTimeStampType createXAdESTimeStamp(
+ byte[] data,
+ RevocationData revocationData,
+ String c14nAlgoId,
+ TimeStampService timeStampService) {
+ // create the time-stamp
+ byte[] timeStampToken;
+ try {
+ timeStampToken = timeStampService.timeStamp(data, revocationData);
+ } catch (Exception e) {
+ throw new RuntimeException("error while creating a time-stamp: "
+ + e.getMessage(), e);
+ }
+
+ // create a XAdES time-stamp container
+ XAdESTimeStampType xadesTimeStamp = XAdESTimeStampType.Factory.newInstance();
+ xadesTimeStamp.setId("time-stamp-" + UUID.randomUUID().toString());
+ CanonicalizationMethodType c14nMethod = xadesTimeStamp.addNewCanonicalizationMethod();
+ c14nMethod.setAlgorithm(c14nAlgoId);
+
+ // embed the time-stamp
+ EncapsulatedPKIDataType encapsulatedTimeStamp = xadesTimeStamp.addNewEncapsulatedTimeStamp();
+ encapsulatedTimeStamp.setByteArrayValue(timeStampToken);
+ encapsulatedTimeStamp.setId("time-stamp-token-" + UUID.randomUUID().toString());
+
+ return xadesTimeStamp;
+ }
+
+ private ValidationDataType createValidationData(
+ RevocationData revocationData) {
+ ValidationDataType validationData = ValidationDataType.Factory.newInstance();
+ RevocationValuesType revocationValues = validationData.addNewRevocationValues();
+ createRevocationValues(revocationValues, revocationData);
+ return validationData;
+ }
+
+ private void createRevocationValues(
+ RevocationValuesType revocationValues, RevocationData revocationData) {
+ if (revocationData.hasCRLs()) {
+ CRLValuesType crlValues = revocationValues.addNewCRLValues();
+ for (byte[] crl : revocationData.getCRLs()) {
+ EncapsulatedPKIDataType encapsulatedCrlValue = crlValues.addNewEncapsulatedCRLValue();
+ encapsulatedCrlValue.setByteArrayValue(crl);
+ }
+ }
+ if (revocationData.hasOCSPs()) {
+ OCSPValuesType ocspValues = revocationValues.addNewOCSPValues();
+ for (byte[] ocsp : revocationData.getOCSPs()) {
+ EncapsulatedPKIDataType encapsulatedOcspValue = ocspValues.addNewEncapsulatedOCSPValue();
+ encapsulatedOcspValue.setByteArrayValue(ocsp);
+ }
+ }
+ }
+
+ public Map<String,String> getNamespacePrefixMapping() {
+ return null;
+ }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java
new file mode 100644
index 0000000000..7769bb7910
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java
@@ -0,0 +1,249 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.xml.crypto.Data;
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.OctetStreamData;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.TransformException;
+import javax.xml.crypto.dsig.TransformService;
+import javax.xml.crypto.dsig.spec.TransformParameterSpec;
+
+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.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlObject;
+import org.apache.xmlbeans.XmlOptions;
+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;
+
+/**
+ * JSR105 implementation of the RelationshipTransform transformation.
+ *
+ * <p>
+ * Specs: http://openiso.org/Ecma/376/Part2/12.2.4#26
+ * </p>
+ */
+public class RelationshipTransformService extends TransformService {
+
+ public static final String TRANSFORM_URI = "http://schemas.openxmlformats.org/package/2006/RelationshipTransform";
+
+ private final List<String> sourceIds;
+
+ private static final POILogger LOG = POILogFactory.getLogger(RelationshipTransformService.class);
+
+ /**
+ * Relationship Transform parameter specification class.
+ */
+ public static class RelationshipTransformParameterSpec implements TransformParameterSpec {
+ List<String> sourceIds = new LinkedList<String>();
+ public void addRelationshipReference(String relationshipId) {
+ sourceIds.add(relationshipId);
+ }
+ public boolean hasSourceIds() {
+ return !sourceIds.isEmpty();
+ }
+ }
+
+
+ public RelationshipTransformService() {
+ super();
+ LOG.log(POILogger.DEBUG, "constructor");
+ this.sourceIds = new LinkedList<String>();
+ }
+
+ /**
+ * Register the provider for this TransformService
+ *
+ * @see javax.xml.crypto.dsig.TransformService
+ */
+ public static synchronized void registerDsigProvider() {
+ // the xml signature classes will try to find a special TransformerService,
+ // which is ofcourse unknown to JCE before ...
+ final String dsigProvider = "POIXmlDsigProvider";
+ if (Security.getProperty(dsigProvider) == null) {
+ Provider p = new Provider(dsigProvider, 1.0, dsigProvider){
+ static final long serialVersionUID = 1L;
+ };
+ p.put("TransformService." + TRANSFORM_URI, RelationshipTransformService.class.getName());
+ p.put("TransformService." + TRANSFORM_URI + " MechanismType", "DOM");
+ Security.addProvider(p);
+ }
+ }
+
+
+ @Override
+ public void init(TransformParameterSpec params) throws InvalidAlgorithmParameterException {
+ LOG.log(POILogger.DEBUG, "init(params)");
+ if (!(params instanceof RelationshipTransformParameterSpec)) {
+ throw new InvalidAlgorithmParameterException();
+ }
+ RelationshipTransformParameterSpec relParams = (RelationshipTransformParameterSpec) params;
+ for (String sourceId : relParams.sourceIds) {
+ this.sourceIds.add(sourceId);
+ }
+ }
+
+ @Override
+ public void init(XMLStructure parent, XMLCryptoContext context) throws InvalidAlgorithmParameterException {
+ LOG.log(POILogger.DEBUG, "init(parent,context)");
+ LOG.log(POILogger.DEBUG, "parent java type: " + parent.getClass().getName());
+ DOMStructure domParent = (DOMStructure) parent;
+ Node parentNode = domParent.getNode();
+
+ try {
+ TransformDocument transDoc = TransformDocument.Factory.parse(parentNode);
+ XmlObject xoList[] = transDoc.getTransform().selectChildren(RelationshipReferenceDocument.type.getDocumentElementName());
+ if (xoList.length == 0) {
+ LOG.log(POILogger.WARN, "no RelationshipReference/@SourceId parameters present");
+ }
+ for (XmlObject xo : xoList) {
+ RelationshipReferenceDocument refDoc =
+ RelationshipReferenceDocument.Factory.parse(xo.getDomNode());
+ String sourceId = refDoc.getRelationshipReference().getSourceId();
+ LOG.log(POILogger.DEBUG, "sourceId: ", sourceId);
+ this.sourceIds.add(sourceId);
+ }
+ } catch (XmlException e) {
+ throw new InvalidAlgorithmParameterException(e);
+ }
+ }
+
+ @Override
+ public void marshalParams(XMLStructure parent, XMLCryptoContext context) throws MarshalException {
+ LOG.log(POILogger.DEBUG, "marshallParams(parent,context)");
+ DOMStructure domParent = (DOMStructure) parent;
+ Element parentNode = (Element)domParent.getNode();
+ // parentNode.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:mdssi", DIGITAL_SIGNATURE);
+ Document doc = parentNode.getOwnerDocument();
+
+ for (String sourceId : this.sourceIds) {
+ RelationshipReferenceDocument relRef = RelationshipReferenceDocument.Factory.newInstance();
+ relRef.addNewRelationshipReference().setSourceId(sourceId);
+ Node n = relRef.getRelationshipReference().getDomNode();
+ // TODO: is there a more elegant way to do this?
+ n.setPrefix("mdssi");
+ n = doc.importNode(n, true);
+ parentNode.appendChild(n);
+ }
+ }
+
+ public AlgorithmParameterSpec getParameterSpec() {
+ LOG.log(POILogger.DEBUG, "getParameterSpec");
+ return null;
+ }
+
+ 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());
+ OctetStreamData octetStreamData = (OctetStreamData) data;
+ LOG.log(POILogger.DEBUG, "URI: " + octetStreamData.getURI());
+ InputStream octetStream = octetStreamData.getOctetStream();
+
+ RelationshipsDocument relDoc;
+ try {
+ relDoc = RelationshipsDocument.Factory.parse(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);
+ }
+ }
+ }
+
+ // 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);
+ }
+ }
+
+ public Data transform(Data data, XMLCryptoContext context, OutputStream os) throws TransformException {
+ LOG.log(POILogger.DEBUG, "transform(data,context,os)");
+ return null;
+ }
+
+ public boolean isFeatureSupported(String feature) {
+ LOG.log(POILogger.DEBUG, "isFeatureSupported(feature)");
+ return false;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationData.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationData.java
new file mode 100644
index 0000000000..31595c39e3
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationData.java
@@ -0,0 +1,131 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.security.cert.CRLException;
+import java.security.cert.X509CRL;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Container class for PKI revocation data.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class RevocationData {
+
+ private final List<byte[]> crls;
+
+ private final List<byte[]> ocsps;
+
+ /**
+ * Default constructor.
+ */
+ public RevocationData() {
+ this.crls = new LinkedList<byte[]>();
+ this.ocsps = new LinkedList<byte[]>();
+ }
+
+ /**
+ * Adds a CRL to this revocation data set.
+ *
+ * @param encodedCrl
+ */
+ public void addCRL(byte[] encodedCrl) {
+ this.crls.add(encodedCrl);
+ }
+
+ /**
+ * Adds a CRL to this revocation data set.
+ *
+ * @param crl
+ */
+ public void addCRL(X509CRL crl) {
+ byte[] encodedCrl;
+ try {
+ encodedCrl = crl.getEncoded();
+ } catch (CRLException e) {
+ throw new IllegalArgumentException("CRL coding error: "
+ + e.getMessage(), e);
+ }
+ addCRL(encodedCrl);
+ }
+
+ /**
+ * Adds an OCSP response to this revocation data set.
+ *
+ * @param encodedOcsp
+ */
+ public void addOCSP(byte[] encodedOcsp) {
+ this.ocsps.add(encodedOcsp);
+ }
+
+ /**
+ * Gives back a list of all CRLs.
+ *
+ * @return
+ */
+ public List<byte[]> getCRLs() {
+ return this.crls;
+ }
+
+ /**
+ * Gives back a list of all OCSP responses.
+ *
+ * @return
+ */
+ public List<byte[]> getOCSPs() {
+ return this.ocsps;
+ }
+
+ /**
+ * Returns <code>true</code> if this revocation data set holds OCSP
+ * responses.
+ *
+ * @return
+ */
+ public boolean hasOCSPs() {
+ return false == this.ocsps.isEmpty();
+ }
+
+ /**
+ * Returns <code>true</code> if this revocation data set holds CRLs.
+ *
+ * @return
+ */
+ public boolean hasCRLs() {
+ return false == this.crls.isEmpty();
+ }
+
+ /**
+ * Returns <code>true</code> if this revocation data is not empty.
+ *
+ * @return
+ */
+ public boolean hasRevocationDataEntries() {
+ return hasOCSPs() || hasCRLs();
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationDataService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationDataService.java
new file mode 100644
index 0000000000..b519c40e3d
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationDataService.java
@@ -0,0 +1,47 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Interface for a service that retrieves revocation data about some given
+ * certificate chain.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface RevocationDataService {
+
+ /**
+ * Gives back the revocation data corresponding with the given certificate
+ * chain.
+ *
+ * @param certificateChain
+ * @return
+ */
+ RevocationData getRevocationData(List<X509Certificate> certificateChain);
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignatureService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignatureService.java
new file mode 100644
index 0000000000..4057807634
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignatureService.java
@@ -0,0 +1,101 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.io.IOException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import org.apache.poi.poifs.crypt.dsig.CertificateSecurityException;
+import org.apache.poi.poifs.crypt.dsig.ExpiredCertificateSecurityException;
+import org.apache.poi.poifs.crypt.dsig.RevokedCertificateSecurityException;
+import org.apache.poi.poifs.crypt.dsig.TrustCertificateSecurityException;
+import org.apache.poi.poifs.crypt.dsig.spi.AddressDTO;
+import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo;
+import org.apache.poi.poifs.crypt.dsig.spi.IdentityDTO;
+
+/**
+ * Interface for signature service component.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface SignatureService {
+
+ /**
+ * Gives back the digest algorithm to be used for construction of the digest
+ * infos of the preSign method. Return a digest algorithm here if you want
+ * to let the client sign some locally stored files. Return
+ * <code>null</code> if no pre-sign digest infos are required.
+ *
+ * @return the digest algorithm to be used when digesting local files.
+ * @see #preSign(List, List)
+ */
+ String getFilesDigestAlgorithm();
+
+ /**
+ * Pre-sign callback method. Depending on the configuration some parameters
+ * are passed. The returned value will be signed by the eID Applet.
+ *
+ * <p>
+ * TODO: service must be able to throw some exception on failure.
+ * </p>
+ *
+ * @param digestInfos
+ * the optional list of digest infos.
+ * @param signingCertificateChain
+ * the optional list of certificates.
+ * @param identity
+ * the optional identity.
+ * @param address
+ * the optional identity address.
+ * @param photo
+ * the optional identity photo.
+ * @param timestamp
+ * the optional timestamp, defaults to now
+ * @return the digest to be signed.
+ * @throws NoSuchAlgorithmException
+ */
+ DigestInfo preSign(List<DigestInfo> digestInfos,
+ List<X509Certificate> signingCertificateChain,
+ IdentityDTO identity, AddressDTO address, byte[] photo)
+ throws NoSuchAlgorithmException;
+
+ /**
+ * Post-sign callback method. Received the signature value. Depending on the
+ * configuration the signing certificate chain is also obtained.
+ *
+ * @param signatureValue
+ * @param signingCertificateChain
+ * the optional chain of signing certificates.
+ */
+ void postSign(byte[] signatureValue,
+ List<X509Certificate> signingCertificateChain)
+ throws ExpiredCertificateSecurityException,
+ RevokedCertificateSecurityException,
+ TrustCertificateSecurityException, CertificateSecurityException,
+ SecurityException, IOException;
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TSPTimeStampService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TSPTimeStampService.java
new file mode 100644
index 0000000000..d0ba961eb9
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TSPTimeStampService.java
@@ -0,0 +1,392 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.security.auth.x500.X500Principal;
+import javax.xml.bind.DatatypeConverter;
+
+import org.apache.commons.codec.binary.Hex;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.ASN1InputStreamIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.ASN1OctetStringIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.AuthorityKeyIdentifierIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.BcDigestCalculatorProviderIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.BcRSASignerInfoVerifierBuilderIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DEROctetStringIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DefaultDigestAlgorithmIdentifierFinderIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.PKIFailureInfoIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.SignerIdIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.SignerInformationVerifierIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.SubjectKeyIdentifierIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.TimeStampRequestGeneratorIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.TimeStampRequestIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.TimeStampResponseIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.TimeStampTokenIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509CertificateHolderIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxy;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * A TSP time-stamp service implementation.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class TSPTimeStampService implements TimeStampService {
+
+ private static final POILogger LOG = POILogFactory.getLogger(TSPTimeStampService.class);
+
+ static {
+ CryptoFunctions.registerBouncyCastle();
+ }
+
+ public static final String DEFAULT_USER_AGENT = "eID Applet Service TSP Client";
+
+ private final String tspServiceUrl;
+
+ private String requestPolicy;
+
+ private final String userAgent;
+
+ private final TimeStampServiceValidator validator;
+
+ private String username;
+
+ private String password;
+
+ private String proxyHost;
+
+ private int proxyPort;
+
+ private String digestAlgo;
+
+ private String digestAlgoOid;
+
+ public TSPTimeStampService(String tspServiceUrl,
+ TimeStampServiceValidator validator) {
+ this(tspServiceUrl, validator, null, null);
+ }
+
+ /**
+ * Main constructor.
+ *
+ * @param tspServiceUrl
+ * the URL of the TSP service.
+ * @param validator
+ * the trust validator used to validate incoming TSP response
+ * signatures.
+ * @param requestPolicy
+ * the optional TSP request policy.
+ * @param userAgent
+ * the optional User-Agent TSP request header value.
+ */
+ public TSPTimeStampService(String tspServiceUrl,
+ TimeStampServiceValidator validator, String requestPolicy,
+ String userAgent) {
+ if (null == tspServiceUrl) {
+ throw new IllegalArgumentException("TSP service URL required");
+ }
+ this.tspServiceUrl = tspServiceUrl;
+
+ if (null == validator) {
+ throw new IllegalArgumentException("TSP validator required");
+ }
+ this.validator = validator;
+
+ this.requestPolicy = requestPolicy;
+
+ if (null != userAgent) {
+ this.userAgent = userAgent;
+ } else {
+ this.userAgent = DEFAULT_USER_AGENT;
+ }
+
+ this.digestAlgo = "SHA-1";
+ this.digestAlgoOid = "1.3.14.3.2.26";
+ }
+
+ /**
+ * Sets the request policy OID.
+ *
+ * @param policyOid
+ */
+ public void setRequestPolicy(String policyOid) {
+ this.requestPolicy = policyOid;
+ }
+
+ /**
+ * Sets the credentials used in case the TSP service requires
+ * authentication.
+ *
+ * @param username
+ * @param password
+ */
+ public void setAuthenticationCredentials(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+
+ /**
+ * Resets the authentication credentials.
+ */
+ public void resetAuthenticationCredentials() {
+ this.username = null;
+ this.password = null;
+ }
+
+ /**
+ * Sets the digest algorithm used for time-stamping data. Example value:
+ * "SHA-1".
+ *
+ * @param digestAlgo
+ */
+ public void setDigestAlgo(String digestAlgo) {
+ if ("SHA-1".equals(digestAlgo)) {
+ this.digestAlgoOid = "1.3.14.3.2.26";
+ } else if ("SHA-256".equals(digestAlgo)) {
+ this.digestAlgoOid = "2.16.840.1.101.3.4.2.1";
+ } else if ("SHA-384".equals(digestAlgo)) {
+ this.digestAlgoOid = "2.16.840.1.101.3.4.2.2";
+ } else if ("SHA-512".equals(digestAlgo)) {
+ this.digestAlgoOid = "2.16.840.1.101.3.4.2.3";
+ } else {
+ throw new IllegalArgumentException("unsupported digest algo: " + digestAlgo);
+ }
+
+ this.digestAlgo = digestAlgo;
+ }
+
+ /**
+ * Configures the HTTP proxy settings to be used to connect to the TSP
+ * service.
+ *
+ * @param proxyHost
+ * @param proxyPort
+ */
+ public void setProxy(String proxyHost, int proxyPort) {
+ this.proxyHost = proxyHost;
+ this.proxyPort = proxyPort;
+ }
+
+ /**
+ * Resets the HTTP proxy settings.
+ */
+ public void resetProxy() {
+ this.proxyHost = null;
+ this.proxyPort = 0;
+ }
+
+ public byte[] timeStamp(byte[] data, RevocationData revocationData)
+ throws Exception {
+ // digest the message
+ MessageDigest messageDigest = MessageDigest
+ .getInstance(this.digestAlgo);
+ byte[] digest = messageDigest.digest(data);
+
+ // generate the TSP request
+ BigInteger nonce = new BigInteger(128, new SecureRandom());
+ TimeStampRequestGeneratorIf requestGenerator = HorribleProxy.newProxy(TimeStampRequestGeneratorIf.class);
+ requestGenerator.setCertReq(true);
+ if (null != this.requestPolicy) {
+ requestGenerator.setReqPolicy(this.requestPolicy);
+ }
+ TimeStampRequestIf request = requestGenerator.generate(this.digestAlgoOid, digest, nonce);
+ byte[] encodedRequest = request.getEncoded();
+
+ // create the HTTP POST request
+ Proxy proxy = (this.proxyHost != null)
+ ? new Proxy(Proxy.Type.HTTP, new InetSocketAddress(this.proxyHost, this.proxyPort))
+ : Proxy.NO_PROXY;
+ HttpURLConnection huc = (HttpURLConnection)new URL(this.tspServiceUrl).openConnection(proxy);
+
+ if (null != this.username) {
+ String userPassword = this.username + ":" + this.password;
+ String encoding = DatatypeConverter.printBase64Binary(userPassword.getBytes(Charset.forName("iso-8859-1")));
+ huc.setRequestProperty("Authorization", "Basic " + encoding);
+ }
+
+ huc.setDoOutput(true); // also sets method to POST.
+ huc.setRequestProperty("User-Agent", this.userAgent);
+ huc.setRequestProperty("Content-Type", "application/timestamp-query;charset=ISO-8859-1");
+
+ OutputStream hucOut = huc.getOutputStream();
+ hucOut.write(encodedRequest);
+
+ // invoke TSP service
+ huc.connect();
+
+ int statusCode = huc.getResponseCode();
+ if (statusCode != 200) {
+ LOG.log(POILogger.ERROR, "Error contacting TSP server ", this.tspServiceUrl);
+ throw new Exception("Error contacting TSP server " + this.tspServiceUrl);
+ }
+
+ // HTTP input validation
+ String contentType = huc.getHeaderField("Content-Type");
+ if (null == contentType) {
+ throw new RuntimeException("missing Content-Type header");
+ }
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ IOUtils.copy(huc.getInputStream(), bos);
+ LOG.log(POILogger.DEBUG, "response content: ", bos.toString());
+
+ if (!contentType.startsWith("application/timestamp-reply")) {
+ throw new RuntimeException("invalid Content-Type: " + contentType);
+ }
+
+ if (bos.size() == 0) {
+ throw new RuntimeException("Content-Length is zero");
+ }
+
+ // TSP response parsing and validation
+ TimeStampResponseIf timeStampResponse = HorribleProxy.newProxy(TimeStampResponseIf.class, bos.toByteArray());
+ timeStampResponse.validate(request);
+
+ if (0 != timeStampResponse.getStatus()) {
+ LOG.log(POILogger.DEBUG, "status: " + timeStampResponse.getStatus());
+ LOG.log(POILogger.DEBUG, "status string: " + timeStampResponse.getStatusString());
+ PKIFailureInfoIf failInfo = timeStampResponse.getFailInfo();
+ if (null != failInfo) {
+ LOG.log(POILogger.DEBUG, "fail info int value: " + failInfo.intValue());
+ if (/*PKIFailureInfo.unacceptedPolicy*/(1 << 8) == failInfo.intValue()) {
+ LOG.log(POILogger.DEBUG, "unaccepted policy");
+ }
+ }
+ throw new RuntimeException("timestamp response status != 0: "
+ + timeStampResponse.getStatus());
+ }
+ TimeStampTokenIf timeStampToken = timeStampResponse.getTimeStampToken();
+ SignerIdIf signerId = timeStampToken.getSID();
+ BigInteger signerCertSerialNumber = signerId.getSerialNumber();
+ X500Principal signerCertIssuer = signerId.getIssuer();
+ LOG.log(POILogger.DEBUG, "signer cert serial number: " + signerCertSerialNumber);
+ LOG.log(POILogger.DEBUG, "signer cert issuer: " + signerCertIssuer);
+
+ // TSP signer certificates retrieval
+ Collection<Certificate> certificates = timeStampToken.getCertificates().getMatches(null);
+
+ X509Certificate signerCert = null;
+ Map<String, X509Certificate> certificateMap = new HashMap<String, X509Certificate>();
+ for (Certificate certificate : certificates) {
+ X509Certificate x509Certificate = (X509Certificate) certificate;
+ if (signerCertIssuer.equals(x509Certificate
+ .getIssuerX500Principal())
+ && signerCertSerialNumber.equals(x509Certificate
+ .getSerialNumber())) {
+ signerCert = x509Certificate;
+ }
+ String ski = Hex.encodeHexString(getSubjectKeyId(x509Certificate));
+ certificateMap.put(ski, x509Certificate);
+ LOG.log(POILogger.DEBUG, "embedded certificate: "
+ + x509Certificate.getSubjectX500Principal() + "; SKI="
+ + ski);
+ }
+
+ // TSP signer cert path building
+ if (null == signerCert) {
+ throw new RuntimeException(
+ "TSP response token has no signer certificate");
+ }
+ List<X509Certificate> tspCertificateChain = new LinkedList<X509Certificate>();
+ X509Certificate certificate = signerCert;
+ do {
+ LOG.log(POILogger.DEBUG, "adding to certificate chain: "
+ + certificate.getSubjectX500Principal());
+ tspCertificateChain.add(certificate);
+ if (certificate.getSubjectX500Principal().equals(
+ certificate.getIssuerX500Principal())) {
+ break;
+ }
+ String aki = Hex.encodeHexString(getAuthorityKeyId(certificate));
+ certificate = certificateMap.get(aki);
+ } while (null != certificate);
+
+ // verify TSP signer signature
+ X509CertificateHolderIf holder = HorribleProxy.newProxy(X509CertificateHolderIf.class, tspCertificateChain.get(0).getEncoded());
+ DefaultDigestAlgorithmIdentifierFinderIf finder = HorribleProxy.newProxy(DefaultDigestAlgorithmIdentifierFinderIf.class);
+ BcDigestCalculatorProviderIf calculator = HorribleProxy.newProxy(BcDigestCalculatorProviderIf.class);
+ BcRSASignerInfoVerifierBuilderIf verifierBuilder = HorribleProxy.newProxy(BcRSASignerInfoVerifierBuilderIf.class, finder, calculator);
+ SignerInformationVerifierIf verifier = verifierBuilder.build(holder);
+
+ timeStampToken.validate(verifier);
+
+ // verify TSP signer certificate
+ this.validator.validate(tspCertificateChain, revocationData);
+
+ LOG.log(POILogger.DEBUG, "time-stamp token time: "
+ + timeStampToken.getTimeStampInfo().getGenTime());
+
+ byte[] timestamp = timeStampToken.getEncoded();
+ return timestamp;
+ }
+
+ private byte[] getSubjectKeyId(X509Certificate cert) throws Exception {
+ // X509Extensions.SubjectKeyIdentifier.getId()
+ byte[] extvalue = cert.getExtensionValue("2.5.29.14");
+ if (extvalue == null) return null;
+
+ ASN1InputStreamIf keyCntStream = HorribleProxy.newProxy(ASN1InputStreamIf.class, new ByteArrayInputStream(extvalue));
+ ASN1OctetStringIf cntStr = HorribleProxy.createProxy(ASN1OctetStringIf.class, "getInstance", keyCntStream.readObject$Object());
+ ASN1InputStreamIf keyIdStream = HorribleProxy.newProxy(ASN1InputStreamIf.class, new ByteArrayInputStream(cntStr.getOctets()));
+ SubjectKeyIdentifierIf keyId = HorribleProxy.createProxy(SubjectKeyIdentifierIf.class, "getInstance", keyIdStream.readObject$Object());
+
+ return keyId.getKeyIdentifier();
+ }
+
+ private byte[] getAuthorityKeyId(X509Certificate cert) throws Exception {
+ // X509Extensions.AuthorityKeyIdentifier.getId()
+ byte[] extvalue = cert.getExtensionValue("2.5.29.35");
+ if (extvalue == null) return null;
+
+ ASN1InputStreamIf keyCntStream = HorribleProxy.newProxy(ASN1InputStreamIf.class, new ByteArrayInputStream(extvalue));
+ DEROctetStringIf cntStr = keyCntStream.readObject$DERString();
+ ASN1InputStreamIf keyIdStream = HorribleProxy.newProxy(ASN1InputStreamIf.class, new ByteArrayInputStream(cntStr.getOctets()));
+ AuthorityKeyIdentifierIf keyId = HorribleProxy.newProxy(AuthorityKeyIdentifierIf.class, keyIdStream.readObject$Sequence());
+
+ return keyId.getKeyIdentifier();
+ }
+} \ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampService.java
new file mode 100644
index 0000000000..dd9474e65e
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampService.java
@@ -0,0 +1,52 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+
+/**
+ * Interface for a time-stamp service.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface TimeStampService {
+
+ /**
+ * Gives back the encoded time-stamp token for the given array of data
+ * bytes. We assume that the time-stamp token itself contains its full
+ * certificate chain required for proper validation.
+ *
+ * @param data
+ * the data to be time-stamped.
+ * @param revocationData
+ * the optional container that needs to be filled up with the
+ * revocation data used to validate the TSA certificate chain.
+ * @return the DER encoded time-stamp token.
+ * @throws Exception
+ * in case something went wrong.
+ */
+ byte[] timeStamp(byte[] data, RevocationData revocationData)
+ throws Exception;
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampServiceValidator.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampServiceValidator.java
new file mode 100644
index 0000000000..4d36be9ec5
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampServiceValidator.java
@@ -0,0 +1,51 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Interface for trust validator of a TSP.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface TimeStampServiceValidator {
+
+ /**
+ * Validates the given certificate chain.
+ *
+ * @param certificateChain
+ * @param revocationData
+ * the optional data container that should be filled with
+ * revocation data that was used to validate the given
+ * certificate chain.
+ * @throws Exception
+ * in case the certificate chain is invalid.
+ */
+ void validate(List<X509Certificate> certificateChain,
+ RevocationData revocationData) throws Exception;
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java
new file mode 100644
index 0000000000..c09501a4a1
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java
@@ -0,0 +1,610 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.URIDereferencer;
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dom.DOMCryptoContext;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.Manifest;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.SignatureMethod;
+import javax.xml.crypto.dsig.SignedInfo;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignContext;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.dom.DOMSignContext;
+import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackageNamespaces;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackagePartName;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
+import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
+import org.apache.poi.openxml4j.opc.TargetMode;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DOMReferenceIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DOMSignedInfoIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DOMXMLSignatureIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.XMLSignatureIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxy;
+import org.apache.poi.poifs.crypt.dsig.OOXMLURIDereferencer;
+import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
+import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.OOXMLSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.Office2010SignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.spi.AddressDTO;
+import org.apache.poi.poifs.crypt.dsig.spi.Constants;
+import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo;
+import org.apache.poi.poifs.crypt.dsig.spi.IdentityDTO;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlOptions;
+import org.w3.x2000.x09.xmldsig.SignatureDocument;
+import org.w3.x2000.x09.xmldsig.SignatureType;
+import org.w3.x2000.x09.xmldsig.SignatureValueType;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+
+/**
+ * Abstract base class for an XML Signature Service implementation.
+ */
+public class XmlSignatureService implements SignatureService {
+ private static final POILogger LOG = POILogFactory.getLogger(XmlSignatureService.class);
+
+ protected final List<SignatureFacet> signatureFacets;
+
+ private String signatureNamespacePrefix;
+ private String signatureId;
+ private final HashAlgorithm hashAlgo;
+ private final OPCPackage opcPackage;
+ private SignatureDocument sigDoc;
+ private XAdESSignatureFacet xadesSignatureFacet;
+
+ /**
+ * Main constructor.
+ */
+ public XmlSignatureService(HashAlgorithm digestAlgo, OPCPackage opcPackage) {
+ this.signatureFacets = new LinkedList<SignatureFacet>();
+ this.signatureNamespacePrefix = null;
+ this.signatureId = null;
+ this.hashAlgo = digestAlgo;
+ this.opcPackage = opcPackage;
+ this.sigDoc = null;
+ }
+
+ public void initFacets(Date clock) {
+ if (clock == null) clock = new Date();
+ addSignatureFacet(new OOXMLSignatureFacet(this, clock, hashAlgo));
+ addSignatureFacet(new KeyInfoSignatureFacet(true, false, false));
+
+ this.xadesSignatureFacet = new XAdESSignatureFacet(clock, hashAlgo, null);
+ this.xadesSignatureFacet.setIdSignedProperties("idSignedProperties");
+ this.xadesSignatureFacet.setSignaturePolicyImplied(true);
+ /*
+ * Work-around for Office 2010.
+ */
+ this.xadesSignatureFacet.setIssuerNameNoReverseOrder(true);
+ setSignatureId("idPackageSignature");
+ addSignatureFacet(this.xadesSignatureFacet);
+ addSignatureFacet(new Office2010SignatureFacet());
+ }
+
+
+ /**
+ * Sets the signature Id attribute value used to create the XML signature. A
+ * <code>null</code> value will trigger an automatically generated signature
+ * Id.
+ *
+ * @param signatureId
+ */
+ protected void setSignatureId(String signatureId) {
+ this.signatureId = signatureId;
+ }
+
+ /**
+ * Sets the XML Signature namespace prefix to be used for signature
+ * creation. A <code>null</code> value will omit the prefixing.
+ *
+ * @param signatureNamespacePrefix
+ */
+ protected void setSignatureNamespacePrefix(String signatureNamespacePrefix) {
+ this.signatureNamespacePrefix = signatureNamespacePrefix;
+ }
+
+ /**
+ * Adds a signature facet to this XML signature service.
+ *
+ * @param signatureFacet
+ */
+ protected void addSignatureFacet(SignatureFacet signatureFacet) {
+ this.signatureFacets.add(signatureFacet);
+ }
+
+ /**
+ * Gives back the signature digest algorithm. Allowed values are SHA-1,
+ * SHA-256, SHA-384, SHA-512, RIPEND160. The default algorithm is SHA-1.
+ * Override this method to select another signature digest algorithm.
+ *
+ * @return
+ */
+ protected HashAlgorithm getSignatureDigestAlgorithm() {
+ return null != this.hashAlgo ? this.hashAlgo : HashAlgorithm.sha1;
+ }
+
+ /**
+ * Override this method to change the URI dereferener used by the signing
+ * engine.
+ *
+ * @return
+ */
+ protected URIDereferencer getURIDereferencer() {
+ OPCPackage ooxmlDocument = getOfficeOpenXMLDocument();
+ return new OOXMLURIDereferencer(ooxmlDocument);
+ }
+
+ /**
+ * Gives back the human-readable description of what the citizen will be
+ * signing. The default value is "XML Document". Override this method to
+ * provide the citizen with another description.
+ *
+ * @return
+ */
+ protected String getSignatureDescription() {
+ return "Office OpenXML Document";
+ }
+
+ /**
+ * Gives back the URL of the OOXML to be signed.
+ *
+ * @return
+ */
+ public OPCPackage getOfficeOpenXMLDocument() {
+ return opcPackage;
+ }
+
+
+
+ /**
+ * Gives back the output stream to which to write the signed XML document.
+ *
+ * @return
+ */
+ // protected abstract OutputStream getSignedDocumentOutputStream();
+
+ public DigestInfo preSign(List<DigestInfo> digestInfos,
+ List<X509Certificate> signingCertificateChain,
+ IdentityDTO identity, AddressDTO address, byte[] photo)
+ throws NoSuchAlgorithmException {
+ SignatureInfo.initXmlProvider();
+
+ LOG.log(POILogger.DEBUG, "preSign");
+ HashAlgorithm hashAlgo = getSignatureDigestAlgorithm();
+
+ byte[] digestValue;
+ try {
+ digestValue = getXmlSignatureDigestValue(hashAlgo, digestInfos, signingCertificateChain);
+ } catch (Exception e) {
+ throw new RuntimeException("XML signature error: " + e.getMessage(), e);
+ }
+
+ String description = getSignatureDescription();
+ return new DigestInfo(digestValue, hashAlgo, description);
+ }
+
+ public void postSign(byte[] signatureValue, List<X509Certificate> signingCertificateChain)
+ throws IOException {
+ LOG.log(POILogger.DEBUG, "postSign");
+ SignatureInfo.initXmlProvider();
+
+ /*
+ * Retrieve the intermediate XML signature document from the temporary
+ * data storage.
+ */
+ SignatureType sigType = sigDoc.getSignature();
+
+ /*
+ * Check ds:Signature node.
+ */
+ if (!signatureId.equals(sigType.getId())) {
+ throw new RuntimeException("ds:Signature not found for @Id: " + signatureId);
+ }
+
+ /*
+ * Insert signature value into the ds:SignatureValue element
+ */
+ SignatureValueType sigVal = sigType.getSignatureValue();
+ sigVal.setByteArrayValue(signatureValue);
+
+ /*
+ * Allow signature facets to inject their own stuff.
+ */
+ for (SignatureFacet signatureFacet : this.signatureFacets) {
+ signatureFacet.postSign(sigType, signingCertificateChain);
+ }
+
+ writeDocument();
+ }
+
+ @SuppressWarnings("unchecked")
+ private byte[] getXmlSignatureDigestValue(HashAlgorithm hashAlgo,
+ List<DigestInfo> digestInfos,
+ List<X509Certificate> signingCertificateChain)
+ throws ParserConfigurationException, NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException, MarshalException,
+ javax.xml.crypto.dsig.XMLSignatureException,
+ TransformerFactoryConfigurationError, TransformerException,
+ IOException, SAXException, NoSuchProviderException, XmlException {
+ /*
+ * DOM Document construction.
+ */
+ DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+ dbf.setNamespaceAware(true);
+ Document doc = dbf.newDocumentBuilder().newDocument();
+
+ /*
+ * Signature context construction.
+ */
+ Key key = new Key() {
+ private static final long serialVersionUID = 1L;
+
+ public String getAlgorithm() {
+ return null;
+ }
+
+ public byte[] getEncoded() {
+ return null;
+ }
+
+ public String getFormat() {
+ return null;
+ }
+ };
+
+ // As of JDK 7, can't use sigDoc here directly, because the
+ // setAttributeID will be called and it's not implemented in xmlbeans
+ XMLSignContext xmlSignContext = new DOMSignContext(key, doc);
+ URIDereferencer uriDereferencer = getURIDereferencer();
+ if (null != uriDereferencer) {
+ xmlSignContext.setURIDereferencer(uriDereferencer);
+ }
+
+ xmlSignContext.putNamespacePrefix(
+ "http://schemas.openxmlformats.org/package/2006/digital-signature",
+ "mdssi");
+
+ if (this.signatureNamespacePrefix != null) {
+ /*
+ * OOo doesn't like ds namespaces so per default prefixing is off.
+ */
+ xmlSignContext.putNamespacePrefix(
+ javax.xml.crypto.dsig.XMLSignature.XMLNS,
+ this.signatureNamespacePrefix);
+ }
+
+ XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM", "XMLDSig");
+
+ /*
+ * Add ds:References that come from signing client local files.
+ */
+ List<Reference> references = new LinkedList<Reference>();
+ addDigestInfosAsReferences(digestInfos, signatureFactory, references);
+
+ /*
+ * Invoke the signature facets.
+ */
+ String localSignatureId;
+ if (null == this.signatureId) {
+ localSignatureId = "xmldsig-" + UUID.randomUUID().toString();
+ } else {
+ localSignatureId = this.signatureId;
+ }
+ List<XMLObject> objects = new LinkedList<XMLObject>();
+ for (SignatureFacet signatureFacet : this.signatureFacets) {
+ LOG.log(POILogger.DEBUG, "invoking signature facet: "
+ + signatureFacet.getClass().getSimpleName());
+ signatureFacet.preSign(signatureFactory, localSignatureId, signingCertificateChain, references, objects);
+ }
+
+ /*
+ * ds:SignedInfo
+ */
+ SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(
+ getSignatureMethod(hashAlgo), null);
+ CanonicalizationMethod canonicalizationMethod = signatureFactory
+ .newCanonicalizationMethod(getCanonicalizationMethod(),
+ (C14NMethodParameterSpec) null);
+ SignedInfo signedInfo = signatureFactory.newSignedInfo(
+ canonicalizationMethod, signatureMethod, references);
+
+ /*
+ * JSR105 ds:Signature creation
+ */
+ String signatureValueId = localSignatureId + "-signature-value";
+ javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory
+ .newXMLSignature(signedInfo, null, objects, localSignatureId,
+ signatureValueId);
+
+ /*
+ * ds:Signature Marshalling.
+ */
+ DOMXMLSignatureIf domXmlSignature;
+ try {
+ domXmlSignature = HorribleProxy.newProxy(DOMXMLSignatureIf.class, xmlSignature);
+ } catch (Exception e) {
+ throw new RuntimeException("DomXmlSignature instance error: " + e.getMessage(), e);
+ }
+
+ domXmlSignature.marshal(doc, this.signatureNamespacePrefix, (DOMCryptoContext) xmlSignContext);
+
+ registerIds(doc);
+ Element el = doc.getElementById("idPackageObject");
+ assert (el != null);
+ el.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:mdssi", PackageNamespaces.DIGITAL_SIGNATURE);
+
+
+ /*
+ * Completion of undigested ds:References in the ds:Manifests.
+ */
+ for (XMLObject object : objects) {
+ LOG.log(POILogger.DEBUG, "object java type: " + object.getClass().getName());
+ List<XMLStructure> objectContentList = object.getContent();
+ for (XMLStructure objectContent : objectContentList) {
+ LOG.log(POILogger.DEBUG, "object content java type: " + objectContent.getClass().getName());
+ if (!(objectContent instanceof Manifest)) continue;
+ Manifest manifest = (Manifest) objectContent;
+ List<Reference> manifestReferences = manifest.getReferences();
+ for (Reference manifestReference : manifestReferences) {
+ if (manifestReference.getDigestValue() != null) continue;
+
+ DOMReferenceIf manifestDOMReference;
+ try {
+ manifestDOMReference = HorribleProxy.newProxy(DOMReferenceIf.class, manifestReference);
+ } catch (Exception e) {
+ throw new RuntimeException("DOMReference instance error: " + e.getMessage(), e);
+ }
+ manifestDOMReference.digest(xmlSignContext);
+ }
+ }
+ }
+
+ /*
+ * Completion of undigested ds:References.
+ */
+ List<Reference> signedInfoReferences = signedInfo.getReferences();
+ for (Reference signedInfoReference : signedInfoReferences) {
+ DOMReferenceIf domReference;
+ try {
+ domReference = HorribleProxy.newProxy(DOMReferenceIf.class, signedInfoReference);
+ } catch (Exception e) {
+ throw new RuntimeException("DOMReference instance error: " + e.getMessage(), e);
+ }
+
+ // ds:Reference with external digest value
+ if (domReference.getDigestValue() != null) continue;
+
+ domReference.digest(xmlSignContext);
+ }
+
+ /*
+ * Calculation of XML signature digest value.
+ */
+ DOMSignedInfoIf domSignedInfo;
+ try {
+ domSignedInfo = HorribleProxy.newProxy(DOMSignedInfoIf.class, signedInfo);
+ } catch (Exception e) {
+ throw new RuntimeException("DOMSignedInfo instance error: " + e.getMessage(), e);
+ }
+
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ domSignedInfo.canonicalize(xmlSignContext, dataStream);
+ byte[] octets = dataStream.toByteArray();
+
+ sigDoc = SignatureDocument.Factory.parse(doc.getDocumentElement());
+
+
+ /*
+ * TODO: we could be using DigestOutputStream here to optimize memory
+ * usage.
+ */
+
+ MessageDigest jcaMessageDigest = CryptoFunctions.getMessageDigest(hashAlgo);
+ byte[] digestValue = jcaMessageDigest.digest(octets);
+ return digestValue;
+ }
+
+ /**
+ * the resulting document needs to be tweaked before it can be digested -
+ * this applies to the verification and signing step
+ *
+ * @param doc
+ */
+ public void registerIds(Document doc) {
+ NodeList nl = doc.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Object");
+ registerIdAttribute(nl);
+ nl = doc.getElementsByTagNameNS("http://uri.etsi.org/01903/v1.3.2#", "SignedProperties");
+ registerIdAttribute(nl);
+ }
+
+ protected void registerIdAttribute(NodeList nl) {
+ for (int i=0; i<nl.getLength(); i++) {
+ Element el = (Element)nl.item(i);
+ if (el.hasAttribute("Id")) {
+ el.setIdAttribute("Id", true);
+ }
+ }
+ }
+
+ private void addDigestInfosAsReferences(List<DigestInfo> digestInfos,
+ XMLSignatureFactory signatureFactory, List<Reference> references)
+ throws NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException, MalformedURLException {
+ if (digestInfos == null) return;
+ for (DigestInfo digestInfo : digestInfos) {
+ byte[] documentDigestValue = digestInfo.digestValue;
+
+ DigestMethod digestMethod = signatureFactory.newDigestMethod(
+ digestInfo.hashAlgo.xmlSignUri, null);
+
+ String uri = new File(digestInfo.description).getName();
+
+ Reference reference = signatureFactory.newReference(uri,
+ digestMethod, null, null, null, documentDigestValue);
+ references.add(reference);
+ }
+ }
+
+ private String getSignatureMethod(HashAlgorithm hashAlgo) {
+ if (null == hashAlgo) {
+ throw new RuntimeException("digest algo is null");
+ }
+
+ XMLSignatureIf XmlSignature;
+ try {
+ XmlSignature = HorribleProxy.newProxy(XMLSignatureIf.class);
+ } catch (Exception e) {
+ throw new RuntimeException("JDK doesn't support XmlSignature", e);
+ }
+
+ switch (hashAlgo) {
+ case sha1: return XmlSignature.ALGO_ID_SIGNATURE_RSA_SHA1();
+ case sha256: return XmlSignature.ALGO_ID_SIGNATURE_RSA_SHA256();
+ case sha384: return XmlSignature.ALGO_ID_SIGNATURE_RSA_SHA384();
+ case sha512: return XmlSignature.ALGO_ID_SIGNATURE_RSA_SHA512();
+ case ripemd160: return XmlSignature.ALGO_ID_MAC_HMAC_RIPEMD160();
+ default: break;
+ }
+
+ throw new RuntimeException("unsupported sign algo: " + hashAlgo);
+ }
+
+ /**
+ * Gives back the used XAdES signature facet.
+ *
+ * @return
+ */
+ protected XAdESSignatureFacet getXAdESSignatureFacet() {
+ return this.xadesSignatureFacet;
+ }
+
+ public String getFilesDigestAlgorithm() {
+ return null;
+ }
+
+ protected String getCanonicalizationMethod() {
+ return CanonicalizationMethod.INCLUSIVE;
+ }
+
+ protected void writeDocument() throws IOException {
+ XmlOptions xo = new XmlOptions();
+ Map<String,String> namespaceMap = new HashMap<String,String>();
+ for (SignatureFacet sf : this.signatureFacets) {
+ Map<String,String> sfm = sf.getNamespacePrefixMapping();
+ if (sfm != null) {
+ namespaceMap.putAll(sfm);
+ }
+ }
+ xo.setSaveSuggestedPrefixes(namespaceMap);
+ xo.setUseDefaultNamespace();
+
+ LOG.log(POILogger.DEBUG, "output signed Office OpenXML document");
+
+ /*
+ * Copy the original OOXML content to the signed OOXML package. During
+ * copying some files need to changed.
+ */
+ OPCPackage pkg = this.getOfficeOpenXMLDocument();
+
+ PackagePartName sigPartName, sigsPartName;
+ try {
+ // <Override PartName="/_xmlsignatures/sig1.xml" ContentType="application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"/>
+ sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig1.xml");
+ // <Default Extension="sigs" ContentType="application/vnd.openxmlformats-package.digital-signature-origin"/>
+ sigsPartName = PackagingURIHelper.createPartName("/_xmlsignatures/origin.sigs");
+ } catch (InvalidFormatException e) {
+ throw new IOException(e);
+ }
+
+ String sigContentType = "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml";
+ PackagePart sigPart = pkg.getPart(sigPartName);
+ if (sigPart == null) {
+ sigPart = pkg.createPart(sigPartName, sigContentType);
+ }
+
+ OutputStream os = sigPart.getOutputStream();
+ sigDoc.save(os, xo);
+ os.close();
+
+ String sigsContentType = "application/vnd.openxmlformats-package.digital-signature-origin";
+ PackagePart sigsPart = pkg.getPart(sigsPartName);
+ if (sigsPart == null) {
+ // touch empty marker file
+ sigsPart = pkg.createPart(sigsPartName, sigsContentType);
+ }
+
+ PackageRelationshipCollection relCol = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
+ for (PackageRelationship pr : relCol) {
+ pkg.removeRelationship(pr.getId());
+ }
+ pkg.addRelationship(sigsPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
+
+ sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE);
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java
new file mode 100644
index 0000000000..a164046319
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java
@@ -0,0 +1,51 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+import java.io.Serializable;
+import java.security.Identity;
+
+/**
+ * Address Data Transfer Object.
+ *
+ * @author Frank Cornelis
+ * @see Identity
+ *
+ */
+public class AddressDTO implements Serializable {
+
+ /*
+ * We implement serializable to allow this class to be used in distributed
+ * containers as defined in the Servlet v2.4 specification.
+ */
+
+ private static final long serialVersionUID = 1L;
+
+ public String streetAndNumber;
+
+ public String zip;
+
+ public String city;
+} \ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/Constants.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/Constants.java
new file mode 100644
index 0000000000..7c2caeb633
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/Constants.java
@@ -0,0 +1,30 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+public interface Constants {
+ String NamespaceSpecNS = "http://www.w3.org/2000/xmlns/";
+ String SignatureSpecNS = "http://www.w3.org/2000/09/xmldsig#";
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java
new file mode 100644
index 0000000000..2f7c58c338
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java
@@ -0,0 +1,56 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+import java.io.Serializable;
+
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+
+/**
+ * Digest Information data transfer class.
+ */
+public class DigestInfo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Main constructor.
+ *
+ * @param digestValue
+ * @param hashAlgo
+ * @param description
+ */
+ public DigestInfo(byte[] digestValue, HashAlgorithm hashAlgo, String description) {
+ this.digestValue = digestValue;
+ this.hashAlgo = hashAlgo;
+ this.description = description;
+ }
+
+ public final byte[] digestValue;
+
+ public final String description;
+
+ public final HashAlgorithm hashAlgo;
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java
new file mode 100644
index 0000000000..9cfa0aae25
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java
@@ -0,0 +1,75 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+import java.io.Serializable;
+import java.util.GregorianCalendar;
+
+/**
+ * Identity Data Transfer Object.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class IdentityDTO implements Serializable {
+
+ /*
+ * We implement serializable to allow this class to be used in distributed
+ * containers as defined in the Servlet v2.4 specification.
+ */
+ private static final long serialVersionUID = 1L;
+
+ public String cardNumber;
+
+ public String chipNumber;
+
+ public GregorianCalendar cardValidityDateBegin;
+
+ public GregorianCalendar cardValidityDateEnd;
+
+ public String cardDeliveryMunicipality;
+
+ public String nationalNumber;
+
+ public String name;
+
+ public String firstName;
+
+ public String middleName;
+
+ public String nationality;
+
+ public String placeOfBirth;
+
+ public GregorianCalendar dateOfBirth;
+
+ public boolean male;
+
+ public boolean female;
+
+ public String nobleCondition;
+
+ public String duplicate;
+} \ No newline at end of file
diff --git a/src/ooxml/java/org/apache/poi/util/MethodUtils.java b/src/ooxml/java/org/apache/poi/util/MethodUtils.java
new file mode 100644
index 0000000000..c006c85462
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/util/MethodUtils.java
@@ -0,0 +1,1334 @@
+/* ====================================================================
+ 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.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * <p> Utility reflection methods focussed on methods in general rather than properties in particular. </p>
+ *
+ * <h3>Known Limitations</h3>
+ * <h4>Accessing Public Methods In A Default Access Superclass</h4>
+ * <p>There is an issue when invoking public methods contained in a default access superclass.
+ * Reflection locates these methods fine and correctly assigns them as public.
+ * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
+ *
+ * <p><code>MethodUtils</code> contains a workaround for this situation.
+ * It will attempt to call <code>setAccessible</code> on this method.
+ * If this call succeeds, then the method can be invoked as normal.
+ * This call will only succeed when the application has sufficient security privilages.
+ * If this call fails then a warning will be logged and the method may fail.</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Ralph Schaer
+ * @author Chris Audley
+ * @author Rey Fran&#231;ois
+ * @author Gregor Ra&#253;man
+ * @author Jan Sorensen
+ * @author Robert Burrell Donkin
+ */
+
+public class MethodUtils {
+
+ // --------------------------------------------------------- Private Methods
+
+ /**
+ * Only log warning about accessibility work around once.
+ * <p>
+ * Note that this is broken when this class is deployed via a shared
+ * classloader in a container, as the warning message will be emitted
+ * only once, not once per webapp. However making the warning appear
+ * once per webapp means having a map keyed by context classloader
+ * which introduces nasty memory-leak problems. As this warning is
+ * really optional we can ignore this problem; only one of the webapps
+ * will get the warning in its logs but that should be good enough.
+ */
+ private static boolean loggedAccessibleWarning = false;
+
+ /**
+ * Indicates whether methods should be cached for improved performance.
+ * <p>
+ * Note that when this class is deployed via a shared classloader in
+ * a container, this will affect all webapps. However making this
+ * configurable per webapp would mean having a map keyed by context classloader
+ * which may introduce memory-leak problems.
+ */
+ private static boolean CACHE_METHODS = true;
+
+ /** An empty class array */
+ private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
+ /** An empty object array */
+ private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * <p>Invoke a named method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than <code>invokeExactMethod()</code>.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeMethod(
+ Object object,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeMethod(object, methodName, args);
+
+ }
+
+
+ /**
+ * <p>Invoke a named method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
+ * </p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeMethod(
+ Object object,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeMethod(object, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Invoke a named method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link
+ * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeMethod(
+ Object object,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ Method method = getMatchingAccessibleMethod(
+ object.getClass(),
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on object: " + object.getClass().getName());
+ }
+ return method.invoke(object, args);
+ }
+
+
+ /**
+ * <p>Invoke a method whose parameter type matches exactly the object
+ * type.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(
+ Object object,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeExactMethod(object, methodName, args);
+
+ }
+
+
+ /**
+ * <p>Invoke a method whose parameter types match exactly the object
+ * types.</p>
+ *
+ * <p> This uses reflection to invoke the method obtained from a call to
+ * <code>getAccessibleMethod()</code>.</p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(
+ Object object,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeExactMethod(object, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Invoke a method whose parameter types match exactly the parameter
+ * types given.</p>
+ *
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * <code>getAccessibleMethod()</code>.</p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(
+ Object object,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+
+ Method method = getAccessibleMethod(
+ object.getClass(),
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on object: " + object.getClass().getName());
+ }
+ return method.invoke(object, args);
+
+ }
+
+ /**
+ * <p>Invoke a static method whose parameter types match exactly the parameter
+ * types given.</p>
+ *
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+
+ Method method = getAccessibleMethod(
+ objectClass,
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on class: " + objectClass.getName());
+ }
+ return method.invoke(null, args);
+
+ }
+
+ /**
+ * <p>Invoke a named static method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeStaticMethod (objectClass, methodName, args);
+
+ }
+
+
+ /**
+ * <p>Invoke a named static method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
+ * </p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeStaticMethod (objectClass, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Invoke a named static method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link
+ * #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ Method method = getMatchingAccessibleMethod(
+ objectClass,
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on class: " + objectClass.getName());
+ }
+ return method.invoke(null, args);
+ }
+
+
+ /**
+ * <p>Invoke a static method whose parameter type matches exactly the object
+ * type.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeExactStaticMethod (objectClass, methodName, args);
+
+ }
+
+
+ /**
+ * <p>Invoke a static method whose parameter types match exactly the object
+ * types.</p>
+ *
+ * <p> This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) with given name and a single parameter. If no such method
+ * can be found, return <code>null</code>.
+ * Basically, a convenience wrapper that constructs a <code>Class</code>
+ * array for you.</p>
+ *
+ * @param clazz get method from this class
+ * @param methodName get method with this name
+ * @param parameterType taking this type of parameter
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(
+ Class clazz,
+ String methodName,
+ Class parameterType) {
+
+ Class[] parameterTypes = {parameterType};
+ return getAccessibleMethod(clazz, methodName, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) with given name and parameters. If no such method
+ * can be found, return <code>null</code>.
+ * This is just a convenient wrapper for
+ * {@link #getAccessibleMethod(Method method)}.</p>
+ *
+ * @param clazz get method from this class
+ * @param methodName get method with this name
+ * @param parameterTypes with these parameters types
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(
+ Class clazz,
+ String methodName,
+ Class[] parameterTypes) {
+
+ try {
+ MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
+ Method method = getAccessibleMethod
+ (clazz, clazz.getMethod(methodName, parameterTypes));
+ return method;
+ } catch (NoSuchMethodException e) {
+ return (null);
+ }
+
+ }
+
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return <code>null</code>.</p>
+ *
+ * @param method The method that we wish to call
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(Method method) {
+
+ // Make sure we have a method to check
+ if (method == null) {
+ return (null);
+ }
+
+ return getAccessibleMethod(method.getDeclaringClass(), method);
+
+ }
+
+
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return <code>null</code>.</p>
+ *
+ * @param clazz The class of the object
+ * @param method The method that we wish to call
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(Class clazz, Method method) {
+
+ // Make sure we have a method to check
+ if (method == null) {
+ return (null);
+ }
+
+ // If the requested method is not public we cannot call it
+ if (!Modifier.isPublic(method.getModifiers())) {
+ return (null);
+ }
+
+ boolean sameClass = true;
+ if (clazz == null) {
+ clazz = method.getDeclaringClass();
+ } else {
+ sameClass = clazz.equals(method.getDeclaringClass());
+ if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
+ throw new IllegalArgumentException(clazz.getName() +
+ " is not assignable from " + method.getDeclaringClass().getName());
+ }
+ }
+
+ // If the class is public, we are done
+ if (Modifier.isPublic(clazz.getModifiers())) {
+ if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
+ setMethodAccessible(method); // Default access superclass workaround
+ }
+ return (method);
+ }
+
+ String methodName = method.getName();
+ Class[] parameterTypes = method.getParameterTypes();
+
+ // Check the implemented interfaces and subinterfaces
+ method =
+ getAccessibleMethodFromInterfaceNest(clazz,
+ methodName,
+ parameterTypes);
+
+ // Check the superclass chain
+ if (method == null) {
+ method = getAccessibleMethodFromSuperclass(clazz,
+ methodName,
+ parameterTypes);
+ }
+
+ return (method);
+
+ }
+
+
+ // -------------------------------------------------------- Private Methods
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) by scanning through the superclasses. If no such method
+ * can be found, return <code>null</code>.</p>
+ *
+ * @param clazz Class to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
+ */
+ private static Method getAccessibleMethodFromSuperclass
+ (Class clazz, String methodName, Class[] parameterTypes) {
+
+ Class parentClazz = clazz.getSuperclass();
+ while (parentClazz != null) {
+ if (Modifier.isPublic(parentClazz.getModifiers())) {
+ try {
+ return parentClazz.getMethod(methodName, parameterTypes);
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+ parentClazz = parentClazz.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified method, by scanning through
+ * all implemented interfaces and subinterfaces. If no such method
+ * can be found, return <code>null</code>.</p>
+ *
+ * <p> There isn't any good reason why this method must be private.
+ * It is because there doesn't seem any reason why other classes should
+ * call this rather than the higher level methods.</p>
+ *
+ * @param clazz Parent class for the interfaces to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
+ */
+ private static Method getAccessibleMethodFromInterfaceNest
+ (Class clazz, String methodName, Class[] parameterTypes) {
+
+ Method method = null;
+
+ // Search up the superclass chain
+ for (; clazz != null; clazz = clazz.getSuperclass()) {
+
+ // Check the implemented interfaces of the parent class
+ Class[] interfaces = clazz.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+
+ // Is this interface public?
+ if (!Modifier.isPublic(interfaces[i].getModifiers())) {
+ continue;
+ }
+
+ // Does the method exist on this interface?
+ try {
+ method = interfaces[i].getDeclaredMethod(methodName,
+ parameterTypes);
+ } catch (NoSuchMethodException e) {
+ /* Swallow, if no method is found after the loop then this
+ * method returns null.
+ */
+ }
+ if (method != null) {
+ return method;
+ }
+
+ // Recursively check our parent interfaces
+ method =
+ getAccessibleMethodFromInterfaceNest(interfaces[i],
+ methodName,
+ parameterTypes);
+ if (method != null) {
+ return method;
+ }
+
+ }
+
+ }
+
+ // If we found a method return it
+ if (method != null) {
+ return (method);
+ }
+
+ // We did not find anything
+ return (null);
+
+ }
+
+ /**
+ * <p>Find an accessible method that matches the given name and has compatible parameters.
+ * Compatible parameters mean that every method parameter is assignable from
+ * the given parameters.
+ * In other words, it finds a method with the given name
+ * that will take the parameters given.<p>
+ *
+ * <p>This method is slightly undeterminstic since it loops
+ * through methods names and return the first matching method.</p>
+ *
+ * <p>This method is used by
+ * {@link
+ * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
+ *
+ * <p>This method can match primitive parameter by passing in wrapper classes.
+ * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
+ * parameter.
+ *
+ * @param clazz find method in this class
+ * @param methodName find method with this name
+ * @param parameterTypes find method with compatible parameters
+ * @return The accessible method
+ */
+ public static Method getMatchingAccessibleMethod(
+ Class clazz,
+ String methodName,
+ Class[] parameterTypes) {
+ // trace logging
+ Log log = LogFactory.getLog(MethodUtils.class);
+ if (log.isTraceEnabled()) {
+ log.trace("Matching name=" + methodName + " on " + clazz);
+ }
+ MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
+
+ // see if we can find the method directly
+ // most of the time this works and it's much faster
+ try {
+ Method method = clazz.getMethod(methodName, parameterTypes);
+ if (log.isTraceEnabled()) {
+ log.trace("Found straight match: " + method);
+ log.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
+ }
+
+ setMethodAccessible(method); // Default access superclass workaround
+
+ return method;
+
+ } catch (NoSuchMethodException e) { /* SWALLOW */ }
+
+ // search through all methods
+ int paramSize = parameterTypes.length;
+ Method bestMatch = null;
+ Method[] methods = clazz.getMethods();
+ float bestMatchCost = Float.MAX_VALUE;
+ float myCost = Float.MAX_VALUE;
+ for (int i = 0, size = methods.length; i < size ; i++) {
+ if (methods[i].getName().equals(methodName)) {
+ // log some trace information
+ if (log.isTraceEnabled()) {
+ log.trace("Found matching name:");
+ log.trace(methods[i]);
+ }
+
+ // compare parameters
+ Class[] methodsParams = methods[i].getParameterTypes();
+ int methodParamSize = methodsParams.length;
+ if (methodParamSize == paramSize) {
+ boolean match = true;
+ for (int n = 0 ; n < methodParamSize; n++) {
+ if (log.isTraceEnabled()) {
+ log.trace("Param=" + parameterTypes[n].getName());
+ log.trace("Method=" + methodsParams[n].getName());
+ }
+ if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
+ if (log.isTraceEnabled()) {
+ log.trace(methodsParams[n] + " is not assignable from "
+ + parameterTypes[n]);
+ }
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ // get accessible version of method
+ Method method = getAccessibleMethod(clazz, methods[i]);
+ if (method != null) {
+ if (log.isTraceEnabled()) {
+ log.trace(method + " accessible version of "
+ + methods[i]);
+ }
+ setMethodAccessible(method); // Default access superclass workaround
+ myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
+ if ( myCost < bestMatchCost ) {
+ bestMatch = method;
+ bestMatchCost = myCost;
+ }
+ }
+
+ log.trace("Couldn't find accessible method.");
+ }
+ }
+ }
+ }
+ if ( bestMatch == null ){
+ // didn't find a match
+ log.trace("No match found.");
+ }
+
+ return bestMatch;
+ }
+
+ public static <T> Constructor<T> getMatchingAccessibleConstructor(
+ Class<T> clazz,
+ Class[] parameterTypes) {
+ // trace logging
+ Log log = LogFactory.getLog(MethodUtils.class);
+ MethodDescriptor md = new MethodDescriptor(clazz, "dummy", parameterTypes, false);
+
+ // see if we can find the method directly
+ // most of the time this works and it's much faster
+ try {
+ Constructor<T> constructor = clazz.getConstructor(parameterTypes);
+ if (log.isTraceEnabled()) {
+ log.trace("Found straight match: " + constructor);
+ log.trace("isPublic:" + Modifier.isPublic(constructor.getModifiers()));
+ }
+
+ setMethodAccessible(constructor); // Default access superclass workaround
+
+ return constructor;
+
+ } catch (NoSuchMethodException e) { /* SWALLOW */ }
+
+ // search through all methods
+ int paramSize = parameterTypes.length;
+ Constructor<T> bestMatch = null;
+ Constructor<?>[] constructors = clazz.getConstructors();
+ float bestMatchCost = Float.MAX_VALUE;
+ float myCost = Float.MAX_VALUE;
+ for (int i = 0, size = constructors.length; i < size ; i++) {
+ // compare parameters
+ Class[] methodsParams = constructors[i].getParameterTypes();
+ int methodParamSize = methodsParams.length;
+ if (methodParamSize == paramSize) {
+ boolean match = true;
+ for (int n = 0 ; n < methodParamSize; n++) {
+ if (log.isTraceEnabled()) {
+ log.trace("Param=" + parameterTypes[n].getName());
+ log.trace("Method=" + methodsParams[n].getName());
+ }
+ if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
+ if (log.isTraceEnabled()) {
+ log.trace(methodsParams[n] + " is not assignable from "
+ + parameterTypes[n]);
+ }
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ // get accessible version of method
+ Constructor<T> cons = (Constructor<T>)constructors[i];
+ myCost = getTotalTransformationCost(parameterTypes,cons.getParameterTypes());
+ if ( myCost < bestMatchCost ) {
+ bestMatch = cons;
+ bestMatchCost = myCost;
+ }
+ }
+ }
+ }
+ if ( bestMatch == null ){
+ // didn't find a match
+ log.trace("No match found.");
+ }
+
+ return bestMatch;
+ }
+
+ /**
+ * Try to make the method accessible
+ * @param method The source arguments
+ */
+ private static void setMethodAccessible(Object method) {
+ try {
+ //
+ // XXX Default access superclass workaround
+ //
+ // When a public class has a default access superclass
+ // with public methods, these methods are accessible.
+ // Calling them from compiled code works fine.
+ //
+ // Unfortunately, using reflection to invoke these methods
+ // seems to (wrongly) to prevent access even when the method
+ // modifer is public.
+ //
+ // The following workaround solves the problem but will only
+ // work from sufficiently privilages code.
+ //
+ // Better workarounds would be greatfully accepted.
+ //
+ if (method instanceof Method) {
+ ((Method)method).setAccessible(true);
+ } else if (method instanceof Constructor) {
+ ((Constructor)method).setAccessible(true);
+ } else {
+ throw new RuntimeException("invalid parameter");
+ }
+
+ } catch (SecurityException se) {
+ // log but continue just in case the method.invoke works anyway
+ Log log = LogFactory.getLog(MethodUtils.class);
+ if (!loggedAccessibleWarning) {
+ boolean vulnerableJVM = false;
+ try {
+ String specVersion = System.getProperty("java.specification.version");
+ if (specVersion.charAt(0) == '1' &&
+ (specVersion.charAt(2) == '0' ||
+ specVersion.charAt(2) == '1' ||
+ specVersion.charAt(2) == '2' ||
+ specVersion.charAt(2) == '3')) {
+
+ vulnerableJVM = true;
+ }
+ } catch (SecurityException e) {
+ // don't know - so display warning
+ vulnerableJVM = true;
+ }
+ if (vulnerableJVM) {
+ log.warn(
+ "Current Security Manager restricts use of workarounds for reflection bugs "
+ + " in pre-1.4 JVMs.");
+ }
+ loggedAccessibleWarning = true;
+ }
+ log.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se);
+ }
+ }
+
+ /**
+ * Returns the sum of the object transformation cost for each class in the source
+ * argument list.
+ * @param srcArgs The source arguments
+ * @param destArgs The destination arguments
+ * @return The total transformation cost
+ */
+ private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) {
+
+ float totalCost = 0.0f;
+ for (int i = 0; i < srcArgs.length; i++) {
+ Class srcClass, destClass;
+ srcClass = srcArgs[i];
+ destClass = destArgs[i];
+ totalCost += getObjectTransformationCost(srcClass, destClass);
+ }
+
+ return totalCost;
+ }
+
+ /**
+ * Gets the number of steps required needed to turn the source class into the
+ * destination class. This represents the number of steps in the object hierarchy
+ * graph.
+ * @param srcClass The source class
+ * @param destClass The destination class
+ * @return The cost of transforming an object
+ */
+ private static float getObjectTransformationCost(Class srcClass, Class destClass) {
+ float cost = 0.0f;
+ while (destClass != null && !destClass.equals(srcClass)) {
+ if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
+ // slight penalty for interface match.
+ // we still want an exact match to override an interface match, but
+ // an interface match should override anything where we have to get a
+ // superclass.
+ cost += 0.25f;
+ break;
+ }
+ cost++;
+ destClass = destClass.getSuperclass();
+ }
+
+ /*
+ * If the destination class is null, we've travelled all the way up to
+ * an Object match. We'll penalize this by adding 1.5 to the cost.
+ */
+ if (destClass == null) {
+ cost += 1.5f;
+ }
+
+ return cost;
+ }
+
+
+ /**
+ * <p>Determine whether a type can be used as a parameter in a method invocation.
+ * This method handles primitive conversions correctly.</p>
+ *
+ * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,
+ * a <code>Long</code> to a <code>long</code>,
+ * a <code>Float</code> to a <code>float</code>,
+ * a <code>Integer</code> to a <code>int</code>,
+ * and a <code>Double</code> to a <code>double</code>.
+ * Now logic widening matches are allowed.
+ * For example, a <code>Long</code> will not match a <code>int</code>.
+ *
+ * @param parameterType the type of parameter accepted by the method
+ * @param parameterization the type of parameter being tested
+ *
+ * @return true if the assignement is compatible.
+ */
+ public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) {
+ // try plain assignment
+ if (parameterType.isAssignableFrom(parameterization)) {
+ return true;
+ }
+
+ if (parameterType.isPrimitive()) {
+ // this method does *not* do widening - you must specify exactly
+ // is this the right behaviour?
+ Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);
+ if (parameterWrapperClazz != null) {
+ return parameterWrapperClazz.equals(parameterization);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the wrapper object class for the given primitive type class.
+ * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code>
+ * @param primitiveType the primitive type class for which a match is to be found
+ * @return the wrapper type associated with the given primitive
+ * or null if no match is found
+ */
+ public static Class getPrimitiveWrapper(Class primitiveType) {
+ // does anyone know a better strategy than comparing names?
+ if (boolean.class.equals(primitiveType)) {
+ return Boolean.class;
+ } else if (float.class.equals(primitiveType)) {
+ return Float.class;
+ } else if (long.class.equals(primitiveType)) {
+ return Long.class;
+ } else if (int.class.equals(primitiveType)) {
+ return Integer.class;
+ } else if (short.class.equals(primitiveType)) {
+ return Short.class;
+ } else if (byte.class.equals(primitiveType)) {
+ return Byte.class;
+ } else if (double.class.equals(primitiveType)) {
+ return Double.class;
+ } else if (char.class.equals(primitiveType)) {
+ return Character.class;
+ } else {
+
+ return null;
+ }
+ }
+
+ /**
+ * Gets the class for the primitive type corresponding to the primitive wrapper class given.
+ * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>.
+ * @param wrapperType the
+ * @return the primitive type class corresponding to the given wrapper class,
+ * null if no match is found
+ */
+ public static Class getPrimitiveType(Class wrapperType) {
+ // does anyone know a better strategy than comparing names?
+ if (Boolean.class.equals(wrapperType)) {
+ return boolean.class;
+ } else if (Float.class.equals(wrapperType)) {
+ return float.class;
+ } else if (Long.class.equals(wrapperType)) {
+ return long.class;
+ } else if (Integer.class.equals(wrapperType)) {
+ return int.class;
+ } else if (Short.class.equals(wrapperType)) {
+ return short.class;
+ } else if (Byte.class.equals(wrapperType)) {
+ return byte.class;
+ } else if (Double.class.equals(wrapperType)) {
+ return double.class;
+ } else if (Character.class.equals(wrapperType)) {
+ return char.class;
+ } else {
+ Log log = LogFactory.getLog(MethodUtils.class);
+ if (log.isDebugEnabled()) {
+ log.debug("Not a known primitive wrapper class: " + wrapperType);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Find a non primitive representation for given primitive class.
+ *
+ * @param clazz the class to find a representation for, not null
+ * @return the original class if it not a primitive. Otherwise the wrapper class. Not null
+ */
+ public static Class toNonPrimitiveClass(Class clazz) {
+ if (clazz.isPrimitive()) {
+ Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz);
+ // the above method returns
+ if (primitiveClazz != null) {
+ return primitiveClazz;
+ } else {
+ return clazz;
+ }
+ } else {
+ return clazz;
+ }
+ }
+
+
+ /**
+ * Represents the key to looking up a Method by reflection.
+ */
+ private static class MethodDescriptor {
+ private Class cls;
+ private String methodName;
+ private Class[] paramTypes;
+ private boolean exact;
+ private int hashCode;
+
+ /**
+ * The sole constructor.
+ *
+ * @param cls the class to reflect, must not be null
+ * @param methodName the method name to obtain
+ * @param paramTypes the array of classes representing the paramater types
+ * @param exact whether the match has to be exact.
+ */
+ public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) {
+ if (cls == null) {
+ throw new IllegalArgumentException("Class cannot be null");
+ }
+ if (methodName == null) {
+ throw new IllegalArgumentException("Method Name cannot be null");
+ }
+ if (paramTypes == null) {
+ paramTypes = EMPTY_CLASS_PARAMETERS;
+ }
+
+ this.cls = cls;
+ this.methodName = methodName;
+ this.paramTypes = paramTypes;
+ this.exact= exact;
+
+ this.hashCode = methodName.length();
+ }
+ /**
+ * Checks for equality.
+ * @param obj object to be tested for equality
+ * @return true, if the object describes the same Method.
+ */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MethodDescriptor)) {
+ return false;
+ }
+ MethodDescriptor md = (MethodDescriptor)obj;
+
+ return (
+ exact == md.exact &&
+ methodName.equals(md.methodName) &&
+ cls.equals(md.cls) &&
+ java.util.Arrays.equals(paramTypes, md.paramTypes)
+ );
+ }
+ /**
+ * Returns the string length of method name. I.e. if the
+ * hashcodes are different, the objects are different. If the
+ * hashcodes are the same, need to use the equals method to
+ * determine equality.
+ * @return the string length of method name.
+ */
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/util/SAXHelper.java b/src/ooxml/java/org/apache/poi/util/SAXHelper.java
index b38b2c2be9..9ee00fb69a 100644
--- a/src/ooxml/java/org/apache/poi/util/SAXHelper.java
+++ b/src/ooxml/java/org/apache/poi/util/SAXHelper.java
@@ -23,6 +23,9 @@ import java.io.StringReader;
import java.lang.reflect.Method;
import javax.xml.XMLConstants;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
import org.dom4j.Document;
import org.dom4j.DocumentException;
@@ -89,4 +92,72 @@ public final class SAXHelper {
public static Document readSAXDocument(InputStream inp) throws DocumentException {
return getSAXReader().read(inp);
}
+
+ private static final EntityResolver IGNORING_ENTITY_RESOLVER = new EntityResolver() {
+ @Override
+ public InputSource resolveEntity(String publicId, String systemId)
+ throws SAXException, IOException {
+ return new InputSource(new StringReader(""));
+ }
+ };
+
+ private static void trySetSAXFeature(DocumentBuilderFactory documentBuilderFactory, String feature, boolean enabled) {
+ try {
+ documentBuilderFactory.setFeature(feature, enabled);
+ } catch (Exception e) {
+ logger.log(POILogger.INFO, "SAX Feature unsupported", feature, e);
+ }
+ }
+ private static void trySetXercesSecurityManager(DocumentBuilderFactory documentBuilderFactory) {
+ // Try built-in JVM one first, standalone if not
+ for (String securityManagerClassName : new String[] {
+ "com.sun.org.apache.xerces.internal.util.SecurityManager",
+ "org.apache.xerces.util.SecurityManager"
+ }) {
+ try {
+ Object mgr = Class.forName(securityManagerClassName).newInstance();
+ Method setLimit = mgr.getClass().getMethod("setEntityExpansionLimit", Integer.TYPE);
+ setLimit.invoke(mgr, 4096);
+ documentBuilderFactory.setAttribute("http://apache.org/xml/properties/security-manager", mgr);
+ // Stop once one can be setup without error
+ return;
+ } catch (Exception e) {
+ logger.log(POILogger.INFO, "SAX Security Manager could not be setup", e);
+ }
+ }
+ }
+
+ private static final ThreadLocal<DocumentBuilder> documentBuilder = new ThreadLocal<DocumentBuilder>() {
+ @Override
+ protected DocumentBuilder initialValue() {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setNamespaceAware(true);
+ factory.setValidating(false);
+ trySetSAXFeature(factory, XMLConstants.FEATURE_SECURE_PROCESSING, true);
+ trySetXercesSecurityManager(factory);
+ try {
+ return factory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new IllegalStateException("cannot create a DocumentBuilder", e);
+ }
+ }
+
+ @Override
+ public DocumentBuilder get() {
+ DocumentBuilder documentBuilder = super.get();
+ documentBuilder.reset();
+ documentBuilder.setEntityResolver(IGNORING_ENTITY_RESOLVER);
+ return documentBuilder;
+ }
+ };
+
+ /**
+ * Parses the given stream via the default (sensible)
+ * SAX Reader
+ * @param inp Stream to read the XML data from
+ * @return the SAX processed Document
+ */
+ public static org.w3c.dom.Document readSAXDocumentW3C(InputStream inp) throws IOException, SAXException {
+ return documentBuilder.get().parse(inp);
+ }
}
diff --git a/src/ooxml/java/org/apache/poi/util/XmlSort.java b/src/ooxml/java/org/apache/poi/util/XmlSort.java
new file mode 100644
index 0000000000..4e1ffa54f0
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/util/XmlSort.java
@@ -0,0 +1,221 @@
+/* ====================================================================
+ 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.io.File;
+import java.io.IOException;
+import java.util.Comparator;
+
+import javax.xml.namespace.QName;
+
+import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlObject;
+
+/**
+ */
+public final class XmlSort
+{
+ /**
+ * Receives an XML element instance and sorts the children of this
+ * element in lexicographical (by default) order.
+ *
+ * @param args An array in which the first item is a
+ * path to the XML instance file and the second item (optional) is
+ * an XPath inside the document identifying the element to be sorted
+ */
+ public static void main(String[] args)
+ {
+ if (args.length < 1 || args.length > 2)
+ {
+ System.out.println(" java XmlSort <XML_File> [<XPath>]");
+ return;
+ }
+ File f = new File(args[0]);
+ try
+ {
+ XmlObject docInstance = XmlObject.Factory.parse(f);
+ XmlObject element = null;
+ if (args.length > 1)
+ {
+ String xpath = args[1];
+ XmlObject[] result = docInstance.selectPath(xpath);
+ if (result.length == 0)
+ {
+ System.out.println("ERROR: XPath \"" + xpath + "\" did not return any results");
+ }
+ else if (result.length > 1)
+ {
+ System.out.println("ERROR: XPath \"" + xpath + "\" returned more than one " +
+ "node (" + result.length + ")");
+ }
+ else
+ element = result[0];
+ }
+ else
+ {
+ // Navigate to the root element
+ XmlCursor c = docInstance.newCursor();
+ c.toFirstChild();
+ element = c.getObject();
+ c.dispose();
+ }
+ if (element != null)
+ sort(element, new QNameComparator(QNameComparator.ASCENDING));
+ System.out.println(docInstance.xmlText());
+ }
+ catch (IOException ioe)
+ {
+ System.out.println("ERROR: Could not open file: \"" + args[0] + "\": " +
+ ioe.getMessage());
+ }
+ catch (XmlException xe)
+ {
+ System.out.println("ERROR: Could not parse file: \"" + args[0] + "\": " +
+ xe.getMessage());
+ }
+ }
+
+ /**
+ * 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. See {@link org.apache.xmlbeans.samples.cursor.XmlSort.QNameComparator} for a simple
+ * implementation that compares two elements based on the value of their QName, but more
+ * complicated implementations are possible, for instance, ones that compare two elements based
+ * on the value of a specifc attribute etc.
+ * @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
+ /*
+ * Uncomment this code to cause the text before the element to move along
+ * with the element, rather than the text after the element. Notice that this
+ * is more difficult to do, because the cursor's "type" refers to the position
+ * to the right of the cursor, so to get the type of the token to the left, the
+ * cursor needs to be first moved to the left (previous token)
+ *
+ headCursor.toPrevToken();
+ while (headCursor.isComment() || headCursor.isProcinst() || headCursor.isText())
+ headCursor.toPrevToken();
+ headCursor.toNextToken();
+ listCursor.toPrevToken();
+ while (listCursor.isComment() || listCursor.isProcinst() || listCursor.isText())
+ listCursor.toPrevToken();
+ listCursor.toNextToken();
+ while (!listCursor.isStart())
+ listCursor.moveXml(headCursor);
+ listCursor.moveXml(headCursor);
+ */
+ // 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();
+ }
+ }
+
+ /**
+ * Implements a <code>java.util.Comparator</code> for comparing <code>QName</code>values.
+ * The namespace URIs are compared first and if they are equal, the local parts are compared.
+ * <p/>
+ * The constructor accepts an argument indicating whether the comparison order is the same as
+ * the lexicographic order of the strings or the reverse.
+ */
+ public static final class QNameComparator implements Comparator
+ {
+ public static final int ASCENDING = 1;
+ public static final int DESCENDING = 2;
+
+ private int order;
+
+ public QNameComparator(int order)
+ {
+ this.order = order;
+ if (order != ASCENDING && order != DESCENDING)
+ throw new IllegalArgumentException("Please specify one of ASCENDING or DESCENDING "+
+ "comparison orders");
+ }
+
+ public int compare(Object o, Object o1)
+ {
+ XmlCursor cursor1 = (XmlCursor) o;
+ XmlCursor cursor2 = (XmlCursor) o1;
+ QName qname1 = cursor1.getName();
+ QName qname2 = cursor2.getName();
+ int qnameComparisonRes = qname1.getNamespaceURI().compareTo(qname2.getNamespaceURI());
+ if (qnameComparisonRes == 0)
+ return order == ASCENDING ?
+ qname1.getLocalPart().compareTo(qname2.getLocalPart()) :
+ -qname1.getLocalPart().compareTo(qname2.getLocalPart());
+ else
+ return order == ASCENDING ? qnameComparisonRes : -qnameComparisonRes;
+ }
+ }
+}
+ \ No newline at end of file
diff --git a/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdES.xsd b/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdES.xsd
new file mode 100644
index 0000000000..656e721c7c
--- /dev/null
+++ b/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdES.xsd
@@ -0,0 +1,466 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema targetNamespace="http://uri.etsi.org/01903/v1.3.2#" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://uri.etsi.org/01903/v1.3.2#" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" elementFormDefault="qualified">
+ <xsd:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd"/>
+ <!-- Start auxiliary types definitions: AnyType, ObjectIdentifierType,
+EncapsulatedPKIDataType and containers for time-stamp tokens -->
+ <!-- Start AnyType -->
+ <xsd:element name="Any" type="AnyType"/>
+ <xsd:complexType name="AnyType" mixed="true">
+ <xsd:sequence minOccurs="0" maxOccurs="unbounded">
+ <xsd:any namespace="##any" processContents="lax"/>
+ </xsd:sequence>
+ <xsd:anyAttribute namespace="##any"/>
+ </xsd:complexType>
+ <!-- End AnyType -->
+ <!-- Start ObjectIdentifierType-->
+ <xsd:element name="ObjectIdentifier" type="ObjectIdentifierType"/>
+ <xsd:complexType name="ObjectIdentifierType">
+ <xsd:sequence>
+ <xsd:element name="Identifier" type="IdentifierType"/>
+ <xsd:element name="Description" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="DocumentationReferences" type="DocumentationReferencesType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="IdentifierType">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:anyURI">
+ <xsd:attribute name="Qualifier" type="QualifierType" use="optional"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ <xsd:simpleType name="QualifierType">
+ <xsd:restriction base="xsd:string">
+ <xsd:enumeration value="OIDAsURI"/>
+ <xsd:enumeration value="OIDAsURN"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:complexType name="DocumentationReferencesType">
+ <xsd:sequence maxOccurs="unbounded">
+ <xsd:element name="DocumentationReference" type="xsd:anyURI"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <!-- End ObjectIdentifierType-->
+ <!-- Start EncapsulatedPKIDataType-->
+ <xsd:element name="EncapsulatedPKIData" type="EncapsulatedPKIDataType"/>
+ <xsd:complexType name="EncapsulatedPKIDataType">
+ <xsd:simpleContent>
+ <xsd:extension base="xsd:base64Binary">
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ <xsd:attribute name="Encoding" type="xsd:anyURI" use="optional"/>
+ </xsd:extension>
+ </xsd:simpleContent>
+ </xsd:complexType>
+ <!-- End EncapsulatedPKIDataType -->
+ <!-- Start time-stamp containers types -->
+ <!-- Start GenericTimeStampType -->
+ <xsd:element name="Include" type="IncludeType"/>
+ <xsd:complexType name="IncludeType">
+ <xsd:attribute name="URI" type="xsd:anyURI" use="required"/>
+ <xsd:attribute name="referencedData" type="xsd:boolean" use="optional"/>
+ </xsd:complexType>
+ <xsd:element name="ReferenceInfo" type="ReferenceInfoType"/>
+ <xsd:complexType name="ReferenceInfoType">
+ <xsd:sequence>
+ <xsd:element ref="ds:DigestMethod"/>
+ <xsd:element ref="ds:DigestValue"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ <xsd:attribute name="URI" type="xsd:anyURI" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="GenericTimeStampType" abstract="true">
+ <xsd:sequence>
+ <xsd:choice minOccurs="0">
+ <xsd:element ref="Include" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element ref="ReferenceInfo" maxOccurs="unbounded"/>
+ </xsd:choice>
+ <xsd:element ref="ds:CanonicalizationMethod" minOccurs="0"/>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="EncapsulatedTimeStamp" type="EncapsulatedPKIDataType"/>
+ <xsd:element name="XMLTimeStamp" type="AnyType"/>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End GenericTimeStampType -->
+ <!-- Start XAdESTimeStampType -->
+ <xsd:element name="XAdESTimeStamp" type="XAdESTimeStampType"/>
+ <xsd:complexType name="XAdESTimeStampType">
+ <xsd:complexContent>
+ <xsd:restriction base="GenericTimeStampType">
+ <xsd:sequence>
+ <xsd:element ref="Include" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element ref="ds:CanonicalizationMethod" minOccurs="0"/>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="EncapsulatedTimeStamp" type="EncapsulatedPKIDataType"/>
+ <xsd:element name="XMLTimeStamp" type="AnyType"/>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <!-- End XAdESTimeStampType -->
+ <!-- Start OtherTimeStampType -->
+ <xsd:element name="OtherTimeStamp" type="OtherTimeStampType"/>
+ <xsd:complexType name="OtherTimeStampType">
+ <xsd:complexContent>
+ <xsd:restriction base="GenericTimeStampType">
+ <xsd:sequence>
+ <xsd:element ref="ReferenceInfo" maxOccurs="unbounded"/>
+ <xsd:element ref="ds:CanonicalizationMethod" minOccurs="0"/>
+ <xsd:choice>
+ <xsd:element name="EncapsulatedTimeStamp" type="EncapsulatedPKIDataType"/>
+ <xsd:element name="XMLTimeStamp" type="AnyType"/>
+ </xsd:choice>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <!-- End OtherTimeStampType -->
+ <!-- End time-stamp containers types -->
+ <!-- End auxiliary types definitions-->
+ <!-- Start container types -->
+ <!-- Start QualifyingProperties -->
+ <xsd:element name="QualifyingProperties" type="QualifyingPropertiesType"/>
+ <xsd:complexType name="QualifyingPropertiesType">
+ <xsd:sequence>
+ <xsd:element name="SignedProperties" type="SignedPropertiesType" minOccurs="0"/>
+ <xsd:element name="UnsignedProperties" type="UnsignedPropertiesType" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="Target" type="xsd:anyURI" use="required"/>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End QualifyingProperties -->
+ <!-- Start SignedProperties-->
+ <xsd:element name="SignedProperties" type="SignedPropertiesType"/>
+ <xsd:complexType name="SignedPropertiesType">
+ <xsd:sequence>
+ <xsd:element name="SignedSignatureProperties" type="SignedSignaturePropertiesType" minOccurs="0"/>
+ <xsd:element name="SignedDataObjectProperties" type="SignedDataObjectPropertiesType" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End SignedProperties-->
+ <!-- Start UnsignedProperties-->
+ <xsd:element name="UnsignedProperties" type="UnsignedPropertiesType"/>
+ <xsd:complexType name="UnsignedPropertiesType">
+ <xsd:sequence>
+ <xsd:element name="UnsignedSignatureProperties" type="UnsignedSignaturePropertiesType" minOccurs="0"/>
+ <xsd:element name="UnsignedDataObjectProperties" type="UnsignedDataObjectPropertiesType" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End UnsignedProperties-->
+ <!-- Start SignedSignatureProperties-->
+ <xsd:element name="SignedSignatureProperties" type="SignedSignaturePropertiesType"/>
+ <xsd:complexType name="SignedSignaturePropertiesType">
+ <xsd:sequence>
+ <xsd:element name="SigningTime" type="xsd:dateTime" minOccurs="0"/>
+ <xsd:element name="SigningCertificate" type="CertIDListType" minOccurs="0"/>
+ <xsd:element name="SignaturePolicyIdentifier" type="SignaturePolicyIdentifierType" minOccurs="0"/>
+ <xsd:element name="SignatureProductionPlace" type="SignatureProductionPlaceType" minOccurs="0"/>
+ <xsd:element name="SignerRole" type="SignerRoleType" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End SignedSignatureProperties-->
+ <!-- Start SignedDataObjectProperties-->
+ <xsd:element name="SignedDataObjectProperties" type="SignedDataObjectPropertiesType"/>
+ <xsd:complexType name="SignedDataObjectPropertiesType">
+ <xsd:sequence>
+ <xsd:element name="DataObjectFormat" type="DataObjectFormatType" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="CommitmentTypeIndication" type="CommitmentTypeIndicationType" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="AllDataObjectsTimeStamp" type="XAdESTimeStampType" minOccurs="0" maxOccurs="unbounded"/>
+ <xsd:element name="IndividualDataObjectsTimeStamp" type="XAdESTimeStampType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End SignedDataObjectProperties-->
+ <!-- Start UnsignedSignatureProperties-->
+ <xsd:element name="UnsignedSignatureProperties" type="UnsignedSignaturePropertiesType"/>
+ <xsd:complexType name="UnsignedSignaturePropertiesType">
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="CounterSignature" type="CounterSignatureType"/>
+ <xsd:element name="SignatureTimeStamp" type="XAdESTimeStampType"/>
+ <xsd:element name="CompleteCertificateRefs" type="CompleteCertificateRefsType"/>
+ <xsd:element name="CompleteRevocationRefs" type="CompleteRevocationRefsType"/>
+ <xsd:element name="AttributeCertificateRefs" type="CompleteCertificateRefsType"/>
+ <xsd:element name="AttributeRevocationRefs" type="CompleteRevocationRefsType"/>
+ <xsd:element name="SigAndRefsTimeStamp" type="XAdESTimeStampType"/>
+ <xsd:element name="RefsOnlyTimeStamp" type="XAdESTimeStampType"/>
+ <xsd:element name="CertificateValues" type="CertificateValuesType"/>
+ <xsd:element name="RevocationValues" type="RevocationValuesType"/>
+ <xsd:element name="AttrAuthoritiesCertValues" type="CertificateValuesType"/>
+ <xsd:element name="AttributeRevocationValues" type="RevocationValuesType"/>
+ <xsd:element name="ArchiveTimeStamp" type="XAdESTimeStampType"/>
+ <xsd:any namespace="##other"/>
+ </xsd:choice>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End UnsignedSignatureProperties-->
+ <!-- Start UnsignedDataObjectProperties-->
+ <xsd:element name="UnsignedDataObjectProperties" type="UnsignedDataObjectPropertiesType"/>
+ <xsd:complexType name="UnsignedDataObjectPropertiesType">
+ <xsd:sequence>
+ <xsd:element name="UnsignedDataObjectProperty" type="AnyType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End UnsignedDataObjectProperties-->
+ <!-- Start QualifyingPropertiesReference-->
+ <xsd:element name="QualifyingPropertiesReference" type="QualifyingPropertiesReferenceType"/>
+ <xsd:complexType name="QualifyingPropertiesReferenceType">
+ <xsd:attribute name="URI" type="xsd:anyURI" use="required"/>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End QualifyingPropertiesReference-->
+ <!-- End container types -->
+ <!-- Start SigningTime element -->
+ <xsd:element name="SigningTime" type="xsd:dateTime"/>
+ <!-- End SigningTime element -->
+ <!-- Start SigningCertificate -->
+ <xsd:element name="SigningCertificate" type="CertIDListType"/>
+ <xsd:complexType name="CertIDListType">
+ <xsd:sequence>
+ <xsd:element name="Cert" type="CertIDType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CertIDType">
+ <xsd:sequence>
+ <xsd:element name="CertDigest" type="DigestAlgAndValueType"/>
+ <xsd:element name="IssuerSerial" type="ds:X509IssuerSerialType"/>
+ </xsd:sequence>
+ <xsd:attribute name="URI" type="xsd:anyURI" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="DigestAlgAndValueType">
+ <xsd:sequence>
+ <xsd:element ref="ds:DigestMethod"/>
+ <xsd:element ref="ds:DigestValue"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <!-- End SigningCertificate -->
+ <!-- Start SignaturePolicyIdentifier -->
+ <xsd:element name="SignaturePolicyIdentifier" type="SignaturePolicyIdentifierType"/>
+ <xsd:complexType name="SignaturePolicyIdentifierType">
+ <xsd:choice>
+ <xsd:element name="SignaturePolicyId" type="SignaturePolicyIdType"/>
+ <xsd:element name="SignaturePolicyImplied"/>
+ </xsd:choice>
+ </xsd:complexType>
+ <xsd:complexType name="SignaturePolicyIdType">
+ <xsd:sequence>
+ <xsd:element name="SigPolicyId" type="ObjectIdentifierType"/>
+ <xsd:element ref="ds:Transforms" minOccurs="0"/>
+ <xsd:element name="SigPolicyHash" type="DigestAlgAndValueType"/>
+ <xsd:element name="SigPolicyQualifiers" type="SigPolicyQualifiersListType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="SigPolicyQualifiersListType">
+ <xsd:sequence>
+ <xsd:element name="SigPolicyQualifier" type="AnyType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="SPURI" type="xsd:anyURI"/>
+ <xsd:element name="SPUserNotice" type="SPUserNoticeType"/>
+ <xsd:complexType name="SPUserNoticeType">
+ <xsd:sequence>
+ <xsd:element name="NoticeRef" type="NoticeReferenceType" minOccurs="0"/>
+ <xsd:element name="ExplicitText" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="NoticeReferenceType">
+ <xsd:sequence>
+ <xsd:element name="Organization" type="xsd:string"/>
+ <xsd:element name="NoticeNumbers" type="IntegerListType"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="IntegerListType">
+ <xsd:sequence>
+ <xsd:element name="int" type="xsd:integer" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <!-- End SignaturePolicyIdentifier -->
+ <!-- Start CounterSignature -->
+ <xsd:element name="CounterSignature" type="CounterSignatureType"/>
+ <xsd:complexType name="CounterSignatureType">
+ <xsd:sequence>
+ <xsd:element ref="ds:Signature"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <!-- End CounterSignature -->
+ <!-- Start DataObjectFormat -->
+ <xsd:element name="DataObjectFormat" type="DataObjectFormatType"/>
+ <xsd:complexType name="DataObjectFormatType">
+ <xsd:sequence>
+ <xsd:element name="Description" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="ObjectIdentifier" type="ObjectIdentifierType" minOccurs="0"/>
+ <xsd:element name="MimeType" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="Encoding" type="xsd:anyURI" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="ObjectReference" type="xsd:anyURI" use="required"/>
+ </xsd:complexType>
+ <!-- End DataObjectFormat -->
+ <!-- Start CommitmentTypeIndication -->
+ <xsd:element name="CommitmentTypeIndication" type="CommitmentTypeIndicationType"/>
+ <xsd:complexType name="CommitmentTypeIndicationType">
+ <xsd:sequence>
+ <xsd:element name="CommitmentTypeId" type="ObjectIdentifierType"/>
+ <xsd:choice>
+ <xsd:element name="ObjectReference" type="xsd:anyURI" maxOccurs="unbounded"/>
+ <xsd:element name="AllSignedDataObjects"/>
+ </xsd:choice>
+ <xsd:element name="CommitmentTypeQualifiers" type="CommitmentTypeQualifiersListType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CommitmentTypeQualifiersListType">
+ <xsd:sequence>
+ <xsd:element name="CommitmentTypeQualifier" type="AnyType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <!-- End CommitmentTypeIndication -->
+ <!-- Start SignatureProductionPlace -->
+ <xsd:element name="SignatureProductionPlace" type="SignatureProductionPlaceType"/>
+ <xsd:complexType name="SignatureProductionPlaceType">
+ <xsd:sequence>
+ <xsd:element name="City" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="StateOrProvince" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="PostalCode" type="xsd:string" minOccurs="0"/>
+ <xsd:element name="CountryName" type="xsd:string" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <!-- End SignatureProductionPlace -->
+ <!-- Start SignerRole -->
+ <xsd:element name="SignerRole" type="SignerRoleType"/>
+ <xsd:complexType name="SignerRoleType">
+ <xsd:sequence>
+ <xsd:element name="ClaimedRoles" type="ClaimedRolesListType" minOccurs="0"/>
+ <xsd:element name="CertifiedRoles" type="CertifiedRolesListType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ClaimedRolesListType">
+ <xsd:sequence>
+ <xsd:element name="ClaimedRole" type="AnyType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CertifiedRolesListType">
+ <xsd:sequence>
+ <xsd:element name="CertifiedRole" type="EncapsulatedPKIDataType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <!-- End SignerRole -->
+ <xsd:element name="AllDataObjectsTimeStamp" type="XAdESTimeStampType"/>
+ <xsd:element name="IndividualDataObjectsTimeStamp" type="XAdESTimeStampType"/>
+ <xsd:element name="SignatureTimeStamp" type="XAdESTimeStampType"/>
+ <!-- Start CompleteCertificateRefs -->
+ <xsd:element name="CompleteCertificateRefs" type="CompleteCertificateRefsType"/>
+ <xsd:complexType name="CompleteCertificateRefsType">
+ <xsd:sequence>
+ <xsd:element name="CertRefs" type="CertIDListType"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End CompleteCertificateRefs -->
+ <!-- Start CompleteRevocationRefs-->
+ <xsd:element name="CompleteRevocationRefs" type="CompleteRevocationRefsType"/>
+ <xsd:complexType name="CompleteRevocationRefsType">
+ <xsd:sequence>
+ <xsd:element name="CRLRefs" type="CRLRefsType" minOccurs="0"/>
+ <xsd:element name="OCSPRefs" type="OCSPRefsType" minOccurs="0"/>
+ <xsd:element name="OtherRefs" type="OtherCertStatusRefsType" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="CRLRefsType">
+ <xsd:sequence>
+ <xsd:element name="CRLRef" type="CRLRefType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CRLRefType">
+ <xsd:sequence>
+ <xsd:element name="DigestAlgAndValue" type="DigestAlgAndValueType"/>
+ <xsd:element name="CRLIdentifier" type="CRLIdentifierType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CRLIdentifierType">
+ <xsd:sequence>
+ <xsd:element name="Issuer" type="xsd:string"/>
+ <xsd:element name="IssueTime" type="xsd:dateTime"/>
+ <xsd:element name="Number" type="xsd:integer" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="URI" type="xsd:anyURI" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="OCSPRefsType">
+ <xsd:sequence>
+ <xsd:element name="OCSPRef" type="OCSPRefType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="OCSPRefType">
+ <xsd:sequence>
+ <xsd:element name="OCSPIdentifier" type="OCSPIdentifierType"/>
+ <xsd:element name="DigestAlgAndValue" type="DigestAlgAndValueType" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="ResponderIDType">
+ <xsd:choice>
+ <xsd:element name="ByName" type="xsd:string"/>
+ <xsd:element name="ByKey" type="xsd:base64Binary"/>
+ </xsd:choice>
+ </xsd:complexType>
+ <xsd:complexType name="OCSPIdentifierType">
+ <xsd:sequence>
+ <xsd:element name="ResponderID" type="ResponderIDType"/>
+ <xsd:element name="ProducedAt" type="xsd:dateTime"/>
+ </xsd:sequence>
+ <xsd:attribute name="URI" type="xsd:anyURI" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="OtherCertStatusRefsType">
+ <xsd:sequence>
+ <xsd:element name="OtherRef" type="AnyType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <!-- End CompleteRevocationRefs-->
+ <xsd:element name="AttributeCertificateRefs" type="CompleteCertificateRefsType"/>
+ <xsd:element name="AttributeRevocationRefs" type="CompleteRevocationRefsType"/>
+ <xsd:element name="SigAndRefsTimeStamp" type="XAdESTimeStampType"/>
+ <xsd:element name="RefsOnlyTimeStamp" type="XAdESTimeStampType"/>
+ <!-- Start CertificateValues -->
+ <xsd:element name="CertificateValues" type="CertificateValuesType"/>
+ <xsd:complexType name="CertificateValuesType">
+ <xsd:choice minOccurs="0" maxOccurs="unbounded">
+ <xsd:element name="EncapsulatedX509Certificate" type="EncapsulatedPKIDataType"/>
+ <xsd:element name="OtherCertificate" type="AnyType"/>
+ </xsd:choice>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <!-- End CertificateValues -->
+ <!-- Start RevocationValues-->
+ <xsd:element name="RevocationValues" type="RevocationValuesType"/>
+ <xsd:complexType name="RevocationValuesType">
+ <xsd:sequence>
+ <xsd:element name="CRLValues" type="CRLValuesType" minOccurs="0"/>
+ <xsd:element name="OCSPValues" type="OCSPValuesType" minOccurs="0"/>
+ <xsd:element name="OtherValues" type="OtherCertStatusValuesType" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ </xsd:complexType>
+ <xsd:complexType name="CRLValuesType">
+ <xsd:sequence>
+ <xsd:element name="EncapsulatedCRLValue" type="EncapsulatedPKIDataType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="OCSPValuesType">
+ <xsd:sequence>
+ <xsd:element name="EncapsulatedOCSPValue" type="EncapsulatedPKIDataType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="OtherCertStatusValuesType">
+ <xsd:sequence>
+ <xsd:element name="OtherValue" type="AnyType" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <!-- End RevocationValues-->
+ <xsd:element name="AttrAuthoritiesCertValues" type="CertificateValuesType"/>
+ <xsd:element name="AttributeRevocationValues" type="RevocationValuesType"/>
+ <xsd:element name="ArchiveTimeStamp" type="XAdESTimeStampType"/>
+</xsd:schema> \ No newline at end of file
diff --git a/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdESv141.xsd b/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdESv141.xsd
new file mode 100644
index 0000000000..cd6614f134
--- /dev/null
+++ b/src/ooxml/resources/org/apache/poi/poifs/crypt/XAdESv141.xsd
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsd:schema targetNamespace="http://uri.etsi.org/01903/v1.4.1#" xmlns="http://uri.etsi.org/01903/v1.4.1#" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" elementFormDefault="qualified">
+ <xsd:import namespace="http://uri.etsi.org/01903/v1.3.2#" schemaLocation="http://uri.etsi.org/01903/v1.3.2/XAdES.xsd"/>
+ <!-- Start CertificateValues -->
+ <xsd:element name="TimeStampValidationData" type="ValidationDataType"/>
+ <xsd:complexType name="ValidationDataType">
+ <xsd:sequence>
+ <xsd:element ref="xades:CertificateValues" minOccurs="0"/>
+ <xsd:element ref="xades:RevocationValues" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="Id" type="xsd:ID" use="optional"/>
+ <xsd:attribute name="URI" type="xsd:anyURI" use="optional"/>
+ </xsd:complexType>
+ <xsd:element name="ArchiveTimeStamp" type="xades:XAdESTimeStampType"/>
+</xsd:schema> \ No newline at end of file
diff --git a/src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd b/src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd
new file mode 100644
index 0000000000..f7019f13f5
--- /dev/null
+++ b/src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ====================================================================
+ 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.
+ ====================================================================
+-->
+<xsd:schema targetNamespace="http://schemas.microsoft.com/office/2006/digsig" elementFormDefault="qualified" xmlns="http://schemas.microsoft.com/office/2006/digsig" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:simpleType name="ST_PositiveInteger">
+ <xsd:restriction base="xsd:int">
+ <xsd:minExclusive value="0"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_SignatureComments">
+ <xsd:restriction base="xsd:string">
+ <xsd:maxLength value="255"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_SignatureProviderUrl">
+ <xsd:restriction base="xsd:string">
+ <xsd:maxLength value="2083"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_SignatureText">
+ <xsd:restriction base="xsd:string">
+ <xsd:maxLength value="100"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_SignatureType">
+ <xsd:restriction base="xsd:int">
+ <xsd:enumeration value="1"/>
+ <xsd:enumeration value="2"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_Version">
+ <xsd:restriction base="xsd:string">
+ <xsd:maxLength value="64"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_UniqueIdentifierWithBraces">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="\{[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}\}|"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:group name="EG_RequiredChildren">
+ <xsd:sequence>
+ <xsd:element name="SetupID" type="ST_UniqueIdentifierWithBraces"/>
+ <xsd:element name="SignatureText" type="ST_SignatureText"/>
+ <xsd:element name="SignatureImage" type="xsd:base64Binary"/>
+ <xsd:element name="SignatureComments" type="ST_SignatureComments"/>
+ <xsd:element name="WindowsVersion" type="ST_Version"/>
+ <xsd:element name="OfficeVersion" type="ST_Version"/>
+ <xsd:element name="ApplicationVersion" type="ST_Version"/>
+ <xsd:element name="Monitors" type="ST_PositiveInteger"/>
+ <xsd:element name="HorizontalResolution" type="ST_PositiveInteger"/>
+ <xsd:element name="VerticalResolution" type="ST_PositiveInteger"/>
+ <xsd:element name="ColorDepth" type="ST_PositiveInteger"/>
+ <xsd:element name="SignatureProviderId" type="ST_UniqueIdentifierWithBraces"/>
+ <xsd:element name="SignatureProviderUrl" type="ST_SignatureProviderUrl"/>
+ <xsd:element name="SignatureProviderDetails" type="xsd:int"/>
+ <xsd:element name="SignatureType" type="ST_SignatureType"/>
+ </xsd:sequence>
+ </xsd:group>
+ <xsd:group name="EG_OptionalChildren">
+ <xsd:sequence>
+ <xsd:element name="DelegateSuggestedSigner" type="xsd:string"/>
+ <xsd:element name="DelegateSuggestedSigner2" type="xsd:string"/>
+ <xsd:element name="DelegateSuggestedSignerEmail" type="xsd:string"/>
+ <xsd:element name="ManifestHashAlgorithm" type="xsd:anyURI" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:group>
+ <xsd:group name="EG_OptionalChildrenV2">
+ <xsd:sequence>
+ <xsd:element name="Address1" type="xsd:string"/>
+ <xsd:element name="Address2" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:group>
+ <xsd:complexType name="CT_SignatureInfoV1">
+ <xsd:sequence>
+ <xsd:group ref="EG_RequiredChildren"/>
+ <xsd:group ref="EG_OptionalChildren" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CT_SignatureInfoV2">
+ <xsd:sequence>
+ <xsd:group ref="EG_OptionalChildrenV2" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="SignatureInfoV1" type="CT_SignatureInfoV1"/>
+ <xsd:element name="SignatureInfoV2" type="CT_SignatureInfoV2"/>
+</xsd:schema> \ No newline at end of file
diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java
new file mode 100644
index 0000000000..6d6592a2a3
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java
@@ -0,0 +1,328 @@
+/* ====================================================================
+ 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.poifs.crypt;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.security.InvalidKeyException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.SignatureException;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Date;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.poi.poifs.crypt.dsig.HorribleProxy;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.ASN1InputStreamIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.AuthorityInformationAccessIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.AuthorityKeyIdentifierIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.BasicConstraintsIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.BasicOCSPRespGeneratorIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.BasicOCSPRespIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.CRLNumberIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.CRLReasonIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.CertificateIDIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.CertificateStatusIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DERIA5StringIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DERSequenceIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DistributionPointIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.DistributionPointNameIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.GeneralNameIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.GeneralNamesIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.KeyUsageIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPReqGeneratorIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPReqIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPRespGeneratorIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.OCSPRespIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.ReqIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.RevokedStatusIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.SubjectKeyIdentifierIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.SubjectPublicKeyInfoIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509ExtensionsIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509ObjectIdentifiersIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509PrincipalIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509V2CRLGeneratorIf;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.X509V3CertificateGeneratorIf;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class PkiTestUtils {
+
+ private PkiTestUtils() {
+ super();
+ }
+
+ static KeyPair generateKeyPair() throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ SecureRandom random = new SecureRandom();
+ keyPairGenerator.initialize(new RSAKeyGenParameterSpec(1024,
+ RSAKeyGenParameterSpec.F4), random);
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ return keyPair;
+ }
+
+ private static SubjectKeyIdentifierIf createSubjectKeyId(PublicKey publicKey)
+ throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException
+ , IllegalAccessException, InvocationTargetException, NoSuchFieldException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded());
+ ASN1InputStreamIf asnObj = HorribleProxy.newProxy(ASN1InputStreamIf.class, bais);
+ SubjectPublicKeyInfoIf info =
+ HorribleProxy.newProxy(SubjectPublicKeyInfoIf.class, asnObj.readObject$Sequence());
+ SubjectKeyIdentifierIf keyId = HorribleProxy.newProxy(SubjectKeyIdentifierIf.class, info);
+ return keyId;
+ }
+
+ private static AuthorityKeyIdentifierIf createAuthorityKeyId(PublicKey publicKey)
+ throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException
+ , IllegalAccessException, InvocationTargetException, NoSuchFieldException {
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded());
+ ASN1InputStreamIf asnObj = HorribleProxy.newProxy(ASN1InputStreamIf.class, bais);
+ SubjectPublicKeyInfoIf info =
+ HorribleProxy.newProxy(SubjectPublicKeyInfoIf.class, asnObj.readObject$Sequence());
+ AuthorityKeyIdentifierIf keyId = HorribleProxy.newProxy(AuthorityKeyIdentifierIf.class, info);
+
+ return keyId;
+ }
+
+ static X509Certificate generateCertificate(PublicKey subjectPublicKey,
+ String subjectDn, Date notBefore, Date notAfter,
+ X509Certificate issuerCertificate, PrivateKey issuerPrivateKey,
+ boolean caFlag, int pathLength, String crlUri, String ocspUri,
+ KeyUsageIf keyUsage)
+ throws IOException, InvalidKeyException, IllegalStateException, NoSuchAlgorithmException
+ , SignatureException, CertificateException, InvocationTargetException, IllegalAccessException
+ , InstantiationException, NoSuchMethodException, ClassNotFoundException, NoSuchFieldException
+ {
+ String signatureAlgorithm = "SHA1withRSA";
+ X509V3CertificateGeneratorIf certificateGenerator = HorribleProxy.newProxy(X509V3CertificateGeneratorIf.class);
+ certificateGenerator.reset();
+ certificateGenerator.setPublicKey(subjectPublicKey);
+ certificateGenerator.setSignatureAlgorithm(signatureAlgorithm);
+ certificateGenerator.setNotBefore(notBefore);
+ certificateGenerator.setNotAfter(notAfter);
+ X509PrincipalIf subjectDN = HorribleProxy.newProxy(X509PrincipalIf.class, subjectDn);
+ X509PrincipalIf issuerDN;
+ if (null != issuerCertificate) {
+ issuerDN = HorribleProxy.newProxy(X509PrincipalIf.class, issuerCertificate
+ .getSubjectX500Principal().toString());
+ } else {
+ issuerDN = subjectDN;
+ }
+ certificateGenerator.setIssuerDN(issuerDN);
+ certificateGenerator.setSubjectDN(subjectDN);
+ certificateGenerator.setSerialNumber(new BigInteger(128,
+ new SecureRandom()));
+
+ X509ExtensionsIf X509Extensions = HorribleProxy.newProxy(X509ExtensionsIf.class);
+
+ certificateGenerator.addExtension(X509Extensions.SubjectKeyIdentifier(),
+ false, createSubjectKeyId(subjectPublicKey));
+ PublicKey issuerPublicKey;
+ issuerPublicKey = subjectPublicKey;
+ certificateGenerator.addExtension(
+ X509Extensions.AuthorityKeyIdentifier(), false,
+ createAuthorityKeyId(issuerPublicKey));
+
+ if (caFlag) {
+ BasicConstraintsIf bc;
+
+ if (-1 == pathLength) {
+ bc = HorribleProxy.newProxy(BasicConstraintsIf.class, true);
+ } else {
+ bc = HorribleProxy.newProxy(BasicConstraintsIf.class, pathLength);
+ }
+ certificateGenerator.addExtension(X509Extensions.BasicConstraints(), false, bc);
+ }
+
+ if (null != crlUri) {
+ GeneralNameIf gn = HorribleProxy.newProxy(GeneralNameIf.class);
+ int uri = gn.uniformResourceIdentifier();
+ DERIA5StringIf crlUriDer = HorribleProxy.newProxy(DERIA5StringIf.class, crlUri);
+ gn = HorribleProxy.newProxy(GeneralNameIf.class, uri, crlUriDer);
+
+ DERSequenceIf gnDer = HorribleProxy.newProxy(DERSequenceIf.class, gn);
+ GeneralNamesIf gns = HorribleProxy.newProxy(GeneralNamesIf.class, gnDer);
+
+ DistributionPointNameIf dpn = HorribleProxy.newProxy(DistributionPointNameIf.class, 0, gns);
+ DistributionPointIf distp = HorribleProxy.newProxy(DistributionPointIf.class, dpn, null, null);
+ DERSequenceIf distpDer = HorribleProxy.newProxy(DERSequenceIf.class, distp);
+ certificateGenerator.addExtension(X509Extensions.CRLDistributionPoints(), false, distpDer);
+ }
+
+ if (null != ocspUri) {
+ GeneralNameIf ocspName = HorribleProxy.newProxy(GeneralNameIf.class);
+ int uri = ocspName.uniformResourceIdentifier();
+ ocspName = HorribleProxy.newProxy(GeneralNameIf.class, uri, ocspUri);
+
+ X509ObjectIdentifiersIf X509ObjectIdentifiers = HorribleProxy.newProxy(X509ObjectIdentifiersIf.class);
+ AuthorityInformationAccessIf authorityInformationAccess =
+ HorribleProxy.newProxy(AuthorityInformationAccessIf.class
+ , X509ObjectIdentifiers.ocspAccessMethod(), ocspName);
+
+ certificateGenerator.addExtension(
+ X509Extensions.AuthorityInfoAccess(), false,
+ authorityInformationAccess);
+ }
+
+ if (null != keyUsage) {
+ certificateGenerator.addExtension(X509Extensions.KeyUsage(), true, keyUsage);
+ }
+
+ X509Certificate certificate;
+ certificate = certificateGenerator.generate(issuerPrivateKey);
+
+ /*
+ * Next certificate factory trick is needed to make sure that the
+ * certificate delivered to the caller is provided by the default
+ * security provider instead of BouncyCastle. If we don't do this trick
+ * we might run into trouble when trying to use the CertPath validator.
+ */
+ CertificateFactory certificateFactory = CertificateFactory
+ .getInstance("X.509");
+ certificate = (X509Certificate) certificateFactory
+ .generateCertificate(new ByteArrayInputStream(certificate
+ .getEncoded()));
+ return certificate;
+ }
+
+ static Document loadDocument(InputStream documentInputStream)
+ throws ParserConfigurationException, SAXException, IOException {
+ InputSource inputSource = new InputSource(documentInputStream);
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
+ .newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory
+ .newDocumentBuilder();
+ Document document = documentBuilder.parse(inputSource);
+ return document;
+ }
+
+ static String toString(Node dom) throws TransformerException {
+ Source source = new DOMSource(dom);
+ StringWriter stringWriter = new StringWriter();
+ Result result = new StreamResult(stringWriter);
+ TransformerFactory transformerFactory = TransformerFactory
+ .newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ /*
+ * We have to omit the ?xml declaration if we want to embed the
+ * document.
+ */
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.transform(source, result);
+ return stringWriter.getBuffer().toString();
+ }
+
+ public static X509CRL generateCrl(X509Certificate issuer,
+ PrivateKey issuerPrivateKey) throws InvalidKeyException,
+ CRLException, IllegalStateException, NoSuchAlgorithmException,
+ SignatureException, InvocationTargetException, IllegalAccessException,
+ InstantiationException, NoSuchMethodException, ClassNotFoundException, NoSuchFieldException {
+ X509V2CRLGeneratorIf crlGenerator = HorribleProxy.newProxy(X509V2CRLGeneratorIf.class);
+ crlGenerator.setIssuerDN(issuer.getSubjectX500Principal());
+ Date now = new Date();
+ crlGenerator.setThisUpdate(now);
+ crlGenerator.setNextUpdate(new Date(now.getTime() + 100000));
+ crlGenerator.setSignatureAlgorithm("SHA1withRSA");
+
+ X509ExtensionsIf X509Extensions = HorribleProxy.newProxy(X509ExtensionsIf.class);
+ CRLNumberIf crlNumber = HorribleProxy.newProxy(CRLNumberIf.class, new BigInteger("1234"));
+
+ crlGenerator.addExtension(X509Extensions.CRLNumber(), false, crlNumber);
+ X509CRL x509Crl = crlGenerator.generate(issuerPrivateKey);
+ return x509Crl;
+ }
+
+ public static OCSPRespIf createOcspResp(X509Certificate certificate,
+ boolean revoked, X509Certificate issuerCertificate,
+ X509Certificate ocspResponderCertificate,
+ PrivateKey ocspResponderPrivateKey, String signatureAlgorithm)
+ throws Exception {
+ // request
+ OCSPReqGeneratorIf ocspReqGenerator = HorribleProxy.newProxy(OCSPReqGeneratorIf.class);
+ CertificateIDIf certId = HorribleProxy.newProxy(CertificateIDIf.class);
+ certId = HorribleProxy.newProxy(CertificateIDIf.class, certId.HASH_SHA1(),
+ issuerCertificate, certificate.getSerialNumber());
+ ocspReqGenerator.addRequest(certId);
+ OCSPReqIf ocspReq = ocspReqGenerator.generate();
+
+ BasicOCSPRespGeneratorIf basicOCSPRespGenerator =
+ HorribleProxy.newProxy(BasicOCSPRespGeneratorIf.class, ocspResponderCertificate.getPublicKey());
+
+ // request processing
+ ReqIf[] requestList = ocspReq.getRequestList();
+ for (ReqIf ocspRequest : requestList) {
+ CertificateIDIf certificateID = ocspRequest.getCertID();
+ CertificateStatusIf certificateStatus;
+ if (revoked) {
+ CRLReasonIf crlr = HorribleProxy.newProxy(CRLReasonIf.class);
+ RevokedStatusIf rs = HorribleProxy.newProxy(RevokedStatusIf.class, new Date(), crlr.unspecified());
+ certificateStatus = HorribleProxy.newProxy(CertificateStatusIf.class, rs.getDelegate());
+ } else {
+ CertificateStatusIf cs = HorribleProxy.newProxy(CertificateStatusIf.class);
+ certificateStatus = cs.GOOD();
+ }
+ basicOCSPRespGenerator
+ .addResponse(certificateID, certificateStatus);
+ }
+
+ // basic response generation
+ X509Certificate[] chain = null;
+ if (!ocspResponderCertificate.equals(issuerCertificate)) {
+ chain = new X509Certificate[] { ocspResponderCertificate,
+ issuerCertificate };
+ }
+
+ BasicOCSPRespIf basicOCSPResp = basicOCSPRespGenerator.generate(
+ signatureAlgorithm, ocspResponderPrivateKey, chain, new Date(),
+ "BC");
+
+ // response generation
+ OCSPRespGeneratorIf ocspRespGenerator = HorribleProxy.newProxy(OCSPRespGeneratorIf.class);
+ OCSPRespIf ocspResp = ocspRespGenerator.generate(
+ ocspRespGenerator.SUCCESSFUL(), basicOCSPResp);
+
+ return ocspResp;
+ }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java
new file mode 100644
index 0000000000..f155620e2d
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java
@@ -0,0 +1,266 @@
+/* ====================================================================
+ 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.
+==================================================================== */
+
+/* ====================================================================
+ 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
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+package org.apache.poi.poifs.crypt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import javax.crypto.Cipher;
+
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackageAccess;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxy;
+import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
+import org.apache.poi.poifs.crypt.dsig.HorribleProxies.KeyUsageIf;
+import org.apache.poi.poifs.crypt.dsig.services.XmlSignatureService;
+import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestSignatureInfo {
+ private static final POILogger LOG = POILogFactory.getLogger(TestSignatureInfo.class);
+ private static final POIDataSamples testdata = POIDataSamples.getXmlDSignInstance();
+
+ private KeyPair keyPair = null;
+ private X509Certificate x509 = null;
+
+
+
+ @BeforeClass
+ public static void initBouncy() throws MalformedURLException {
+ File bcJar = testdata.getFile("bcprov-ext-jdk15on-1.49.jar");
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ URLClassLoader ucl = new URLClassLoader(new URL[]{bcJar.toURI().toURL()}, cl);
+ Thread.currentThread().setContextClassLoader(ucl);
+ }
+
+ @Test
+ public void getSignerUnsigned() throws Exception {
+ String testFiles[] = {
+ "hello-world-unsigned.docx",
+ "hello-world-unsigned.pptx",
+ "hello-world-unsigned.xlsx",
+ "hello-world-office-2010-technical-preview-unsigned.docx"
+ };
+
+ for (String testFile : testFiles) {
+ OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ);
+ SignatureInfo si = new SignatureInfo(pkg);
+ List<X509Certificate> result = si.getSigners();
+ pkg.revert();
+ pkg.close();
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ }
+ }
+
+ @Test
+ public void getSigner() throws Exception {
+ String testFiles[] = {
+ "hyperlink-example-signed.docx",
+ "hello-world-signed.docx",
+ "hello-world-signed.pptx",
+ "hello-world-signed.xlsx",
+ "hello-world-office-2010-technical-preview.docx",
+ "ms-office-2010-signed.docx",
+ "ms-office-2010-signed.pptx",
+ "ms-office-2010-signed.xlsx",
+ "Office2010-SP1-XAdES-X-L.docx",
+ "signed.docx",
+ };
+
+ for (String testFile : testFiles) {
+ OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ);
+ SignatureInfo si = new SignatureInfo(pkg);
+ List<X509Certificate> result = si.getSigners();
+
+ assertNotNull(result);
+ assertEquals("test-file: "+testFile, 1, result.size());
+ X509Certificate signer = result.get(0);
+ LOG.log(POILogger.DEBUG, "signer: " + signer.getSubjectX500Principal());
+
+ boolean b = si.verifySignature();
+ assertTrue("test-file: "+testFile, b);
+ pkg.revert();
+ }
+ }
+
+ @Test
+ public void getMultiSigners() throws Exception {
+ String testFile = "hello-world-signed-twice.docx";
+ OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ);
+ SignatureInfo si = new SignatureInfo(pkg);
+ List<X509Certificate> result = si.getSigners();
+
+ assertNotNull(result);
+ assertEquals("test-file: "+testFile, 2, result.size());
+ X509Certificate signer1 = result.get(0);
+ X509Certificate signer2 = result.get(1);
+ LOG.log(POILogger.DEBUG, "signer 1: " + signer1.getSubjectX500Principal());
+ LOG.log(POILogger.DEBUG, "signer 2: " + signer2.getSubjectX500Principal());
+
+ boolean b = si.verifySignature();
+ assertTrue("test-file: "+testFile, b);
+ pkg.revert();
+ }
+
+ @Test
+ public void testSignSpreadsheet() throws Exception {
+ String testFile = "hello-world-unsigned.xlsx";
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+ sign(pkg, "Test", "CN=Test", 1);
+ pkg.close();
+ }
+
+ @Test
+ public void testSignSpreadsheetWithSignatureInfo() throws Exception {
+ String testFile = "hello-world-unsigned.xlsx";
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+ SignatureInfo si = new SignatureInfo(pkg);
+ initKeyPair("Test", "CN=Test");
+ si.confirmSignature(keyPair.getPrivate(), x509, HashAlgorithm.sha1);
+ List<X509Certificate> signer = si.getSigners();
+ assertEquals(1, signer.size());
+ pkg.close();
+ }
+
+
+ private OPCPackage sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception {
+ /*** TODO : set cal to now ... only set to fixed date for debugging ... */
+ Calendar cal = Calendar.getInstance();
+ cal.clear();
+ cal.setTimeZone(TimeZone.getTimeZone("UTC"));
+ cal.set(2014, 7, 6, 21, 42, 12);
+
+ XmlSignatureService signatureService = new XmlSignatureService(HashAlgorithm.sha1, pkgCopy);
+ signatureService.initFacets(cal.getTime());
+ initKeyPair(alias, signerDn);
+
+ // operate
+ List<X509Certificate> x509Chain = Collections.singletonList(x509);
+ DigestInfo digestInfo = signatureService.preSign(null, x509Chain, null, null, null);
+
+ // verify
+ assertNotNull(digestInfo);
+ LOG.log(POILogger.DEBUG, "digest algo: " + digestInfo.hashAlgo);
+ LOG.log(POILogger.DEBUG, "digest description: " + digestInfo.description);
+ assertEquals("Office OpenXML Document", digestInfo.description);
+ assertNotNull(digestInfo.hashAlgo);
+ assertNotNull(digestInfo.digestValue);
+
+ // setup: key material, signature value
+
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
+ cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate());
+ ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream();
+ digestInfoValueBuf.write(SignatureInfo.SHA1_DIGEST_INFO_PREFIX);
+ digestInfoValueBuf.write(digestInfo.digestValue);
+ byte[] digestInfoValue = digestInfoValueBuf.toByteArray();
+ byte[] signatureValue = cipher.doFinal(digestInfoValue);
+
+ // operate: postSign
+ signatureService.postSign(signatureValue, Collections.singletonList(x509));
+
+ // verify: signature
+ SignatureInfo si = new SignatureInfo(pkgCopy);
+ List<X509Certificate> signers = si.getSigners();
+ assertEquals(signerCount, signers.size());
+
+ return pkgCopy;
+ }
+
+ private void initKeyPair(String alias, String subjectDN) 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);
+ keystore.load(fis, password);
+ fis.close();
+ } else {
+ keystore.load(null, password);
+ }
+
+ if (keystore.isKeyEntry(alias)) {
+ Key key = keystore.getKey(alias, password);
+ x509 = (X509Certificate)keystore.getCertificate(alias);
+ keyPair = new KeyPair(x509.getPublicKey(), (PrivateKey)key);
+ } else {
+ keyPair = PkiTestUtils.generateKeyPair();
+ Calendar cal = Calendar.getInstance();
+ Date notBefore = cal.getTime();
+ cal.add(Calendar.YEAR, 1);
+ Date notAfter = cal.getTime();
+ KeyUsageIf keyUsage = HorribleProxy.newProxy(KeyUsageIf.class);
+ keyUsage = HorribleProxy.newProxy(KeyUsageIf.class, keyUsage.digitalSignature());
+
+ x509 = PkiTestUtils.generateCertificate(keyPair.getPublic(), subjectDN
+ , 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();
+ }
+ }
+
+ private static File copy(File input) throws IOException {
+ String extension = input.getName().replaceAll(".*?(\\.[^.]+)?$", "$1");
+ if (extension == null || "".equals(extension)) extension = ".zip";
+ File tmpFile = new File("build", "sigtest"+extension);
+ FileOutputStream fos = new FileOutputStream(tmpFile);
+ FileInputStream fis = new FileInputStream(input);
+ IOUtils.copy(fis, fos);
+ fis.close();
+ fos.close();
+ return tmpFile;
+ }
+}
diff --git a/src/testcases/org/apache/poi/POIDataSamples.java b/src/testcases/org/apache/poi/POIDataSamples.java
index b335215390..a62e664e1e 100644
--- a/src/testcases/org/apache/poi/POIDataSamples.java
+++ b/src/testcases/org/apache/poi/POIDataSamples.java
@@ -44,6 +44,7 @@ public final class POIDataSamples {
private static POIDataSamples _instHPSF;
private static POIDataSamples _instHPBF;
private static POIDataSamples _instHSMF;
+ private static POIDataSamples _instXmlDSign;
private File _resolvedDataDir;
/** <code>true</code> if standard system propery is not set,
@@ -114,6 +115,12 @@ public final class POIDataSamples {
if(_instHSMF == null) _instHSMF = new POIDataSamples("hsmf");
return _instHSMF;
}
+
+ public static POIDataSamples getXmlDSignInstance(){
+ if(_instXmlDSign == null) _instXmlDSign = new POIDataSamples("xmldsign");
+ return _instXmlDSign;
+ }
+
/**
* Opens a sample file from the test data directory
*
diff --git a/test-data/xmldsign/Office2010-SP1-XAdES-X-L.docx b/test-data/xmldsign/Office2010-SP1-XAdES-X-L.docx
new file mode 100644
index 0000000000..4aaa772a0d
--- /dev/null
+++ b/test-data/xmldsign/Office2010-SP1-XAdES-X-L.docx
Binary files differ
diff --git a/test-data/xmldsign/bcprov-ext-jdk15on-1.49.jar b/test-data/xmldsign/bcprov-ext-jdk15on-1.49.jar
new file mode 100644
index 0000000000..cb0e0be8cd
--- /dev/null
+++ b/test-data/xmldsign/bcprov-ext-jdk15on-1.49.jar
Binary files differ
diff --git a/test-data/xmldsign/hello-world-office-2010-technical-preview-unsigned.docx b/test-data/xmldsign/hello-world-office-2010-technical-preview-unsigned.docx
new file mode 100644
index 0000000000..5162b67c60
--- /dev/null
+++ b/test-data/xmldsign/hello-world-office-2010-technical-preview-unsigned.docx
Binary files differ
diff --git a/test-data/xmldsign/hello-world-office-2010-technical-preview.docx b/test-data/xmldsign/hello-world-office-2010-technical-preview.docx
new file mode 100644
index 0000000000..cbd4277564
--- /dev/null
+++ b/test-data/xmldsign/hello-world-office-2010-technical-preview.docx
Binary files differ
diff --git a/test-data/xmldsign/hello-world-signed-twice.docx b/test-data/xmldsign/hello-world-signed-twice.docx
new file mode 100644
index 0000000000..96c91e957e
--- /dev/null
+++ b/test-data/xmldsign/hello-world-signed-twice.docx
Binary files differ
diff --git a/test-data/xmldsign/hello-world-signed.docx b/test-data/xmldsign/hello-world-signed.docx
new file mode 100644
index 0000000000..79a7bbb81f
--- /dev/null
+++ b/test-data/xmldsign/hello-world-signed.docx
Binary files differ
diff --git a/test-data/xmldsign/hello-world-signed.pptx b/test-data/xmldsign/hello-world-signed.pptx
new file mode 100644
index 0000000000..9b37033f54
--- /dev/null
+++ b/test-data/xmldsign/hello-world-signed.pptx
Binary files differ
diff --git a/test-data/xmldsign/hello-world-signed.xlsx b/test-data/xmldsign/hello-world-signed.xlsx
new file mode 100644
index 0000000000..0d45c53ede
--- /dev/null
+++ b/test-data/xmldsign/hello-world-signed.xlsx
Binary files differ
diff --git a/test-data/xmldsign/hello-world-unsigned.docx b/test-data/xmldsign/hello-world-unsigned.docx
new file mode 100644
index 0000000000..1790c961ce
--- /dev/null
+++ b/test-data/xmldsign/hello-world-unsigned.docx
Binary files differ
diff --git a/test-data/xmldsign/hello-world-unsigned.pptx b/test-data/xmldsign/hello-world-unsigned.pptx
new file mode 100644
index 0000000000..ca42529a9a
--- /dev/null
+++ b/test-data/xmldsign/hello-world-unsigned.pptx
Binary files differ
diff --git a/test-data/xmldsign/hello-world-unsigned.xlsx b/test-data/xmldsign/hello-world-unsigned.xlsx
new file mode 100644
index 0000000000..b99143e92c
--- /dev/null
+++ b/test-data/xmldsign/hello-world-unsigned.xlsx
Binary files differ
diff --git a/test-data/xmldsign/hyperlink-example-signed.docx b/test-data/xmldsign/hyperlink-example-signed.docx
new file mode 100644
index 0000000000..f8698fe4d7
--- /dev/null
+++ b/test-data/xmldsign/hyperlink-example-signed.docx
Binary files differ
diff --git a/test-data/xmldsign/ms-office-2010-signed.docx b/test-data/xmldsign/ms-office-2010-signed.docx
new file mode 100644
index 0000000000..61e4e2a1b8
--- /dev/null
+++ b/test-data/xmldsign/ms-office-2010-signed.docx
Binary files differ
diff --git a/test-data/xmldsign/ms-office-2010-signed.pptx b/test-data/xmldsign/ms-office-2010-signed.pptx
new file mode 100644
index 0000000000..c70f153248
--- /dev/null
+++ b/test-data/xmldsign/ms-office-2010-signed.pptx
Binary files differ
diff --git a/test-data/xmldsign/ms-office-2010-signed.xlsx b/test-data/xmldsign/ms-office-2010-signed.xlsx
new file mode 100644
index 0000000000..d8ba05b1da
--- /dev/null
+++ b/test-data/xmldsign/ms-office-2010-signed.xlsx
Binary files differ
diff --git a/test-data/xmldsign/signed.docx b/test-data/xmldsign/signed.docx
new file mode 100644
index 0000000000..98f3b8d841
--- /dev/null
+++ b/test-data/xmldsign/signed.docx
Binary files differ