]> source.dussan.org Git - poi.git/commitdiff
Xml signature support - version 1
authorAndreas Beeker <kiwiwings@apache.org>
Sun, 10 Aug 2014 18:25:10 +0000 (18:25 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Sun, 10 Aug 2014 18:25:10 +0000 (18:25 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/xml_signature@1617141 13f79535-47bb-0310-9956-ffa450edef68

61 files changed:
.classpath
build.xml
src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java
src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java
src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java
src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java
src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java
src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/CertificateSecurityException.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/ExpiredCertificateSecurityException.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxies.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/HorribleProxy.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/KeyInfoKeySelector.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/OOXMLURIDereferencer.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/RevokedCertificateSecurityException.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/TrustCertificateSecurityException.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignaturePolicyService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationData.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationDataService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignatureService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TSPTimeStampService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampServiceValidator.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/XmlSignatureService.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/Constants.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/util/MethodUtils.java [new file with mode: 0644]
src/ooxml/java/org/apache/poi/util/SAXHelper.java
src/ooxml/java/org/apache/poi/util/XmlSort.java [new file with mode: 0644]
src/ooxml/resources/org/apache/poi/poifs/crypt/XAdES.xsd [new file with mode: 0644]
src/ooxml/resources/org/apache/poi/poifs/crypt/XAdESv141.xsd [new file with mode: 0644]
src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java [new file with mode: 0644]
src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java [new file with mode: 0644]
src/testcases/org/apache/poi/POIDataSamples.java
test-data/xmldsign/Office2010-SP1-XAdES-X-L.docx [new file with mode: 0644]
test-data/xmldsign/bcprov-ext-jdk15on-1.49.jar [new file with mode: 0644]
test-data/xmldsign/hello-world-office-2010-technical-preview-unsigned.docx [new file with mode: 0644]
test-data/xmldsign/hello-world-office-2010-technical-preview.docx [new file with mode: 0644]
test-data/xmldsign/hello-world-signed-twice.docx [new file with mode: 0644]
test-data/xmldsign/hello-world-signed.docx [new file with mode: 0644]
test-data/xmldsign/hello-world-signed.pptx [new file with mode: 0644]
test-data/xmldsign/hello-world-signed.xlsx [new file with mode: 0644]
test-data/xmldsign/hello-world-unsigned.docx [new file with mode: 0644]
test-data/xmldsign/hello-world-unsigned.pptx [new file with mode: 0644]
test-data/xmldsign/hello-world-unsigned.xlsx [new file with mode: 0644]
test-data/xmldsign/hyperlink-example-signed.docx [new file with mode: 0644]
test-data/xmldsign/ms-office-2010-signed.docx [new file with mode: 0644]
test-data/xmldsign/ms-office-2010-signed.pptx [new file with mode: 0644]
test-data/xmldsign/ms-office-2010-signed.xlsx [new file with mode: 0644]
test-data/xmldsign/signed.docx [new file with mode: 0644]

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