diff options
57 files changed, 6914 insertions, 92 deletions
diff --git a/.classpath b/.classpath index a7dc0d8fc5..313dbdb1e8 100644 --- a/.classpath +++ b/.classpath @@ -1,29 +1,33 @@ -<?xml version="1.0" encoding="UTF-8"?> -<classpath> - <classpathentry kind="src" path="src/java"/> - <classpathentry kind="src" path="src/testcases"/> - <classpathentry kind="src" path="src/resources/main"/> - <classpathentry kind="src" path="src/ooxml/java"/> - <classpathentry kind="src" path="src/ooxml/testcases"/> - <classpathentry kind="src" path="src/resources/ooxml"/> - <classpathentry kind="src" path="src/scratchpad/src"/> - <classpathentry kind="src" path="src/scratchpad/testcases"/> - <classpathentry kind="src" path="src/resources/scratchpad"/> - <classpathentry kind="src" path="src/contrib/poi-ruby/java"/> - <classpathentry kind="src" path="src/examples/src"/> - <classpathentry kind="src" path="src/excelant/java"/> - <classpathentry kind="src" path="src/excelant/testcases"/> - <classpathentry kind="src" path="src/excelant/resources"/> - <classpathentry kind="lib" path="lib/ant-1.9.4.jar"/> - <classpathentry kind="lib" path="lib/ant-launcher-1.9.4.jar"/> - <classpathentry kind="lib" path="lib/commons-codec-1.9.jar"/> - <classpathentry kind="lib" path="lib/commons-logging-1.1.3.jar"/> - <classpathentry kind="lib" path="lib/log4j-1.2.17.jar"/> - <classpathentry kind="lib" path="ooxml-lib/xmlbeans-2.6.0.jar"/> - <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="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> - <classpathentry kind="output" path="build/eclipse"/> -</classpath> +<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/java"/>
+ <classpathentry kind="src" path="src/testcases"/>
+ <classpathentry kind="src" path="src/resources/main"/>
+ <classpathentry kind="src" path="src/ooxml/java"/>
+ <classpathentry kind="src" path="src/ooxml/testcases"/>
+ <classpathentry kind="src" path="src/resources/ooxml"/>
+ <classpathentry kind="src" path="src/scratchpad/src"/>
+ <classpathentry kind="src" path="src/scratchpad/testcases"/>
+ <classpathentry kind="src" path="src/resources/scratchpad"/>
+ <classpathentry kind="src" path="src/contrib/poi-ruby/java"/>
+ <classpathentry kind="src" path="src/examples/src"/>
+ <classpathentry kind="src" path="src/excelant/java"/>
+ <classpathentry kind="src" path="src/excelant/testcases"/>
+ <classpathentry kind="src" path="src/excelant/resources"/>
+ <classpathentry kind="lib" path="lib/ant-1.9.4.jar"/>
+ <classpathentry kind="lib" path="lib/ant-launcher-1.9.4.jar"/>
+ <classpathentry kind="lib" path="lib/commons-codec-1.9.jar"/>
+ <classpathentry kind="lib" path="lib/commons-logging-1.1.3.jar"/>
+ <classpathentry kind="lib" path="lib/log4j-1.2.17.jar"/>
+ <classpathentry kind="lib" path="ooxml-lib/xmlbeans-2.6.0.jar"/>
+ <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.2.jar" sourcepath="ooxml-lib/ooxml-encryption-src-1.2.jar"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="lib" path="compile-lib/slf4j-api-1.7.7.jar"/>
+ <classpathentry kind="lib" path="compile-lib/bcpkix-jdk15on-1.51.jar"/>
+ <classpathentry kind="lib" path="compile-lib/bcprov-ext-jdk15on-1.51.jar"/>
+ <classpathentry kind="lib" path="compile-lib/xmlsec-2.0.1.jar"/>
+ <classpathentry kind="output" path="build/eclipse"/>
+</classpath>
diff --git a/.settings/org.sonar.ide.eclipse.core.prefs b/.settings/org.sonar.ide.eclipse.core.prefs new file mode 100644 index 0000000000..80551be6b1 --- /dev/null +++ b/.settings/org.sonar.ide.eclipse.core.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1
+extraProperties=
+lastAnalysisDate=1394235544000
+projectKey=org.apache.poi\:poi-main
+serverUrl=http\://nemo.sonarqube.org
+version=2
@@ -61,6 +61,7 @@ under the License. <property name="main.lib" location="lib"/> <property name="ooxml.lib" location="ooxml-lib"/> + <property name="compile.lib" location="compile-lib"/> <property name="forrest.home" value="${env.FORREST_HOME}"/> <!-- compiler options options --> @@ -118,7 +119,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"/> @@ -147,7 +147,17 @@ under the License. <property name="main.antlauncher.jar" location="${main.lib}/ant-launcher-1.9.4.jar"/> <property name="main.antlauncher.url" value="${repository.m2}/maven2/org/apache/ant/ant-launcher/1.9.4/ant-launcher-1.9.4.jar"/> - <!-- jars in the lib-ooxml directory, see the fetch-ooxml-jars target--> + <!-- xml signature libs --> + <property name="dsig.xmlsec.jar" location="${compile.lib}/xmlsec-2.0.1.jar"/> + <property name="dsig.xmlsec.url" value="${repository.m2}/maven2/org/apache/santuario/xmlsec/2.0.1/xmlsec-2.0.1.jar"/> + <property name="dsig.bouncycastle-prov.jar" location="${compile.lib}/bcprov-ext-jdk15on-1.51.jar"/> + <property name="dsig.bouncycastle-prov.url" value="${repository.m2}/maven2/org/bouncycastle/bcprov-ext-jdk15on/1.51/bcprov-ext-jdk15on-1.51.jar"/> + <property name="dsig.bouncycastle-pkix.jar" location="${compile.lib}/bcpkix-jdk15on-1.51.jar"/> + <property name="dsig.bouncycastle-pkix.url" value="${repository.m2}/maven2/org/bouncycastle/bcpkix-jdk15on/1.51/bcpkix-jdk15on-1.51.jar"/> + <property name="dsig.sl4j-api.jar" location="${compile.lib}/slf4j-api-1.7.7.jar"/> + <property name="dsig.sl4j-api.url" value="${repository.m2}/maven2/org/slf4j/slf4j-api/1.7.7/slf4j-api-1.7.7.jar"/> + + <!-- jars in the lib-ooxml directory, see the fetch-ooxml-jars target--> <property name="ooxml.xmlbeans23.jar" location="${ooxml.lib}/xmlbeans-2.3.0.jar"/> <property name="ooxml.xmlbeans23.url" value="${repository.m2}/maven2/org/apache/xmlbeans/xmlbeans/2.3.0/xmlbeans-2.3.0.jar"/> @@ -160,7 +170,7 @@ under the License. <property name="jacoco.url" value="${repository.m2}/maven2/org/jacoco/jacoco/0.7.1.201405082137/jacoco-0.7.1.201405082137.zip"/> <property name="asm.jar" location="${main.lib}/asm-all-5.0.3.jar"/> <property name="asm.url" value="${repository.m2}/maven2/org/ow2/asm/asm-all/5.0.3/asm-all-5.0.3.jar"/> - + <!-- for testing with older Xerces implementation --> <property name="xerces.jar" location="${main.lib}/xercesImpl-2.6.1.jar"/> <property name="xerces.url" value="${repository.m2}/maven2/xerces/xercesImpl/2.6.1//xercesImpl-2.6.1.jar"/> @@ -171,17 +181,30 @@ 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.1" value="http://www.w3.org/TR/2002/REC-xmldsig-core-20020212/xmldsig-core-schema.xsd"/> + <property name="ooxml.xsds.dsig.2" value="http://uri.etsi.org/01903/v1.3.2/XAdES.xsd"/> + <property name="ooxml.xsds.dsig.3" value="http://uri.etsi.org/01903/v1.4.1/XAdESv141.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"/> @@ -222,6 +245,13 @@ under the License. <pathelement location="${main.output.dir}"/> </path> + <path id="ooxml.xmlsec.classpath"> + <pathelement location="${dsig.xmlsec.jar}"/> + <pathelement location="${dsig.bouncycastle-prov.jar}"/> + <pathelement location="${dsig.bouncycastle-pkix.jar}"/> + <pathelement location="${dsig.sl4j-api.jar}"/> + </path> + <path id="ooxml.classpath"> <pathelement location="${ooxml.xmlbeans26.jar}"/> <pathelement location="${ooxml.xsds.jar}"/> @@ -229,6 +259,7 @@ under the License. <pathelement location="${main.output.dir}"/> <pathelement location="${scratchpad.output.dir}"/> <pathelement location="${ooxml.encryption.jar}"/> + <path refid="ooxml.xmlsec.classpath"/> </path> <path id="test.classpath"> @@ -361,7 +392,7 @@ under the License. </fileset> </delete> - <condition property="jars.present"> + <condition property="jars.present"> <or> <and> <available file="${main.commons-logging.jar}"/> @@ -375,6 +406,10 @@ under the License. <available file="${jacoco.zip}"/> <available file="${rat.jar}"/> <available file="${xerces.jar}"/> + <available file="${dsig.bouncycastle-prov.jar}"/> + <available file="${dsig.bouncycastle-pkix.jar}"/> + <available file="${dsig.xmlsec.jar}"/> + <available file="${dsig.sl4j-api.jar}"/> </and> <isset property="disconnected"/> </or> @@ -433,6 +468,22 @@ under the License. <param name="sourcefile" value="${rat.url}"/> <param name="destfile" value="${rat.jar}"/> </antcall> + <antcall target="downloadfile"> + <param name="sourcefile" value="${dsig.bouncycastle-prov.url}"/> + <param name="destfile" value="${dsig.bouncycastle-prov.jar}"/> + </antcall> + <antcall target="downloadfile"> + <param name="sourcefile" value="${dsig.bouncycastle-pkix.url}"/> + <param name="destfile" value="${dsig.bouncycastle-pkix.jar}"/> + </antcall> + <antcall target="downloadfile"> + <param name="sourcefile" value="${dsig.xmlsec.url}"/> + <param name="destfile" value="${dsig.xmlsec.jar}"/> + </antcall> + <antcall target="downloadfile"> + <param name="sourcefile" value="${dsig.sl4j-api.url}"/> + <param name="destfile" value="${dsig.sl4j-api.jar}"/> + </antcall> </target> <target name="check-ooxml-jars"> @@ -463,19 +514,37 @@ 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.1}"/> + <url url="${ooxml.xsds.dsig.2}"/> + <url url="${ooxml.xsds.dsig.3}"/> + <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> @@ -486,19 +555,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}"/> @@ -510,11 +570,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}" @@ -528,41 +586,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,XAdES*.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}" @@ -655,7 +712,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}" @@ -676,6 +733,7 @@ under the License. includeantruntime="false"> <classpath> <path refid="ooxml.classpath"/> + <path refid="test.ooxml.classpath"/> <pathelement path="${ooxml.output.dir}"/> <pathelement path="${main.output.test.dir}"/> </classpath> @@ -1354,7 +1412,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"> @@ -1450,7 +1508,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="lib/commons-codec-1.9.jar" /> <auxClasspath path="lib/commons-logging-1.1.3.jar" /> diff --git a/src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java b/src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java index be507a6660..68682f496c 100644 --- a/src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java +++ b/src/java/org/apache/poi/poifs/crypt/CipherAlgorithm.java @@ -34,6 +34,8 @@ public enum CipherAlgorithm { // need bouncycastle provider for this one ...
// see http://stackoverflow.com/questions/4436397/3des-des-encryption-using-the-jce-generating-an-acceptable-key
des3_112(null, "DESede", -1, 128, new int[]{128}, 8, 32, "3DES_112", true),
+ // only for digital signatures
+ rsa(null, "RSA", -1, 1024, new int[]{1024, 2048, 3072, 4096}, -1, -1, "", false);
;
public final CipherProvider provider;
diff --git a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java index ffb7498f3f..875ade3ab9 100644 --- a/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java +++ b/src/java/org/apache/poi/poifs/crypt/CryptoFunctions.java @@ -19,6 +19,7 @@ package org.apache.poi.poifs.crypt; import java.nio.charset.Charset;
import java.security.DigestException;
import java.security.GeneralSecurityException;
+import java.security.Key;
import java.security.MessageDigest;
import java.security.Provider;
import java.security.Security;
@@ -195,7 +196,7 @@ public class CryptoFunctions { * @throws EncryptedDocumentException if the initialization failed or if an algorithm was specified,
* which depends on a missing bouncy castle provider
*/
- public static Cipher getCipher(SecretKey key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode, String padding) {
+ public static Cipher getCipher(Key key, CipherAlgorithm cipherAlgorithm, ChainingMode chain, byte[] vec, int cipherMode, String padding) {
int keySizeInBytes = key.getEncoded().length;
if (padding == null) padding = "NoPadding";
@@ -296,10 +297,12 @@ public class CryptoFunctions { }
@SuppressWarnings("unchecked")
- private static void registerBouncyCastle() {
+ public static void registerBouncyCastle() {
if (Security.getProvider("BC") != null) return;
try {
- Class<Provider> clazz = (Class<Provider>)Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ String bcProviderName = "org.bouncycastle.jce.provider.BouncyCastleProvider";
+ Class<Provider> clazz = (Class<Provider>)cl.loadClass(bcProviderName);
Security.addProvider(clazz.newInstance());
} catch (Exception e) {
throw new EncryptedDocumentException("Only the BouncyCastle provider supports your encryption settings - please add it to the classpath.");
diff --git a/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java b/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java index 61608822f8..6a490b0148 100644 --- a/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java +++ b/src/java/org/apache/poi/poifs/crypt/HashAlgorithm.java @@ -33,6 +33,8 @@ public enum HashAlgorithm { ripemd128("RipeMD128", -1, "RIPEMD-128", 16, "HMac-RipeMD128", true),
ripemd160("RipeMD160", -1, "RIPEMD-160", 20, "HMac-RipeMD160", true),
whirlpool("Whirlpool", -1, "WHIRLPOOL", 64, "HMac-Whirlpool", true),
+ // only for xml signing
+ sha224 ( "SHA-224", -1, "SHA224", 28, "HmacSHA224", true);
;
public final String jceId;
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java index 2a5ade28d6..7d4741b219 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationship.java @@ -182,8 +182,6 @@ public final class PackageRelationship { } /** - * public URI getSourceUri(){ } - * * @return the targetMode */ public TargetMode getTargetMode() { diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java index 043fd632f4..08d82fbaab 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/PackageRelationshipCollection.java @@ -307,7 +307,7 @@ public final class PackageRelationshipCollection implements * @throws InvalidFormatException * Throws if the relationship part is invalid. */ - private void parseRelationshipsPart(PackagePart relPart) + public void parseRelationshipsPart(PackagePart relPart) throws InvalidFormatException { try { logger.log(POILogger.DEBUG, "Parsing relationship: " + relPart.getPartName()); diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java index 581dabe000..59801d3aee 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ContentTypeManager.java @@ -145,11 +145,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); @@ -452,7 +451,7 @@ public abstract class ContentTypeManager { } /** - * Use to append default types XML elements, use by the save() metid. + * Use to append default types XML elements, use by the save() method. * * @param root * XML parent element use to append this default type element. diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/CertificateSecurityException.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/CertificateSecurityException.java new file mode 100644 index 0000000000..707ad758a8 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/CertificateSecurityException.java @@ -0,0 +1,38 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+/**
+ * Exception thrown in case there is something wrong with the incoming eID
+ * certificate.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class CertificateSecurityException extends SecurityException {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/ExpiredCertificateSecurityException.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/ExpiredCertificateSecurityException.java new file mode 100644 index 0000000000..adbcfdb35e --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/ExpiredCertificateSecurityException.java @@ -0,0 +1,38 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+/**
+ * Exception thrown in case the incoming eID certificate is expired.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class ExpiredCertificateSecurityException extends
+ CertificateSecurityException {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/KeyInfoKeySelector.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/KeyInfoKeySelector.java new file mode 100644 index 0000000000..61fedcb9ec --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/KeyInfoKeySelector.java @@ -0,0 +1,103 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import java.security.Key;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.crypto.AlgorithmMethod;
+import javax.xml.crypto.KeySelector;
+import javax.xml.crypto.KeySelectorException;
+import javax.xml.crypto.KeySelectorResult;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.X509Data;
+
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * JSR105 key selector implementation using the ds:KeyInfo data of the signature
+ * itself.
+ */
+public class KeyInfoKeySelector extends KeySelector implements KeySelectorResult {
+
+ private static final POILogger LOG = POILogFactory.getLogger(KeyInfoKeySelector.class);
+
+ private List<X509Certificate> certChain = new ArrayList<X509Certificate>();
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public KeySelectorResult select(KeyInfo keyInfo, Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException {
+ LOG.log(POILogger.DEBUG, "select key");
+ if (null == keyInfo) {
+ throw new KeySelectorException("no ds:KeyInfo present");
+ }
+ List<XMLStructure> keyInfoContent = keyInfo.getContent();
+ certChain.clear();
+ for (XMLStructure keyInfoStructure : keyInfoContent) {
+ if (!(keyInfoStructure instanceof X509Data)) {
+ continue;
+ }
+ X509Data x509Data = (X509Data) keyInfoStructure;
+ List<Object> x509DataList = x509Data.getContent();
+ for (Object x509DataObject : x509DataList) {
+ if (!(x509DataObject instanceof X509Certificate)) {
+ continue;
+ }
+ X509Certificate certificate = (X509Certificate) x509DataObject;
+ LOG.log(POILogger.DEBUG, "certificate", certificate.getSubjectX500Principal());
+ certChain.add(certificate);
+ }
+ }
+ if (certChain.isEmpty()) {
+ throw new KeySelectorException("No key found!");
+ }
+ return this;
+ }
+
+ public Key getKey() {
+ // The first certificate is presumably the signer.
+ return certChain.isEmpty() ? null : certChain.get(0).getPublicKey();
+ }
+
+ /**
+ * Gives back the X509 certificate used during the last signature
+ * verification operation.
+ *
+ * @return
+ */
+ public X509Certificate getSigner() {
+ // The first certificate is presumably the signer.
+ return certChain.isEmpty() ? null : certChain.get(0);
+ }
+
+ public List<X509Certificate> getCertChain() {
+ return certChain;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/OOXMLURIDereferencer.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/OOXMLURIDereferencer.java new file mode 100644 index 0000000000..50dd68ddef --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/OOXMLURIDereferencer.java @@ -0,0 +1,113 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.xml.crypto.Data;
+import javax.xml.crypto.OctetStreamData;
+import javax.xml.crypto.URIDereferencer;
+import javax.xml.crypto.URIReference;
+import javax.xml.crypto.URIReferenceException;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackagePartName;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * JSR105 URI dereferencer for Office Open XML documents.
+ */
+public class OOXMLURIDereferencer implements URIDereferencer, SignatureConfigurable {
+
+ private static final POILogger LOG = POILogFactory.getLogger(OOXMLURIDereferencer.class);
+
+ private SignatureConfig signatureConfig;
+ private URIDereferencer baseUriDereferencer;
+
+ public OOXMLURIDereferencer() {
+ XMLSignatureFactory xmlSignatureFactory = SignatureInfo.getSignatureFactory();
+ this.baseUriDereferencer = xmlSignatureFactory.getURIDereferencer();
+ }
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+
+ public Data dereference(URIReference uriReference, XMLCryptoContext context) throws URIReferenceException {
+ if (null == uriReference) {
+ throw new NullPointerException("URIReference cannot be null");
+ }
+ if (null == context) {
+ throw new NullPointerException("XMLCrytoContext cannot be null");
+ }
+
+ URI uri;
+ try {
+ uri = new URI(uriReference.getURI());
+ } catch (URISyntaxException e) {
+ throw new URIReferenceException("could not URL decode the uri: "+uriReference.getURI(), e);
+ }
+
+ PackagePart part = findPart(uri);
+ if (part == null) {
+ LOG.log(POILogger.DEBUG, "cannot resolve, delegating to base DOM URI dereferencer", uri);
+ return this.baseUriDereferencer.dereference(uriReference, context);
+ }
+
+ try {
+ return new OctetStreamData(part.getInputStream(), uri.toString(), null);
+ } catch (IOException e) {
+ throw new URIReferenceException("I/O error: " + e.getMessage(), e);
+ }
+ }
+
+ private PackagePart findPart(URI uri) {
+ LOG.log(POILogger.DEBUG, "dereference", uri);
+
+ String path = uri.getPath();
+ if (path == null || "".equals(path)) {
+ LOG.log(POILogger.DEBUG, "illegal part name (expected)", uri);
+ return null;
+ }
+
+ PackagePartName ppn;
+ try {
+ ppn = PackagingURIHelper.createPartName(path);
+ } catch (InvalidFormatException e) {
+ LOG.log(POILogger.WARN, "illegal part name (not expected)", uri);
+ return null;
+ }
+
+ return signatureConfig.getOpcPackage().getPart(ppn);
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/RevokedCertificateSecurityException.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/RevokedCertificateSecurityException.java new file mode 100644 index 0000000000..fa4fee7370 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/RevokedCertificateSecurityException.java @@ -0,0 +1,38 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+/**
+ * Exception thrown in case the incoming eID certificate has been revoked.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class RevokedCertificateSecurityException extends
+ CertificateSecurityException {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java new file mode 100644 index 0000000000..7c59fbcae0 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureConfig.java @@ -0,0 +1,505 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.OO_DIGSIG_NS;
+import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XADES_132_NS;
+
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.xml.crypto.URIDereferencer;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.OOXMLSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.Office2010SignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService;
+import org.apache.poi.poifs.crypt.dsig.services.SignaturePolicyService;
+import org.apache.poi.poifs.crypt.dsig.services.TSPTimeStampService;
+import org.apache.poi.poifs.crypt.dsig.services.TimeStampService;
+import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator;
+import org.apache.poi.poifs.crypt.dsig.spi.AddressDTO;
+import org.apache.poi.poifs.crypt.dsig.spi.IdentityDTO;
+import org.w3c.dom.events.EventListener;
+
+/**
+ * This class bundles the configuration options used for the existing
+ * signature facets.
+ * Apart of the opc-package (thread local) most values will probably be constant, so
+ * it might be configured centrally (e.g. by spring)
+ */
+public class SignatureConfig {
+
+ public static interface SignatureConfigurable {
+ void setSignatureConfig(SignatureConfig signatureConfig);
+ }
+
+ private ThreadLocal<OPCPackage> opcPackage = new ThreadLocal<OPCPackage>();
+
+ private List<SignatureFacet> signatureFacets = new ArrayList<SignatureFacet>();
+ private HashAlgorithm digestAlgo = HashAlgorithm.sha1;
+ private Date executionTime = new Date();
+ private PrivateKey key;
+ private List<X509Certificate> signingCertificateChain;
+ private IdentityDTO identity;
+ private AddressDTO address;
+ private byte[] photo;
+
+ /**
+ * the optional signature policy service used for XAdES-EPES.
+ */
+ private SignaturePolicyService signaturePolicyService;
+ private URIDereferencer uriDereferencer = new OOXMLURIDereferencer();
+ private String canonicalizationMethod = CanonicalizationMethod.INCLUSIVE;
+
+ private boolean includeEntireCertificateChain = true;
+ private boolean includeIssuerSerial = false;
+ private boolean includeKeyValue = false;
+
+ private TimeStampService tspService = new TSPTimeStampService();
+ // timestamp service provider URL
+ private String tspUrl;
+ private boolean tspOldProtocol = false;
+ /**
+ * if not defined, it's the same as the main digest
+ */
+ private HashAlgorithm tspDigestAlgo = null;
+ private String tspUser;
+ private String tspPass;
+ private TimeStampServiceValidator tspValidator;
+ /**
+ * the optional TSP request policy OID.
+ */
+ private String tspRequestPolicy = "1.3.6.1.4.1.13762.3";
+ private String userAgent = "POI XmlSign Service TSP Client";
+ private String proxyUrl;
+
+ /**
+ * the optional revocation data service used for XAdES-C and XAdES-X-L.
+ * When <code>null</code> the signature will be limited to XAdES-T only.
+ */
+ private RevocationDataService revocationDataService;
+ /**
+ * if not defined, it's the same as the main digest
+ */
+ private HashAlgorithm xadesDigestAlgo = null;
+ private String xadesRole = null;
+ private String xadesSignatureId = null;
+ private boolean xadesSignaturePolicyImplied = true;
+
+ /**
+ * Work-around for Office 2010 IssuerName encoding.
+ */
+ private boolean xadesIssuerNameNoReverseOrder = true;
+
+ /**
+ * The signature Id attribute value used to create the XML signature. A
+ * <code>null</code> value will trigger an automatically generated signature Id.
+ */
+ private String packageSignatureId = "idPackageSignature";
+
+ /**
+ * Gives back the human-readable description of what the citizen will be
+ * signing. The default value is "Office OpenXML Document".
+ */
+ private String signatureDescription = "Office OpenXML Document";
+
+ /**
+ * The process of signing includes the marshalling of xml structures.
+ * This also includes the canonicalization. Currently this leads to problems
+ * with certain namespaces, so this EventListener is used to interfere
+ * with the marshalling process.
+ */
+ EventListener signatureMarshalListener = null;
+
+ /**
+ * Map of namespace uris to prefix
+ * If a mapping is specified, the corresponding elements will be prefixed
+ */
+ Map<String,String> namespacePrefixes = new HashMap<String,String>();
+
+ protected void init(boolean onlyValidation) {
+ if (uriDereferencer == null) {
+ throw new EncryptedDocumentException("uriDereferencer is null");
+ }
+ if (opcPackage == null) {
+ throw new EncryptedDocumentException("opcPackage is null");
+ }
+ if (uriDereferencer instanceof SignatureConfigurable) {
+ ((SignatureConfigurable)uriDereferencer).setSignatureConfig(this);
+ }
+ if (namespacePrefixes.isEmpty()) {
+ /*
+ * OOo doesn't like ds namespaces so per default prefixing is off.
+ */
+ // namespacePrefixes.put(XML_DIGSIG_NS, "");
+ namespacePrefixes.put(OO_DIGSIG_NS, "mdssi");
+ namespacePrefixes.put(XADES_132_NS, "xd");
+ }
+
+ if (onlyValidation) return;
+
+ if (signatureMarshalListener == null) {
+ signatureMarshalListener = new SignatureMarshalListener();
+ }
+
+ if (signatureMarshalListener instanceof SignatureConfigurable) {
+ ((SignatureConfigurable)signatureMarshalListener).setSignatureConfig(this);
+ }
+
+ if (tspService != null) {
+ tspService.setSignatureConfig(this);
+ }
+
+ if (xadesSignatureId == null || xadesSignatureId.isEmpty()) {
+ xadesSignatureId = "idSignedProperties";
+ }
+
+ if (signatureFacets.isEmpty()) {
+ addSignatureFacet(new OOXMLSignatureFacet());
+ addSignatureFacet(new KeyInfoSignatureFacet());
+ addSignatureFacet(new XAdESSignatureFacet());
+ addSignatureFacet(new Office2010SignatureFacet());
+ }
+
+ for (SignatureFacet sf : signatureFacets) {
+ sf.setSignatureConfig(this);
+ }
+ }
+
+ public void addSignatureFacet(SignatureFacet sf) {
+ signatureFacets.add(sf);
+ }
+
+ /**
+ * Gives back the used XAdES signature facet.
+ *
+ * @return
+ */
+ public XAdESSignatureFacet getXAdESSignatureFacet() {
+ for (SignatureFacet sf : getSignatureFacets()) {
+ if (sf instanceof XAdESSignatureFacet) {
+ return (XAdESSignatureFacet)sf;
+ }
+ }
+ return null;
+ }
+
+
+ public List<SignatureFacet> getSignatureFacets() {
+ return signatureFacets;
+ }
+ public void setSignatureFacets(List<SignatureFacet> signatureFacets) {
+ this.signatureFacets = signatureFacets;
+ }
+ public HashAlgorithm getDigestAlgo() {
+ return digestAlgo;
+ }
+ public void setDigestAlgo(HashAlgorithm digestAlgo) {
+ this.digestAlgo = digestAlgo;
+ }
+ public OPCPackage getOpcPackage() {
+ return opcPackage.get();
+ }
+ public void setOpcPackage(OPCPackage opcPackage) {
+ this.opcPackage.set(opcPackage);
+ }
+ public PrivateKey getKey() {
+ return key;
+ }
+ public void setKey(PrivateKey key) {
+ this.key = key;
+ }
+ public List<X509Certificate> getSigningCertificateChain() {
+ return signingCertificateChain;
+ }
+ public void setSigningCertificateChain(
+ List<X509Certificate> signingCertificateChain) {
+ this.signingCertificateChain = signingCertificateChain;
+ }
+ public IdentityDTO getIdentity() {
+ return identity;
+ }
+ public void setIdentity(IdentityDTO identity) {
+ this.identity = identity;
+ }
+ public AddressDTO getAddress() {
+ return address;
+ }
+ public void setAddress(AddressDTO address) {
+ this.address = address;
+ }
+ public byte[] getPhoto() {
+ return photo;
+ }
+ public void setPhoto(byte[] photo) {
+ this.photo = photo;
+ }
+ public Date getExecutionTime() {
+ return executionTime;
+ }
+ public void setExecutionTime(Date executionTime) {
+ this.executionTime = executionTime;
+ }
+ public SignaturePolicyService getSignaturePolicyService() {
+ return signaturePolicyService;
+ }
+ public void setSignaturePolicyService(SignaturePolicyService signaturePolicyService) {
+ this.signaturePolicyService = signaturePolicyService;
+ }
+ public URIDereferencer getUriDereferencer() {
+ return uriDereferencer;
+ }
+ public void setUriDereferencer(URIDereferencer uriDereferencer) {
+ this.uriDereferencer = uriDereferencer;
+ }
+ public String getSignatureDescription() {
+ return signatureDescription;
+ }
+ public void setSignatureDescription(String signatureDescription) {
+ this.signatureDescription = signatureDescription;
+ }
+ public String getCanonicalizationMethod() {
+ return canonicalizationMethod;
+ }
+ public void setCanonicalizationMethod(String canonicalizationMethod) {
+ this.canonicalizationMethod = canonicalizationMethod;
+ }
+ public String getPackageSignatureId() {
+ return packageSignatureId;
+ }
+ public void setPackageSignatureId(String packageSignatureId) {
+ this.packageSignatureId = nvl(packageSignatureId,"xmldsig-"+UUID.randomUUID());
+ }
+ public String getTspUrl() {
+ return tspUrl;
+ }
+ public void setTspUrl(String tspUrl) {
+ this.tspUrl = tspUrl;
+ }
+ public boolean isTspOldProtocol() {
+ return tspOldProtocol;
+ }
+ public void setTspOldProtocol(boolean tspOldProtocol) {
+ this.tspOldProtocol = tspOldProtocol;
+ }
+ public HashAlgorithm getTspDigestAlgo() {
+ return nvl(tspDigestAlgo,digestAlgo);
+ }
+ public void setTspDigestAlgo(HashAlgorithm tspDigestAlgo) {
+ this.tspDigestAlgo = tspDigestAlgo;
+ }
+ public String getProxyUrl() {
+ return proxyUrl;
+ }
+ public void setProxyUrl(String proxyUrl) {
+ this.proxyUrl = proxyUrl;
+ }
+ public TimeStampService getTspService() {
+ return tspService;
+ }
+ public void setTspService(TimeStampService tspService) {
+ this.tspService = tspService;
+ }
+ public String getTspUser() {
+ return tspUser;
+ }
+ public void setTspUser(String tspUser) {
+ this.tspUser = tspUser;
+ }
+ public String getTspPass() {
+ return tspPass;
+ }
+ public void setTspPass(String tspPass) {
+ this.tspPass = tspPass;
+ }
+ public TimeStampServiceValidator getTspValidator() {
+ return tspValidator;
+ }
+ public void setTspValidator(TimeStampServiceValidator tspValidator) {
+ this.tspValidator = tspValidator;
+ }
+ public RevocationDataService getRevocationDataService() {
+ return revocationDataService;
+ }
+ public void setRevocationDataService(RevocationDataService revocationDataService) {
+ this.revocationDataService = revocationDataService;
+ }
+ public HashAlgorithm getXadesDigestAlgo() {
+ return nvl(xadesDigestAlgo,digestAlgo);
+ }
+ public void setXadesDigestAlgo(HashAlgorithm xadesDigestAlgo) {
+ this.xadesDigestAlgo = xadesDigestAlgo;
+ }
+ public String getUserAgent() {
+ return userAgent;
+ }
+ public void setUserAgent(String userAgent) {
+ this.userAgent = userAgent;
+ }
+ public String getTspRequestPolicy() {
+ return tspRequestPolicy;
+ }
+ public void setTspRequestPolicy(String tspRequestPolicy) {
+ this.tspRequestPolicy = tspRequestPolicy;
+ }
+ public boolean isIncludeEntireCertificateChain() {
+ return includeEntireCertificateChain;
+ }
+ public void setIncludeEntireCertificateChain(boolean includeEntireCertificateChain) {
+ this.includeEntireCertificateChain = includeEntireCertificateChain;
+ }
+ public boolean isIncludeIssuerSerial() {
+ return includeIssuerSerial;
+ }
+ public void setIncludeIssuerSerial(boolean includeIssuerSerial) {
+ this.includeIssuerSerial = includeIssuerSerial;
+ }
+ public boolean isIncludeKeyValue() {
+ return includeKeyValue;
+ }
+ public void setIncludeKeyValue(boolean includeKeyValue) {
+ this.includeKeyValue = includeKeyValue;
+ }
+ public String getXadesRole() {
+ return xadesRole;
+ }
+ public void setXadesRole(String xadesRole) {
+ this.xadesRole = xadesRole;
+ }
+ public String getXadesSignatureId() {
+ return xadesSignatureId;
+ }
+ public void setXadesSignatureId(String xadesSignatureId) {
+ this.xadesSignatureId = xadesSignatureId;
+ }
+ public boolean isXadesSignaturePolicyImplied() {
+ return xadesSignaturePolicyImplied;
+ }
+ public void setXadesSignaturePolicyImplied(boolean xadesSignaturePolicyImplied) {
+ this.xadesSignaturePolicyImplied = xadesSignaturePolicyImplied;
+ }
+ public boolean isXadesIssuerNameNoReverseOrder() {
+ return xadesIssuerNameNoReverseOrder;
+ }
+ public void setXadesIssuerNameNoReverseOrder(boolean xadesIssuerNameNoReverseOrder) {
+ this.xadesIssuerNameNoReverseOrder = xadesIssuerNameNoReverseOrder;
+ }
+ public EventListener getSignatureMarshalListener() {
+ return signatureMarshalListener;
+ }
+ public void setSignatureMarshalListener(EventListener signatureMarshalListener) {
+ this.signatureMarshalListener = signatureMarshalListener;
+ }
+ public Map<String, String> getNamespacePrefixes() {
+ return namespacePrefixes;
+ }
+ public void setNamespacePrefixes(Map<String, String> namespacePrefixes) {
+ this.namespacePrefixes = namespacePrefixes;
+ }
+ protected static <T> T nvl(T value, T defaultValue) {
+ return value == null ? defaultValue : value;
+ }
+ public byte[] getHashMagic() {
+ // see https://www.ietf.org/rfc/rfc3110.txt
+ // RSA/SHA1 SIG Resource Records
+ byte result[];
+ switch (getDigestAlgo()) {
+ case sha1: result = new byte[]
+ { 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e
+ , 0x03, 0x02, 0x1a, 0x04, 0x14 };
+ break;
+ case sha224: result = new byte[]
+ { 0x30, 0x2b, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
+ , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x04, 0x1c };
+ break;
+ case sha256: result = new byte[]
+ { 0x30, 0x2f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
+ , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x04, 0x20 };
+ break;
+ case sha384: result = new byte[]
+ { 0x30, 0x3f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
+ , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x04, 0x30 };
+ break;
+ case sha512: result = new byte[]
+ { 0x30, 0x4f, 0x30, 0x0b, 0x06, 0x09, 0x60, (byte) 0x86
+ , 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x04, 0x40 };
+ break;
+ case ripemd128: result = new byte[]
+ { 0x30, 0x1b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24
+ , 0x03, 0x02, 0x02, 0x04, 0x10 };
+ break;
+ case ripemd160: result = new byte[]
+ { 0x30, 0x1f, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24
+ , 0x03, 0x02, 0x01, 0x04, 0x14 };
+ break;
+ // case ripemd256: result = new byte[]
+ // { 0x30, 0x2b, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x24
+ // , 0x03, 0x02, 0x03, 0x04, 0x20 };
+ // break;
+ default: throw new EncryptedDocumentException("Hash algorithm "
+ +getDigestAlgo()+" not supported for signing.");
+ }
+
+ return result;
+ }
+
+ public String getSignatureMethod() {
+ switch (getDigestAlgo()) {
+ case sha1: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1;
+ case sha224: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA224;
+ case sha256: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256;
+ case sha384: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA384;
+ case sha512: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA512;
+ case ripemd160: return org.apache.xml.security.signature.XMLSignature.ALGO_ID_SIGNATURE_RSA_RIPEMD160;
+ default: throw new EncryptedDocumentException("Hash algorithm "
+ +getDigestAlgo()+" not supported for signing.");
+ }
+ }
+
+ public String getDigestMethodUri() {
+ return getDigestMethodUri(getDigestAlgo());
+ }
+
+ public static String getDigestMethodUri(HashAlgorithm digestAlgo) {
+ switch (digestAlgo) {
+ case sha1: return DigestMethod.SHA1;
+ case sha224: return "http://www.w3.org/2001/04/xmldsig-more#sha224";
+ case sha256: return DigestMethod.SHA256;
+ case sha384: return "http://www.w3.org/2001/04/xmldsig-more#sha384";
+ case sha512: return DigestMethod.SHA512;
+ case ripemd160: return DigestMethod.RIPEMD160;
+ default: throw new EncryptedDocumentException("Hash algorithm "
+ +digestAlgo+" not supported for signing.");
+ }
+ }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java new file mode 100644 index 0000000000..69a771b40f --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureInfo.java @@ -0,0 +1,546 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_DIGSIG_NS;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URISyntaxException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import javax.crypto.Cipher;
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.URIDereferencer;
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.Manifest;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.SignatureMethod;
+import javax.xml.crypto.dsig.SignedInfo;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignContext;
+import javax.xml.crypto.dsig.XMLSignature;
+import javax.xml.crypto.dsig.XMLSignatureException;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.dom.DOMSignContext;
+import javax.xml.crypto.dsig.dom.DOMValidateContext;
+import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
+import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactoryConfigurationError;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathFactory;
+
+import org.apache.jcp.xml.dsig.internal.dom.DOMReference;
+import org.apache.jcp.xml.dsig.internal.dom.DOMSignedInfo;
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.ContentTypes;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackagePartName;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
+import org.apache.poi.openxml4j.opc.PackageRelationshipTypes;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
+import org.apache.poi.openxml4j.opc.TargetMode;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
+import org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
+import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo;
+import org.apache.poi.util.DocumentHelper;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xml.security.Init;
+import org.apache.xml.security.utils.Base64;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlOptions;
+import org.w3.x2000.x09.xmldsig.SignatureDocument;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+import org.xml.sax.SAXException;
+
+public class SignatureInfo implements SignatureConfigurable {
+
+ private static final POILogger LOG = POILogFactory.getLogger(SignatureInfo.class);
+ private static boolean isInitialized = false;
+
+ private SignatureConfig signatureConfig;
+
+ public class SignaturePart {
+ private final PackagePart signaturePart;
+ private X509Certificate signer;
+ private List<X509Certificate> certChain;
+
+ private SignaturePart(PackagePart signaturePart) {
+ this.signaturePart = signaturePart;
+ }
+
+ public PackagePart getPackagePart() {
+ return signaturePart;
+ }
+
+ public X509Certificate getSigner() {
+ return signer;
+ }
+
+ public List<X509Certificate> getCertChain() {
+ return certChain;
+ }
+
+ public SignatureDocument getSignatureDocument() throws IOException, XmlException {
+ // TODO: check for XXE
+ return SignatureDocument.Factory.parse(signaturePart.getInputStream());
+ }
+
+ public boolean validate() {
+ KeyInfoKeySelector keySelector = new KeyInfoKeySelector();
+ try {
+ Document doc = DocumentHelper.readDocument(signaturePart.getInputStream());
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ NodeList nl = (NodeList)xpath.compile("//*[@Id]").evaluate(doc, XPathConstants.NODESET);
+ for (int i=0; i<nl.getLength(); i++) {
+ ((Element)nl.item(i)).setIdAttribute("Id", true);
+ }
+
+ DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, doc);
+ domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE);
+ domValidateContext.setURIDereferencer(signatureConfig.getUriDereferencer());
+
+ XMLSignatureFactory xmlSignatureFactory = getSignatureFactory();
+ XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext);
+ boolean valid = xmlSignature.validate(domValidateContext);
+
+ if (valid) {
+ signer = keySelector.getSigner();
+ certChain = keySelector.getCertChain();
+ }
+
+ return valid;
+ } catch (Exception e) {
+ LOG.log(POILogger.ERROR, "error in marshalling and validating the signature", e);
+ return false;
+ }
+ }
+ }
+
+ public SignatureInfo() {
+ initXmlProvider();
+ }
+
+ public SignatureConfig getSignatureConfig() {
+ return signatureConfig;
+ }
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+
+ public boolean verifySignature() {
+ // http://www.oracle.com/technetwork/articles/javase/dig-signature-api-140772.html
+ for (SignaturePart sp : getSignatureParts()){
+ // only validate first part
+ return sp.validate();
+ }
+ return false;
+ }
+
+ public void confirmSignature()
+ throws NoSuchAlgorithmException, IOException, MarshalException, ParserConfigurationException, XmlException, InvalidAlgorithmParameterException, NoSuchProviderException, XMLSignatureException, TransformerFactoryConfigurationError, TransformerException, SAXException, URISyntaxException {
+ Document document = DocumentHelper.createDocument();
+
+ // operate
+ DigestInfo digestInfo = preSign(document, null);
+
+ // setup: key material, signature value
+ byte[] signatureValue = signDigest(digestInfo.digestValue);
+
+ // operate: postSign
+ postSign(document, signatureValue);
+ }
+
+ public byte[] signDigest(byte digest[]) {
+ Cipher cipher = CryptoFunctions.getCipher(signatureConfig.getKey(), CipherAlgorithm.rsa
+ , ChainingMode.ecb, null, Cipher.ENCRYPT_MODE, "PKCS1Padding");
+
+ try {
+ ByteArrayOutputStream digestInfoValueBuf = new ByteArrayOutputStream();
+ digestInfoValueBuf.write(signatureConfig.getHashMagic());
+ digestInfoValueBuf.write(digest);
+ byte[] digestInfoValue = digestInfoValueBuf.toByteArray();
+ byte[] signatureValue = cipher.doFinal(digestInfoValue);
+ return signatureValue;
+ } catch (Exception e) {
+ throw new EncryptedDocumentException(e);
+ }
+ }
+
+ public Iterable<SignaturePart> getSignatureParts() {
+ signatureConfig.init(true);
+ return new Iterable<SignaturePart>() {
+ public Iterator<SignaturePart> iterator() {
+ return new Iterator<SignaturePart>() {
+ OPCPackage pkg = signatureConfig.getOpcPackage();
+ Iterator<PackageRelationship> sigOrigRels =
+ pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN).iterator();
+ Iterator<PackageRelationship> sigRels = null;
+ PackagePart sigPart = null;
+
+ public boolean hasNext() {
+ while (sigRels == null || !sigRels.hasNext()) {
+ if (!sigOrigRels.hasNext()) return false;
+ sigPart = pkg.getPart(sigOrigRels.next());
+ LOG.log(POILogger.DEBUG, "Digital Signature Origin part", sigPart);
+ try {
+ sigRels = sigPart.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE).iterator();
+ } catch (InvalidFormatException e) {
+ LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);
+ }
+ }
+ return true;
+ }
+
+ public SignaturePart next() {
+ PackagePart sigRelPart = null;
+ do {
+ try {
+ if (!hasNext()) throw new NoSuchElementException();
+ sigRelPart = sigPart.getRelatedPart(sigRels.next());
+ LOG.log(POILogger.DEBUG, "XML Signature part", sigRelPart);
+ } catch (InvalidFormatException e) {
+ LOG.log(POILogger.WARN, "Reference to signature is invalid.", e);
+ }
+ } while (sigPart == null);
+ return new SignaturePart(sigRelPart);
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+ };
+ }
+
+ public static XMLSignatureFactory getSignatureFactory() {
+ return XMLSignatureFactory.getInstance("DOM", getProvider());
+ }
+
+ public static KeyInfoFactory getKeyInfoFactory() {
+ return KeyInfoFactory.getInstance("DOM", getProvider());
+ }
+
+ // currently classes are linked to Apache Santuario, so this might be superfluous
+ public static Provider getProvider() {
+ String dsigProviderNames[] = {
+ System.getProperty("jsr105Provider"),
+ "org.apache.jcp.xml.dsig.internal.dom.XMLDSigRI", // Santuario xmlsec
+ "org.jcp.xml.dsig.internal.dom.XMLDSigRI" // JDK xmlsec
+ };
+ for (String pn : dsigProviderNames) {
+ if (pn == null) continue;
+ try {
+ return (Provider)Class.forName(pn).newInstance();
+ } catch (Exception e) {
+ LOG.log(POILogger.DEBUG, "XMLDsig-Provider '"+pn+"' can't be found - trying next.");
+ }
+ }
+
+ throw new RuntimeException("JRE doesn't support default xml signature provider - set jsr105Provider system property!");
+ }
+
+ protected static synchronized void initXmlProvider() {
+ if (isInitialized) return;
+ isInitialized = true;
+
+ try {
+ Init.init();
+ RelationshipTransformService.registerDsigProvider();
+ CryptoFunctions.registerBouncyCastle();
+ } catch (Exception e) {
+ throw new RuntimeException("Xml & BouncyCastle-Provider initialization failed", e);
+ }
+ }
+
+ /**
+ * Helper method for adding informations before the signing.
+ * Normally {@link #confirmSignature()} is sufficient to be used.
+ */
+ @SuppressWarnings("unchecked")
+ public DigestInfo preSign(Document document, List<DigestInfo> digestInfos)
+ throws ParserConfigurationException, NoSuchAlgorithmException,
+ InvalidAlgorithmParameterException, MarshalException,
+ javax.xml.crypto.dsig.XMLSignatureException,
+ TransformerFactoryConfigurationError, TransformerException,
+ IOException, SAXException, NoSuchProviderException, XmlException, URISyntaxException {
+ signatureConfig.init(false);
+
+ // it's necessary to explicitly set the mdssi namespace, but the sign() method has no
+ // normal way to interfere with, so we need to add the namespace under the hand ...
+ EventTarget target = (EventTarget)document;
+ EventListener creationListener = signatureConfig.getSignatureMarshalListener();
+ if (creationListener != null) {
+ if (creationListener instanceof SignatureMarshalListener) {
+ ((SignatureMarshalListener)creationListener).setEventTarget(target);
+ }
+ SignatureMarshalListener.setListener(target, creationListener, true);
+ }
+
+ /*
+ * Signature context construction.
+ */
+ XMLSignContext xmlSignContext = new DOMSignContext(signatureConfig.getKey(), document);
+ URIDereferencer uriDereferencer = signatureConfig.getUriDereferencer();
+ if (null != uriDereferencer) {
+ xmlSignContext.setURIDereferencer(uriDereferencer);
+ }
+
+ for (Map.Entry<String,String> me : signatureConfig.getNamespacePrefixes().entrySet()) {
+ xmlSignContext.putNamespacePrefix(me.getKey(), me.getValue());
+ }
+ xmlSignContext.setDefaultNamespacePrefix(""); // signatureConfig.getNamespacePrefixes().get(XML_DIGSIG_NS));
+
+ XMLSignatureFactory signatureFactory = SignatureInfo.getSignatureFactory();
+
+ /*
+ * Add ds:References that come from signing client local files.
+ */
+ List<Reference> references = new ArrayList<Reference>();
+ for (DigestInfo digestInfo : safe(digestInfos)) {
+ byte[] documentDigestValue = digestInfo.digestValue;
+
+ DigestMethod digestMethod = signatureFactory.newDigestMethod
+ (signatureConfig.getDigestMethodUri(), null);
+
+ String uri = new File(digestInfo.description).getName();
+
+ Reference reference = signatureFactory.newReference
+ (uri, digestMethod, null, null, null, documentDigestValue);
+ references.add(reference);
+ }
+
+ /*
+ * Invoke the signature facets.
+ */
+ List<XMLObject> objects = new ArrayList<XMLObject>();
+ for (SignatureFacet signatureFacet : signatureConfig.getSignatureFacets()) {
+ LOG.log(POILogger.DEBUG, "invoking signature facet: " + signatureFacet.getClass().getSimpleName());
+ signatureFacet.preSign(document, signatureFactory, references, objects);
+ }
+
+ /*
+ * ds:SignedInfo
+ */
+ SignatureMethod signatureMethod = signatureFactory.newSignatureMethod
+ (signatureConfig.getSignatureMethod(), null);
+ CanonicalizationMethod canonicalizationMethod = signatureFactory
+ .newCanonicalizationMethod(signatureConfig.getCanonicalizationMethod(),
+ (C14NMethodParameterSpec) null);
+ SignedInfo signedInfo = signatureFactory.newSignedInfo(
+ canonicalizationMethod, signatureMethod, references);
+
+ /*
+ * JSR105 ds:Signature creation
+ */
+ String signatureValueId = signatureConfig.getPackageSignatureId() + "-signature-value";
+ javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory
+ .newXMLSignature(signedInfo, null, objects, signatureConfig.getPackageSignatureId(),
+ signatureValueId);
+
+ /*
+ * ds:Signature Marshalling.
+ */
+ xmlSignature.sign(xmlSignContext);
+
+ /*
+ * Completion of undigested ds:References in the ds:Manifests.
+ */
+ for (XMLObject object : objects) {
+ LOG.log(POILogger.DEBUG, "object java type: " + object.getClass().getName());
+ List<XMLStructure> objectContentList = object.getContent();
+ for (XMLStructure objectContent : objectContentList) {
+ LOG.log(POILogger.DEBUG, "object content java type: " + objectContent.getClass().getName());
+ if (!(objectContent instanceof Manifest)) continue;
+ Manifest manifest = (Manifest) objectContent;
+ List<Reference> manifestReferences = manifest.getReferences();
+ for (Reference manifestReference : manifestReferences) {
+ if (manifestReference.getDigestValue() != null) continue;
+
+ DOMReference manifestDOMReference = (DOMReference)manifestReference;
+ manifestDOMReference.digest(xmlSignContext);
+ }
+ }
+ }
+
+ /*
+ * Completion of undigested ds:References.
+ */
+ List<Reference> signedInfoReferences = signedInfo.getReferences();
+ for (Reference signedInfoReference : signedInfoReferences) {
+ DOMReference domReference = (DOMReference)signedInfoReference;
+
+ // ds:Reference with external digest value
+ if (domReference.getDigestValue() != null) continue;
+
+ domReference.digest(xmlSignContext);
+ }
+
+ /*
+ * Calculation of XML signature digest value.
+ */
+ DOMSignedInfo domSignedInfo = (DOMSignedInfo)signedInfo;
+ ByteArrayOutputStream dataStream = new ByteArrayOutputStream();
+ domSignedInfo.canonicalize(xmlSignContext, dataStream);
+ byte[] octets = dataStream.toByteArray();
+
+ /*
+ * TODO: we could be using DigestOutputStream here to optimize memory
+ * usage.
+ */
+
+ MessageDigest md = CryptoFunctions.getMessageDigest(signatureConfig.getDigestAlgo());
+ byte[] digestValue = md.digest(octets);
+
+
+ String description = signatureConfig.getSignatureDescription();
+ return new DigestInfo(digestValue, signatureConfig.getDigestAlgo(), description);
+ }
+
+ /**
+ * Helper method for adding informations after the signing.
+ * Normally {@link #confirmSignature()} is sufficient to be used.
+ */
+ public void postSign(Document document, byte[] signatureValue)
+ throws IOException, MarshalException, ParserConfigurationException, XmlException {
+ LOG.log(POILogger.DEBUG, "postSign");
+
+ /*
+ * Check ds:Signature node.
+ */
+ String signatureId = signatureConfig.getPackageSignatureId();
+ if (!signatureId.equals(document.getDocumentElement().getAttribute("Id"))) {
+ throw new RuntimeException("ds:Signature not found for @Id: " + signatureId);
+ }
+
+ /*
+ * Insert signature value into the ds:SignatureValue element
+ */
+ NodeList sigValNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue");
+ if (sigValNl.getLength() != 1) {
+ throw new RuntimeException("preSign has to be called before postSign");
+ }
+ sigValNl.item(0).setTextContent(Base64.encode(signatureValue));
+
+ /*
+ * Allow signature facets to inject their own stuff.
+ */
+ for (SignatureFacet signatureFacet : signatureConfig.getSignatureFacets()) {
+ signatureFacet.postSign(document, signatureConfig.getSigningCertificateChain());
+ }
+
+ writeDocument(document);
+ }
+
+ protected void writeDocument(Document document) throws IOException, XmlException {
+ XmlOptions xo = new XmlOptions();
+ Map<String,String> namespaceMap = new HashMap<String,String>();
+ for(Map.Entry<String,String> entry : signatureConfig.getNamespacePrefixes().entrySet()){
+ namespaceMap.put(entry.getValue(), entry.getKey());
+ }
+ xo.setSaveSuggestedPrefixes(namespaceMap);
+ xo.setUseDefaultNamespace();
+
+ LOG.log(POILogger.DEBUG, "output signed Office OpenXML document");
+
+ /*
+ * Copy the original OOXML content to the signed OOXML package. During
+ * copying some files need to changed.
+ */
+ OPCPackage pkg = signatureConfig.getOpcPackage();
+
+ PackagePartName sigPartName, sigsPartName;
+ try {
+ // <Override PartName="/_xmlsignatures/sig1.xml" ContentType="application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"/>
+ sigPartName = PackagingURIHelper.createPartName("/_xmlsignatures/sig1.xml");
+ // <Default Extension="sigs" ContentType="application/vnd.openxmlformats-package.digital-signature-origin"/>
+ sigsPartName = PackagingURIHelper.createPartName("/_xmlsignatures/origin.sigs");
+ } catch (InvalidFormatException e) {
+ throw new IOException(e);
+ }
+
+ PackagePart sigPart = pkg.getPart(sigPartName);
+ if (sigPart == null) {
+ sigPart = pkg.createPart(sigPartName, ContentTypes.DIGITAL_SIGNATURE_XML_SIGNATURE_PART);
+ }
+
+ OutputStream os = sigPart.getOutputStream();
+ SignatureDocument sigDoc = SignatureDocument.Factory.parse(document);
+ sigDoc.save(os, xo);
+ os.close();
+
+ PackagePart sigsPart = pkg.getPart(sigsPartName);
+ if (sigsPart == null) {
+ // touch empty marker file
+ sigsPart = pkg.createPart(sigsPartName, ContentTypes.DIGITAL_SIGNATURE_ORIGIN_PART);
+ }
+
+ PackageRelationshipCollection relCol = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
+ for (PackageRelationship pr : relCol) {
+ pkg.removeRelationship(pr.getId());
+ }
+ pkg.addRelationship(sigsPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN);
+
+ sigsPart.addRelationship(sigPartName, TargetMode.INTERNAL, PackageRelationshipTypes.DIGITAL_SIGNATURE);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> List<T> safe(List<T> other) {
+ return other == null ? Collections.EMPTY_LIST : other;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java new file mode 100644 index 0000000000..f0f43c674d --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/SignatureMarshalListener.java @@ -0,0 +1,92 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.OO_DIGSIG_NS;
+import static org.apache.poi.poifs.crypt.dsig.facets.SignatureFacet.XML_NS;
+
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.events.Event;
+import org.w3c.dom.events.EventListener;
+import org.w3c.dom.events.EventTarget;
+import org.w3c.dom.events.MutationEvent;
+
+/**
+ * This listener class is used, to modify the to be digested xml document,
+ * e.g. to register id attributes or set prefixes for registered namespaces
+ */
+public class SignatureMarshalListener implements EventListener, SignatureConfigurable {
+ ThreadLocal<EventTarget> target = new ThreadLocal<EventTarget>();
+ SignatureConfig signatureConfig;
+ public void setEventTarget(EventTarget target) {
+ this.target.set(target);
+ }
+
+ public void handleEvent(Event e) {
+ if (!(e instanceof MutationEvent)) return;
+ MutationEvent mutEvt = (MutationEvent)e;
+ EventTarget et = mutEvt.getTarget();
+ if (!(et instanceof Element)) return;
+ handleElement((Element)et);
+ }
+
+ public void handleElement(Element el) {
+ EventTarget target = this.target.get();
+ String packageId = signatureConfig.getPackageSignatureId();
+ if (el.hasAttribute("Id")) {
+ el.setIdAttribute("Id", true);
+ }
+
+ setListener(target, this, false);
+ if (packageId.equals(el.getAttribute("Id"))) {
+ el.setAttributeNS(XML_NS, "xmlns:mdssi", OO_DIGSIG_NS);
+ }
+ setPrefix(el);
+ setListener(target, this, true);
+ }
+
+ // helper method to keep it in one place
+ public static void setListener(EventTarget target, EventListener listener, boolean enabled) {
+ String type = "DOMSubtreeModified";
+ boolean useCapture = false;
+ if (enabled) {
+ target.addEventListener(type, listener, useCapture);
+ } else {
+ target.removeEventListener(type, listener, useCapture);
+ }
+ }
+
+ protected void setPrefix(Node el) {
+ String prefix = signatureConfig.getNamespacePrefixes().get(el.getNamespaceURI());
+ if (prefix != null && el.getPrefix() == null) {
+ el.setPrefix(prefix);
+ }
+
+ NodeList nl = el.getChildNodes();
+ for (int i=0; i<nl.getLength(); i++) {
+ setPrefix(nl.item(i));
+ }
+ }
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/TrustCertificateSecurityException.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/TrustCertificateSecurityException.java new file mode 100644 index 0000000000..b99b5ebb99 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/TrustCertificateSecurityException.java @@ -0,0 +1,38 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig;
+
+/**
+ * Exception thrown in case the incoming eID certificate is not trusted.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class TrustCertificateSecurityException extends
+ CertificateSecurityException {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/EnvelopedSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/EnvelopedSignatureFacet.java new file mode 100644 index 0000000000..2a281a6e6f --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/EnvelopedSignatureFacet.java @@ -0,0 +1,85 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.Transform;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.spec.TransformParameterSpec;
+
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
+import org.w3c.dom.Document;
+
+/**
+ * Signature Facet implementation to create enveloped signatures.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class EnvelopedSignatureFacet implements SignatureFacet {
+
+ private SignatureConfig signatureConfig;
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+
+ @Override
+ public void postSign(Document document, List<X509Certificate> signingCertificateChain) {
+ // empty
+ }
+
+ @Override
+ public void preSign(Document document
+ , XMLSignatureFactory signatureFactory
+ , List<Reference> references
+ , List<XMLObject> objects)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ DigestMethod digestMethod = signatureFactory.newDigestMethod
+ (signatureConfig.getDigestMethodUri(), null);
+
+ List<Transform> transforms = new ArrayList<Transform>();
+ Transform envelopedTransform = signatureFactory.newTransform
+ (CanonicalizationMethod.ENVELOPED, (TransformParameterSpec) null);
+ transforms.add(envelopedTransform);
+ Transform exclusiveTransform = signatureFactory.newTransform
+ (CanonicalizationMethod.EXCLUSIVE, (TransformParameterSpec) null);
+ transforms.add(exclusiveTransform);
+
+ Reference reference = signatureFactory.newReference("", digestMethod,
+ transforms, null, null);
+
+ references.add(reference);
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java new file mode 100644 index 0000000000..2bdcc4022a --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/KeyInfoSignatureFacet.java @@ -0,0 +1,168 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Key;
+import java.security.KeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.dom.DOMSignContext;
+import javax.xml.crypto.dsig.keyinfo.KeyInfo;
+import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory;
+import javax.xml.crypto.dsig.keyinfo.KeyValue;
+import javax.xml.crypto.dsig.keyinfo.X509Data;
+
+import org.apache.jcp.xml.dsig.internal.dom.DOMKeyInfo;
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
+import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Signature Facet implementation that adds ds:KeyInfo to the XML signature.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class KeyInfoSignatureFacet implements SignatureFacet {
+
+ private static final POILogger LOG = POILogFactory.getLogger(KeyInfoSignatureFacet.class);
+
+ SignatureConfig signatureConfig;
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+
+ @Override
+ public void postSign(Document document, List<X509Certificate> signingCertificateChain)
+ throws MarshalException {
+ LOG.log(POILogger.DEBUG, "postSign");
+
+ NodeList nl = document.getElementsByTagNameNS(XML_DIGSIG_NS, "Object");
+
+ /*
+ * Make sure we insert right after the ds:SignatureValue element, just
+ * before the first ds:Object element.
+ */
+ Node nextSibling = (nl.getLength() == 0) ? null : nl.item(0);
+
+ /*
+ * Construct the ds:KeyInfo element using JSR 105.
+ */
+ KeyInfoFactory keyInfoFactory = SignatureInfo.getKeyInfoFactory();
+ List<Object> x509DataObjects = new ArrayList<Object>();
+ X509Certificate signingCertificate = signingCertificateChain.get(0);
+
+ List<Object> keyInfoContent = new ArrayList<Object>();
+
+ if (signatureConfig.isIncludeKeyValue()) {
+ KeyValue keyValue;
+ try {
+ keyValue = keyInfoFactory.newKeyValue(signingCertificate.getPublicKey());
+ } catch (KeyException e) {
+ throw new RuntimeException("key exception: " + e.getMessage(), e);
+ }
+ keyInfoContent.add(keyValue);
+ }
+
+ if (signatureConfig.isIncludeIssuerSerial()) {
+ x509DataObjects.add(keyInfoFactory.newX509IssuerSerial(
+ signingCertificate.getIssuerX500Principal().toString(),
+ signingCertificate.getSerialNumber()));
+ }
+
+ if (signatureConfig.isIncludeEntireCertificateChain()) {
+ x509DataObjects.addAll(signingCertificateChain);
+ } else {
+ x509DataObjects.add(signingCertificate);
+ }
+
+ if (!x509DataObjects.isEmpty()) {
+ X509Data x509Data = keyInfoFactory.newX509Data(x509DataObjects);
+ keyInfoContent.add(x509Data);
+ }
+ KeyInfo keyInfo = keyInfoFactory.newKeyInfo(keyInfoContent);
+ DOMKeyInfo domKeyInfo = (DOMKeyInfo)keyInfo;
+
+ Key key = new Key() {
+ private static final long serialVersionUID = 1L;
+
+ public String getAlgorithm() {
+ return null;
+ }
+
+ public byte[] getEncoded() {
+ return null;
+ }
+
+ public String getFormat() {
+ return null;
+ }
+ };
+
+ Element n = document.getDocumentElement();
+ DOMSignContext domSignContext = new DOMSignContext(key, n, nextSibling);
+ for (Map.Entry<String,String> me : signatureConfig.getNamespacePrefixes().entrySet()) {
+ domSignContext.putNamespacePrefix(me.getKey(), me.getValue());
+ }
+
+ DOMStructure domStructure = new DOMStructure(n);
+ domKeyInfo.marshal(domStructure, domSignContext);
+
+ // move keyinfo into the right place
+ if (nextSibling != null) {
+ NodeList kiNl = document.getElementsByTagNameNS(XML_DIGSIG_NS, "KeyInfo");
+ if (kiNl.getLength() != 1) {
+ throw new RuntimeException("KeyInfo wasn't set");
+ }
+ nextSibling.getParentNode().insertBefore(kiNl.item(0), nextSibling);
+ }
+ }
+
+ @Override
+ public void preSign(
+ Document document
+ , XMLSignatureFactory signatureFactory
+ , List<Reference> references
+ , List<XMLObject> objects
+ ) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ // empty
+ }
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java new file mode 100644 index 0000000000..28626e8270 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/OOXMLSignatureFacet.java @@ -0,0 +1,504 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.xml.XMLConstants;
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.Manifest;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.SignatureProperties;
+import javax.xml.crypto.dsig.SignatureProperty;
+import javax.xml.crypto.dsig.Transform;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.spec.TransformParameterSpec;
+
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.ContentTypes;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.openxml4j.opc.PackagePartName;
+import org.apache.poi.openxml4j.opc.PackageRelationship;
+import org.apache.poi.openxml4j.opc.PackageRelationshipCollection;
+import org.apache.poi.openxml4j.opc.PackagingURIHelper;
+import org.apache.poi.openxml4j.opc.TargetMode;
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
+import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService;
+import org.apache.poi.poifs.crypt.dsig.services.RelationshipTransformService.RelationshipTransformParameterSpec;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xmlbeans.XmlException;
+import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.CTSignatureTime;
+import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.SignatureTimeDocument;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.microsoft.schemas.office.x2006.digsig.CTSignatureInfoV1;
+import com.microsoft.schemas.office.x2006.digsig.SignatureInfoV1Document;
+
+/**
+ * Office OpenXML Signature Facet implementation.
+ *
+ * @author fcorneli
+ * @see http://msdn.microsoft.com/en-us/library/cc313071.aspx
+ */
+public class OOXMLSignatureFacet implements SignatureFacet {
+
+ private static final POILogger LOG = POILogFactory.getLogger(OOXMLSignatureFacet.class);
+
+ private SignatureConfig signatureConfig;
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+
+ @Override
+ public void preSign(
+ Document document
+ , XMLSignatureFactory signatureFactory
+ , List<Reference> references
+ , List<XMLObject> objects)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, URISyntaxException, XmlException {
+ LOG.log(POILogger.DEBUG, "pre sign");
+ addManifestObject(document, signatureFactory, references, objects);
+ addSignatureInfo(document, signatureFactory, references, objects);
+ }
+
+ protected void addManifestObject(
+ Document document
+ , XMLSignatureFactory signatureFactory
+ , List<Reference> references
+ , List<XMLObject> objects)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, URISyntaxException, XmlException {
+
+ List<Reference> manifestReferences = new ArrayList<Reference>();
+ addManifestReferences(signatureFactory, manifestReferences);
+ Manifest manifest = signatureFactory.newManifest(manifestReferences);
+
+ String objectId = "idPackageObject"; // really has to be this value.
+ List<XMLStructure> objectContent = new ArrayList<XMLStructure>();
+ objectContent.add(manifest);
+
+ addSignatureTime(document, signatureFactory, objectContent);
+
+ XMLObject xo = signatureFactory.newXMLObject(objectContent, objectId, null, null);
+ objects.add(xo);
+
+ DigestMethod digestMethod = signatureFactory.newDigestMethod
+ (signatureConfig.getDigestMethodUri(), null);
+ Reference reference = signatureFactory.newReference
+ ("#" + objectId, digestMethod, null, XML_DIGSIG_NS+"Object", null);
+ references.add(reference);
+ }
+
+ protected void addManifestReferences
+ (XMLSignatureFactory signatureFactory, List<Reference> manifestReferences)
+ throws IOException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, URISyntaxException, XmlException {
+
+ OPCPackage ooxml = signatureConfig.getOpcPackage();
+ List<PackagePart> relsEntryNames = ooxml.getPartsByContentType(ContentTypes.RELATIONSHIPS_PART);
+
+ DigestMethod digestMethod = signatureFactory.newDigestMethod
+ (signatureConfig.getDigestMethodUri(), null);
+ Set<String> digestedPartNames = new HashSet<String>();
+ for (PackagePart pp : relsEntryNames) {
+ String baseUri = pp.getPartName().getName().replaceFirst("(.*)/_rels/.*", "$1");
+
+ PackageRelationshipCollection prc;
+ try {
+ prc = new PackageRelationshipCollection(ooxml);
+ prc.parseRelationshipsPart(pp);
+ } catch (InvalidFormatException e) {
+ throw new IOException("Invalid relationship descriptor: "+pp.getPartName().getName(), e);
+ }
+
+ RelationshipTransformParameterSpec parameterSpec = new RelationshipTransformParameterSpec();
+ for (PackageRelationship relationship : prc) {
+ String relationshipType = relationship.getRelationshipType();
+
+ /*
+ * ECMA-376 Part 2 - 3rd edition
+ * 13.2.4.16 Manifest Element
+ * "The producer shall not create a Manifest element that references any data outside of the package."
+ */
+ if (TargetMode.EXTERNAL == relationship.getTargetMode()) {
+ continue;
+ }
+
+ if (!isSignedRelationship(relationshipType)) continue;
+
+ parameterSpec.addRelationshipReference(relationship.getId());
+
+ // TODO: find a better way ...
+ String partName = baseUri + relationship.getTargetURI().toString();
+ partName = new URI(partName).normalize().getPath().replace('\\', '/');
+ LOG.log(POILogger.DEBUG, "part name: " + partName);
+
+ String contentType;
+ try {
+ PackagePartName relName = PackagingURIHelper.createPartName(partName);
+ PackagePart pp2 = ooxml.getPart(relName);
+ contentType = pp2.getContentType();
+ } catch (InvalidFormatException e) {
+ throw new IOException(e);
+ }
+
+ if (relationshipType.endsWith("customXml")
+ && !(contentType.equals("inkml+xml") || contentType.equals("text/xml"))) {
+ LOG.log(POILogger.DEBUG, "skipping customXml with content type: " + contentType);
+ continue;
+ }
+
+ if (!digestedPartNames.contains(partName)) {
+ // We only digest a part once.
+ String uri = partName + "?ContentType=" + contentType;
+ Reference reference = signatureFactory.newReference(uri, digestMethod);
+ manifestReferences.add(reference);
+ digestedPartNames.add(partName);
+ }
+ }
+
+ if (parameterSpec.hasSourceIds()) {
+ List<Transform> transforms = new ArrayList<Transform>();
+ transforms.add(signatureFactory.newTransform(
+ RelationshipTransformService.TRANSFORM_URI,
+ parameterSpec));
+ transforms.add(signatureFactory.newTransform(
+ CanonicalizationMethod.INCLUSIVE,
+ (TransformParameterSpec) null));
+ String uri = pp.getPartName().getName()
+ + "?ContentType=application/vnd.openxmlformats-package.relationships+xml";
+ Reference reference = signatureFactory.newReference(uri, digestMethod, transforms, null, null);
+ manifestReferences.add(reference);
+ }
+ }
+ }
+
+
+ protected void addSignatureTime(
+ Document document
+ , XMLSignatureFactory signatureFactory
+ , List<XMLStructure> objectContent) {
+ /*
+ * SignatureTime
+ */
+ DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
+ String nowStr = fmt.format(signatureConfig.getExecutionTime());
+ LOG.log(POILogger.DEBUG, "now: " + nowStr);
+
+ SignatureTimeDocument sigTime = SignatureTimeDocument.Factory.newInstance();
+ CTSignatureTime ctTime = sigTime.addNewSignatureTime();
+ ctTime.setFormat("YYYY-MM-DDThh:mm:ssTZD");
+ ctTime.setValue(nowStr);
+
+ Element n = (Element)document.importNode(ctTime.getDomNode(),true);
+ List<XMLStructure> signatureTimeContent = new ArrayList<XMLStructure>();
+ signatureTimeContent.add(new DOMStructure(n));
+ SignatureProperty signatureTimeSignatureProperty = signatureFactory
+ .newSignatureProperty(signatureTimeContent, "#" + signatureConfig.getPackageSignatureId(),
+ "idSignatureTime");
+ List<SignatureProperty> signaturePropertyContent = new ArrayList<SignatureProperty>();
+ signaturePropertyContent.add(signatureTimeSignatureProperty);
+ SignatureProperties signatureProperties = signatureFactory
+ .newSignatureProperties(signaturePropertyContent,
+ "id-signature-time-" + signatureConfig.getExecutionTime());
+ objectContent.add(signatureProperties);
+ }
+
+ protected void addSignatureInfo(Document document,
+ XMLSignatureFactory signatureFactory,
+ List<Reference> references,
+ List<XMLObject> objects)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ List<XMLStructure> objectContent = new ArrayList<XMLStructure>();
+
+ SignatureInfoV1Document sigV1 = SignatureInfoV1Document.Factory.newInstance();
+ CTSignatureInfoV1 ctSigV1 = sigV1.addNewSignatureInfoV1();
+ ctSigV1.setManifestHashAlgorithm(signatureConfig.getDigestMethodUri());
+ Element n = (Element)document.importNode(ctSigV1.getDomNode(), true);
+ n.setAttributeNS(XML_NS, XMLConstants.XMLNS_ATTRIBUTE, MS_DIGSIG_NS);
+
+ List<XMLStructure> signatureInfoContent = new ArrayList<XMLStructure>();
+ signatureInfoContent.add(new DOMStructure(n));
+ SignatureProperty signatureInfoSignatureProperty = signatureFactory
+ .newSignatureProperty(signatureInfoContent, "#" + signatureConfig.getPackageSignatureId(),
+ "idOfficeV1Details");
+
+ List<SignatureProperty> signaturePropertyContent = new ArrayList<SignatureProperty>();
+ signaturePropertyContent.add(signatureInfoSignatureProperty);
+ SignatureProperties signatureProperties = signatureFactory
+ .newSignatureProperties(signaturePropertyContent, null);
+ objectContent.add(signatureProperties);
+
+ String objectId = "idOfficeObject";
+ objects.add(signatureFactory.newXMLObject(objectContent, objectId, null, null));
+
+ DigestMethod digestMethod = signatureFactory.newDigestMethod
+ (signatureConfig.getDigestMethodUri(), null);
+ Reference reference = signatureFactory.newReference
+ ("#" + objectId, digestMethod, null, XML_DIGSIG_NS+"Object", null);
+ references.add(reference);
+ }
+
+ @Override
+ public void postSign(Document document, List<X509Certificate> signingCertificateChain) {
+ // empty
+ }
+
+ protected static String getRelationshipReferenceURI(String zipEntryName) {
+ return "/"
+ + zipEntryName
+ + "?ContentType=application/vnd.openxmlformats-package.relationships+xml";
+ }
+
+ protected static String getResourceReferenceURI(String resourceName, String contentType) {
+ return "/" + resourceName + "?ContentType=" + contentType;
+ }
+
+ protected static boolean isSignedRelationship(String relationshipType) {
+ LOG.log(POILogger.DEBUG, "relationship type: " + relationshipType);
+ for (String signedTypeExtension : signed) {
+ if (relationshipType.endsWith(signedTypeExtension)) {
+ return true;
+ }
+ }
+ if (relationshipType.endsWith("customXml")) {
+ LOG.log(POILogger.DEBUG, "customXml relationship type");
+ return true;
+ }
+ return false;
+ }
+
+ public static final String[] contentTypes = {
+ /*
+ * Word
+ */
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
+ "application/vnd.openxmlformats-officedocument.theme+xml",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml",
+
+ /*
+ * Word 2010
+ */
+ "application/vnd.ms-word.stylesWithEffects+xml",
+
+ /*
+ * Excel
+ */
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
+
+ /*
+ * Powerpoint
+ */
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml",
+ "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml",
+ "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml",
+ "application/vnd.openxmlformats-officedocument.presentationml.slide+xml",
+ "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml",
+
+ /*
+ * Powerpoint 2010
+ */
+ "application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml",
+ "application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"
+ };
+
+ /**
+ * Office 2010 list of signed types (extensions).
+ */
+ public static final String[] signed = {
+ "powerPivotData", //
+ "activeXControlBinary", //
+ "attachedToolbars", //
+ "connectorXml", //
+ "downRev", //
+ "functionPrototypes", //
+ "graphicFrameDoc", //
+ "groupShapeXml", //
+ "ink", //
+ "keyMapCustomizations", //
+ "legacyDiagramText", //
+ "legacyDocTextInfo", //
+ "officeDocument", //
+ "pictureXml", //
+ "shapeXml", //
+ "smartTags", //
+ "ui/altText", //
+ "ui/buttonSize", //
+ "ui/controlID", //
+ "ui/description", //
+ "ui/enabled", //
+ "ui/extensibility", //
+ "ui/helperText", //
+ "ui/imageID", //
+ "ui/imageMso", //
+ "ui/keyTip", //
+ "ui/label", //
+ "ui/lcid", //
+ "ui/loud", //
+ "ui/pressed", //
+ "ui/progID", //
+ "ui/ribbonID", //
+ "ui/showImage", //
+ "ui/showLabel", //
+ "ui/supertip", //
+ "ui/target", //
+ "ui/text", //
+ "ui/title", //
+ "ui/tooltip", //
+ "ui/userCustomization", //
+ "ui/visible", //
+ "userXmlData", //
+ "vbaProject", //
+ "wordVbaData", //
+ "wsSortMap", //
+ "xlBinaryIndex", //
+ "xlExternalLinkPath/xlAlternateStartup", //
+ "xlExternalLinkPath/xlLibrary", //
+ "xlExternalLinkPath/xlPathMissing", //
+ "xlExternalLinkPath/xlStartup", //
+ "xlIntlMacrosheet", //
+ "xlMacrosheet", //
+ "customData", //
+ "diagramDrawing", //
+ "hdphoto", //
+ "inkXml", //
+ "media", //
+ "slicer", //
+ "slicerCache", //
+ "stylesWithEffects", //
+ "ui/extensibility", //
+ "chartColorStyle", //
+ "chartLayout", //
+ "chartStyle", //
+ "dictionary", //
+ "timeline", //
+ "timelineCache", //
+ "aFChunk", //
+ "attachedTemplate", //
+ "audio", //
+ "calcChain", //
+ "chart", //
+ "chartsheet", //
+ "chartUserShapes", //
+ "commentAuthors", //
+ "comments", //
+ "connections", //
+ "control", //
+ "customProperty", //
+ "customXml", //
+ "diagramColors", //
+ "diagramData", //
+ "diagramLayout", //
+ "diagramQuickStyle", //
+ "dialogsheet", //
+ "drawing", //
+ "endnotes", //
+ "externalLink", //
+ "externalLinkPath", //
+ "font", //
+ "fontTable", //
+ "footer", //
+ "footnotes", //
+ "glossaryDocument", //
+ "handoutMaster", //
+ "header", //
+ "hyperlink", //
+ "image", //
+ "mailMergeHeaderSource", //
+ "mailMergeRecipientData", //
+ "mailMergeSource", //
+ "notesMaster", //
+ "notesSlide", //
+ "numbering", //
+ "officeDocument", //
+ "oleObject", //
+ "package", //
+ "pivotCacheDefinition", //
+ "pivotCacheRecords", //
+ "pivotTable", //
+ "presProps", //
+ "printerSettings", //
+ "queryTable", //
+ "recipientData", //
+ "settings", //
+ "sharedStrings", //
+ "sheetMetadata", //
+ "slide", //
+ "slideLayout", //
+ "slideMaster", //
+ "slideUpdateInfo", //
+ "slideUpdateUrl", //
+ "styles", //
+ "table", //
+ "tableSingleCells", //
+ "tableStyles", //
+ "tags", //
+ "theme", //
+ "themeOverride", //
+ "transform", //
+ "video", //
+ "viewProps", //
+ "volatileDependencies", //
+ "webSettings", //
+ "worksheet", //
+ "xmlMaps", //
+ "ctrlProp", //
+ "customData", //
+ "diagram", //
+ "diagramColorsHeader", //
+ "diagramLayoutHeader", //
+ "diagramQuickStyleHeader", //
+ "documentParts", //
+ "slicer", //
+ "slicerCache", //
+ "vmlDrawing" //
+ };
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java new file mode 100644 index 0000000000..d64eac3196 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/Office2010SignatureFacet.java @@ -0,0 +1,94 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
+import org.apache.xmlbeans.XmlException;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesType;
+import org.etsi.uri.x01903.v13.UnsignedPropertiesType;
+import org.etsi.uri.x01903.v13.UnsignedSignaturePropertiesType;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Work-around for Office2010 to accept the XAdES-BES/EPES signature.
+ *
+ * xades:UnsignedProperties/xades:UnsignedSignatureProperties needs to be
+ * present.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class Office2010SignatureFacet implements SignatureFacet {
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ // this.signatureConfig = signatureConfig;
+ }
+
+ @Override
+ public void preSign(
+ Document document
+ , XMLSignatureFactory signatureFactory
+ , List<Reference> references
+ , List<XMLObject> objects
+ ) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ }
+
+ @Override
+ public void postSign(Document document, List<X509Certificate> signingCertificateChain)
+ throws XmlException {
+ // check for XAdES-BES
+ NodeList nl = document.getElementsByTagNameNS(XADES_132_NS, "QualifyingProperties");
+ if (nl.getLength() != 1) {
+ throw new IllegalArgumentException("no XAdES-BES extension present");
+ }
+
+ QualifyingPropertiesType qualProps =
+ QualifyingPropertiesType.Factory.parse(nl.item(0));
+
+ // create basic XML container structure
+ UnsignedPropertiesType unsignedProps = qualProps.getUnsignedProperties();
+ if (unsignedProps == null) {
+ unsignedProps = qualProps.addNewUnsignedProperties();
+ }
+ UnsignedSignaturePropertiesType unsignedSigProps = unsignedProps.getUnsignedSignatureProperties();
+ if (unsignedSigProps == null) {
+ unsignedSigProps = unsignedProps.addNewUnsignedSignatureProperties();
+ }
+
+ Node n = document.importNode(qualProps.getDomNode().getFirstChild(), true);
+ nl.item(0).getParentNode().replaceChild(n, nl.item(0));
+ }
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java new file mode 100644 index 0000000000..188830bdf0 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/SignatureFacet.java @@ -0,0 +1,96 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import javax.xml.XMLConstants;
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignature;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+
+import org.apache.poi.openxml4j.opc.PackageNamespaces;
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
+import org.apache.xmlbeans.XmlException;
+import org.w3c.dom.Document;
+
+/**
+ * JSR105 Signature Facet interface.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface SignatureFacet extends SignatureConfigurable {
+
+ String XML_NS = XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
+ String XML_DIGSIG_NS = XMLSignature.XMLNS;
+ String OO_DIGSIG_NS = PackageNamespaces.DIGITAL_SIGNATURE;
+ String MS_DIGSIG_NS = "http://schemas.microsoft.com/office/2006/digsig";
+ String XADES_132_NS = "http://uri.etsi.org/01903/v1.3.2#";
+ String XADES_141_NS = "http://uri.etsi.org/01903/v1.4.1#";
+
+
+ /**
+ * This method is being invoked by the XML signature service engine during
+ * pre-sign phase. Via this method a signature facet implementation can add
+ * signature facets to an XML signature.
+ *
+ * @param signatureFactory
+ * @param document
+ * @param signatureId
+ * @param signingCertificateChain
+ * the optional signing certificate chain
+ * @param references
+ * @param objects
+ * @throws InvalidAlgorithmParameterException
+ * @throws NoSuchAlgorithmException
+ */
+ void preSign(
+ Document document
+ , XMLSignatureFactory signatureFactory
+ , List<Reference> references
+ , List<XMLObject> objects
+ ) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, IOException, URISyntaxException, XmlException;
+
+ /**
+ * This method is being invoked by the XML signature service engine during
+ * the post-sign phase. Via this method a signature facet can extend the XML
+ * signatures with for example key information.
+ *
+ * @param signatureElement
+ * @param signingCertificateChain
+ */
+ void postSign(
+ Document document
+ , List<X509Certificate> signingCertificateChain
+ ) throws MarshalException, XmlException;
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java new file mode 100644 index 0000000000..d34b367dda --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESSignatureFacet.java @@ -0,0 +1,304 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.DigestMethod;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.Transform;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+import javax.xml.crypto.dsig.spec.TransformParameterSpec;
+
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
+import org.apache.poi.poifs.crypt.dsig.services.SignaturePolicyService;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlObject;
+import org.apache.xmlbeans.XmlString;
+import org.etsi.uri.x01903.v13.AnyType;
+import org.etsi.uri.x01903.v13.CertIDListType;
+import org.etsi.uri.x01903.v13.CertIDType;
+import org.etsi.uri.x01903.v13.ClaimedRolesListType;
+import org.etsi.uri.x01903.v13.DataObjectFormatType;
+import org.etsi.uri.x01903.v13.DigestAlgAndValueType;
+import org.etsi.uri.x01903.v13.IdentifierType;
+import org.etsi.uri.x01903.v13.ObjectIdentifierType;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesDocument;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesType;
+import org.etsi.uri.x01903.v13.SigPolicyQualifiersListType;
+import org.etsi.uri.x01903.v13.SignaturePolicyIdType;
+import org.etsi.uri.x01903.v13.SignaturePolicyIdentifierType;
+import org.etsi.uri.x01903.v13.SignedDataObjectPropertiesType;
+import org.etsi.uri.x01903.v13.SignedPropertiesType;
+import org.etsi.uri.x01903.v13.SignedSignaturePropertiesType;
+import org.etsi.uri.x01903.v13.SignerRoleType;
+import org.w3.x2000.x09.xmldsig.DigestMethodType;
+import org.w3.x2000.x09.xmldsig.X509IssuerSerialType;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+/**
+ * XAdES Signature Facet. Implements XAdES v1.4.1 which is compatible with XAdES
+ * v1.3.2. The implemented XAdES format is XAdES-BES/EPES. It's up to another
+ * part of the signature service to upgrade the XAdES-BES to a XAdES-X-L.
+ *
+ * This implementation has been tested against an implementation that
+ * participated multiple ETSI XAdES plugtests.
+ *
+ * @author Frank Cornelis
+ * @see http://en.wikipedia.org/wiki/XAdES
+ *
+ */
+public class XAdESSignatureFacet implements SignatureFacet {
+
+ private static final POILogger LOG = POILogFactory.getLogger(XAdESSignatureFacet.class);
+
+ private static final String XADES_TYPE = "http://uri.etsi.org/01903#SignedProperties";
+
+ private SignatureConfig signatureConfig;
+
+ private Map<String, String> dataObjectFormatMimeTypes = new HashMap<String, String>();
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+
+ @Override
+ public void postSign(Document document, List<X509Certificate> signingCertificateChain) {
+ LOG.log(POILogger.DEBUG, "postSign");
+ }
+
+ @Override
+ public void preSign(Document document,
+ XMLSignatureFactory signatureFactory,
+ List<Reference> references, List<XMLObject> objects)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ LOG.log(POILogger.DEBUG, "preSign");
+
+ // QualifyingProperties
+ QualifyingPropertiesDocument qualDoc = QualifyingPropertiesDocument.Factory.newInstance();
+ QualifyingPropertiesType qualifyingProperties = qualDoc.addNewQualifyingProperties();
+ qualifyingProperties.setTarget("#" + signatureConfig.getPackageSignatureId());
+
+ // SignedProperties
+ SignedPropertiesType signedProperties = qualifyingProperties.addNewSignedProperties();
+ signedProperties.setId(signatureConfig.getXadesSignatureId());
+
+ // SignedSignatureProperties
+ SignedSignaturePropertiesType signedSignatureProperties = signedProperties.addNewSignedSignatureProperties();
+
+ // SigningTime
+ Calendar xmlGregorianCalendar = Calendar.getInstance();
+ xmlGregorianCalendar.setTimeZone(TimeZone.getTimeZone("Z"));
+ xmlGregorianCalendar.setTime(signatureConfig.getExecutionTime());
+ xmlGregorianCalendar.clear(Calendar.MILLISECOND);
+ signedSignatureProperties.setSigningTime(xmlGregorianCalendar);
+
+ // SigningCertificate
+ if (signatureConfig.getSigningCertificateChain() == null
+ || signatureConfig.getSigningCertificateChain().isEmpty()) {
+ throw new RuntimeException("no signing certificate chain available");
+ }
+ CertIDListType signingCertificates = signedSignatureProperties.addNewSigningCertificate();
+ CertIDType certId = signingCertificates.addNewCert();
+ X509Certificate certificate = signatureConfig.getSigningCertificateChain().get(0);
+ setCertID(certId, signatureConfig, signatureConfig.isXadesIssuerNameNoReverseOrder(), certificate);
+
+ // ClaimedRole
+ String role = signatureConfig.getXadesRole();
+ if (role != null && !role.isEmpty()) {
+ SignerRoleType signerRole = signedSignatureProperties.addNewSignerRole();
+ signedSignatureProperties.setSignerRole(signerRole);
+ ClaimedRolesListType claimedRolesList = signerRole.addNewClaimedRoles();
+ AnyType claimedRole = claimedRolesList.addNewClaimedRole();
+ XmlString roleString = XmlString.Factory.newInstance();
+ roleString.setStringValue(role);
+ insertXChild(claimedRole, roleString);
+ }
+
+ // XAdES-EPES
+ SignaturePolicyService policyService = signatureConfig.getSignaturePolicyService();
+ if (policyService != null) {
+ SignaturePolicyIdentifierType signaturePolicyIdentifier =
+ signedSignatureProperties.addNewSignaturePolicyIdentifier();
+
+ SignaturePolicyIdType signaturePolicyId = signaturePolicyIdentifier.addNewSignaturePolicyId();
+
+ ObjectIdentifierType objectIdentifier = signaturePolicyId.addNewSigPolicyId();
+ objectIdentifier.setDescription(policyService.getSignaturePolicyDescription());
+
+ IdentifierType identifier = objectIdentifier.addNewIdentifier();
+ identifier.setStringValue(policyService.getSignaturePolicyIdentifier());
+
+ byte[] signaturePolicyDocumentData = policyService.getSignaturePolicyDocument();
+ DigestAlgAndValueType sigPolicyHash = signaturePolicyId.addNewSigPolicyHash();
+ setDigestAlgAndValue(sigPolicyHash, signaturePolicyDocumentData, signatureConfig.getDigestAlgo());
+
+ String signaturePolicyDownloadUrl = policyService.getSignaturePolicyDownloadUrl();
+ if (null != signaturePolicyDownloadUrl) {
+ SigPolicyQualifiersListType sigPolicyQualifiers = signaturePolicyId.addNewSigPolicyQualifiers();
+ AnyType sigPolicyQualifier = sigPolicyQualifiers.addNewSigPolicyQualifier();
+ XmlString spUriElement = XmlString.Factory.newInstance();
+ spUriElement.setStringValue(signaturePolicyDownloadUrl);
+ insertXChild(sigPolicyQualifier, spUriElement);
+ }
+ } else if (signatureConfig.isXadesSignaturePolicyImplied()) {
+ SignaturePolicyIdentifierType signaturePolicyIdentifier =
+ signedSignatureProperties.addNewSignaturePolicyIdentifier();
+ signaturePolicyIdentifier.addNewSignaturePolicyImplied();
+ }
+
+ // DataObjectFormat
+ if (!dataObjectFormatMimeTypes.isEmpty()) {
+ SignedDataObjectPropertiesType signedDataObjectProperties =
+ signedProperties.addNewSignedDataObjectProperties();
+
+ List<DataObjectFormatType> dataObjectFormats = signedDataObjectProperties
+ .getDataObjectFormatList();
+ for (Map.Entry<String, String> dataObjectFormatMimeType : this.dataObjectFormatMimeTypes
+ .entrySet()) {
+ DataObjectFormatType dataObjectFormat = DataObjectFormatType.Factory.newInstance();
+ dataObjectFormat.setObjectReference("#" + dataObjectFormatMimeType.getKey());
+ dataObjectFormat.setMimeType(dataObjectFormatMimeType.getValue());
+ dataObjectFormats.add(dataObjectFormat);
+ }
+ }
+
+ // add XAdES ds:Object
+ List<XMLStructure> xadesObjectContent = new ArrayList<XMLStructure>();
+ Element qualDocElSrc = (Element)qualifyingProperties.getDomNode();
+ Element qualDocEl = (Element)document.importNode(qualDocElSrc, true);
+ xadesObjectContent.add(new DOMStructure(qualDocEl));
+ XMLObject xadesObject = signatureFactory.newXMLObject(xadesObjectContent, null, null, null);
+ objects.add(xadesObject);
+
+ // add XAdES ds:Reference
+ DigestMethod digestMethod = signatureFactory.newDigestMethod(signatureConfig.getDigestMethodUri(), null);
+ List<Transform> transforms = new ArrayList<Transform>();
+ Transform exclusiveTransform = signatureFactory
+ .newTransform(CanonicalizationMethod.INCLUSIVE,
+ (TransformParameterSpec) null);
+ transforms.add(exclusiveTransform);
+ Reference reference = signatureFactory.newReference
+ ("#"+signatureConfig.getXadesSignatureId(), digestMethod, transforms, XADES_TYPE, null);
+ references.add(reference);
+ }
+
+ /**
+ * Gives back the JAXB DigestAlgAndValue data structure.
+ *
+ * @param data
+ * @param xadesObjectFactory
+ * @param xmldsigObjectFactory
+ * @param hashAlgo
+ * @return
+ */
+ protected static void setDigestAlgAndValue(
+ DigestAlgAndValueType digestAlgAndValue,
+ byte[] data,
+ HashAlgorithm digestAlgo) {
+ DigestMethodType digestMethod = digestAlgAndValue.addNewDigestMethod();
+ digestMethod.setAlgorithm(SignatureConfig.getDigestMethodUri(digestAlgo));
+
+ MessageDigest messageDigest = CryptoFunctions.getMessageDigest(digestAlgo);
+ byte[] digestValue = messageDigest.digest(data);
+ digestAlgAndValue.setDigestValue(digestValue);
+ }
+
+ /**
+ * Gives back the JAXB CertID data structure.
+ */
+ protected static void setCertID
+ (CertIDType certId, SignatureConfig signatureConfig, boolean issuerNameNoReverseOrder, X509Certificate certificate) {
+ X509IssuerSerialType issuerSerial = certId.addNewIssuerSerial();
+ String issuerName;
+ if (issuerNameNoReverseOrder) {
+ /*
+ * Make sure the DN is encoded using the same order as present
+ * within the certificate. This is an Office2010 work-around.
+ * Should be reverted back.
+ *
+ * XXX: not correct according to RFC 4514.
+ */
+ // TODO: check if issuerName is different on getTBSCertificate
+ // issuerName = PrincipalUtil.getIssuerX509Principal(certificate).getName().replace(",", ", ");
+ issuerName = certificate.getIssuerDN().getName().replace(",", ", ");
+ } else {
+ issuerName = certificate.getIssuerX500Principal().toString();
+ }
+ issuerSerial.setX509IssuerName(issuerName);
+ issuerSerial.setX509SerialNumber(certificate.getSerialNumber());
+
+ byte[] encodedCertificate;
+ try {
+ encodedCertificate = certificate.getEncoded();
+ } catch (CertificateEncodingException e) {
+ throw new RuntimeException("certificate encoding error: "
+ + e.getMessage(), e);
+ }
+ DigestAlgAndValueType certDigest = certId.addNewCertDigest();
+ setDigestAlgAndValue(certDigest, encodedCertificate, signatureConfig.getXadesDigestAlgo());
+ }
+
+ /**
+ * Adds a mime-type for the given ds:Reference (referred via its @URI). This
+ * information is added via the xades:DataObjectFormat element.
+ *
+ * @param dsReferenceUri
+ * @param mimetype
+ */
+ public void addMimeType(String dsReferenceUri, String mimetype) {
+ this.dataObjectFormatMimeTypes.put(dsReferenceUri, mimetype);
+ }
+
+ protected static void insertXChild(XmlObject root, XmlObject child) {
+ XmlCursor rootCursor = root.newCursor();
+ rootCursor.toEndToken();
+ XmlCursor childCursor = child.newCursor();
+ childCursor.toNextToken();
+ childCursor.moveXml(rootCursor);
+ childCursor.dispose();
+ rootCursor.dispose();
+ }
+
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java new file mode 100644 index 0000000000..acbb1b9fc6 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/facets/XAdESXLSignatureFacet.java @@ -0,0 +1,433 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.facets;
+
+import static org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet.insertXChild;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import javax.xml.crypto.dsig.CanonicalizationMethod;
+import javax.xml.crypto.dsig.Reference;
+import javax.xml.crypto.dsig.XMLObject;
+import javax.xml.crypto.dsig.XMLSignatureFactory;
+
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
+import org.apache.poi.poifs.crypt.dsig.services.RevocationData;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.xml.security.c14n.Canonicalizer;
+import org.apache.xmlbeans.XmlException;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.DERTaggedObject;
+import org.bouncycastle.asn1.ocsp.ResponderID;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+import org.bouncycastle.cert.ocsp.RespID;
+import org.etsi.uri.x01903.v13.CRLIdentifierType;
+import org.etsi.uri.x01903.v13.CRLRefType;
+import org.etsi.uri.x01903.v13.CRLRefsType;
+import org.etsi.uri.x01903.v13.CRLValuesType;
+import org.etsi.uri.x01903.v13.CertIDListType;
+import org.etsi.uri.x01903.v13.CertIDType;
+import org.etsi.uri.x01903.v13.CertificateValuesType;
+import org.etsi.uri.x01903.v13.CompleteCertificateRefsType;
+import org.etsi.uri.x01903.v13.CompleteRevocationRefsType;
+import org.etsi.uri.x01903.v13.DigestAlgAndValueType;
+import org.etsi.uri.x01903.v13.EncapsulatedPKIDataType;
+import org.etsi.uri.x01903.v13.OCSPIdentifierType;
+import org.etsi.uri.x01903.v13.OCSPRefType;
+import org.etsi.uri.x01903.v13.OCSPRefsType;
+import org.etsi.uri.x01903.v13.OCSPValuesType;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesDocument;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesType;
+import org.etsi.uri.x01903.v13.ResponderIDType;
+import org.etsi.uri.x01903.v13.RevocationValuesType;
+import org.etsi.uri.x01903.v13.UnsignedPropertiesType;
+import org.etsi.uri.x01903.v13.UnsignedSignaturePropertiesType;
+import org.etsi.uri.x01903.v13.XAdESTimeStampType;
+import org.etsi.uri.x01903.v14.ValidationDataType;
+import org.w3.x2000.x09.xmldsig.CanonicalizationMethodType;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * XAdES-X-L v1.4.1 signature facet. This signature facet implementation will
+ * upgrade a given XAdES-BES/EPES signature to XAdES-X-L.
+ *
+ * We don't inherit from XAdESSignatureFacet as we also want to be able to use
+ * this facet out of the context of a signature creation. This signature facet
+ * assumes that the signature is already XAdES-BES/EPES compliant.
+ *
+ * This implementation has been tested against an implementation that
+ * participated multiple ETSI XAdES plugtests.
+ *
+ * @author Frank Cornelis
+ * @see XAdESSignatureFacet
+ */
+public class XAdESXLSignatureFacet implements SignatureFacet {
+
+ private static final POILogger LOG = POILogFactory.getLogger(XAdESXLSignatureFacet.class);
+
+ private SignatureConfig signatureConfig;
+
+ private String c14nAlgoId = CanonicalizationMethod.EXCLUSIVE;
+
+ private final CertificateFactory certificateFactory;
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+
+
+
+ /**
+ * Convenience constructor.
+ *
+ * @param timeStampService
+ * the time-stamp service used for XAdES-T and XAdES-X.
+ * @param revocationDataService
+ */
+ public XAdESXLSignatureFacet() {
+ try {
+ this.certificateFactory = CertificateFactory.getInstance("X.509");
+ } catch (CertificateException e) {
+ throw new RuntimeException("X509 JCA error: " + e.getMessage(), e);
+ }
+ }
+
+ public void setCanonicalizerAlgorithm(String c14nAlgoId) {
+ this.c14nAlgoId = c14nAlgoId;
+ }
+
+ @Override
+ public void postSign(Document document,
+ List<X509Certificate> signingCertificateChain
+ ) throws XmlException {
+ LOG.log(POILogger.DEBUG, "XAdES-X-L post sign phase");
+
+ QualifyingPropertiesDocument qualDoc = null;
+ QualifyingPropertiesType qualProps = null;
+
+ // check for XAdES-BES
+ NodeList qualNl = document.getElementsByTagNameNS(XADES_132_NS, "QualifyingProperties");
+ if (qualNl.getLength() == 1) {
+ qualDoc = QualifyingPropertiesDocument.Factory.parse(qualNl.item(0));
+ qualProps = qualDoc.getQualifyingProperties();
+ } else {
+ throw new IllegalArgumentException("no XAdES-BES extension present");
+ }
+
+ // create basic XML container structure
+ UnsignedPropertiesType unsignedProps = qualProps.getUnsignedProperties();
+ if (unsignedProps == null) {
+ unsignedProps = qualProps.addNewUnsignedProperties();
+ }
+ UnsignedSignaturePropertiesType unsignedSigProps = unsignedProps.getUnsignedSignatureProperties();
+ if (unsignedSigProps == null) {
+ unsignedSigProps = unsignedProps.addNewUnsignedSignatureProperties();
+ }
+
+
+ // create the XAdES-T time-stamp
+ NodeList nlSigVal = document.getElementsByTagNameNS(XML_DIGSIG_NS, "SignatureValue");
+ if (nlSigVal.getLength() != 1) {
+ throw new IllegalArgumentException("SignatureValue is not set.");
+ }
+
+ RevocationData tsaRevocationDataXadesT = new RevocationData();
+ LOG.log(POILogger.DEBUG, "creating XAdES-T time-stamp");
+ XAdESTimeStampType signatureTimeStamp = createXAdESTimeStamp
+ (Collections.singletonList(nlSigVal.item(0)), tsaRevocationDataXadesT);
+
+ // marshal the XAdES-T extension
+ unsignedSigProps.addNewSignatureTimeStamp().set(signatureTimeStamp);
+
+ // xadesv141::TimeStampValidationData
+ if (tsaRevocationDataXadesT.hasRevocationDataEntries()) {
+ ValidationDataType validationData = createValidationData(tsaRevocationDataXadesT);
+ insertXChild(unsignedSigProps, validationData);
+ }
+
+ if (signatureConfig.getRevocationDataService() == null) {
+ /*
+ * Without revocation data service we cannot construct the XAdES-C
+ * extension.
+ */
+ return;
+ }
+
+ // XAdES-C: complete certificate refs
+ CompleteCertificateRefsType completeCertificateRefs =
+ unsignedSigProps.addNewCompleteCertificateRefs();
+
+ CertIDListType certIdList = completeCertificateRefs.addNewCertRefs();
+ /*
+ * We skip the signing certificate itself according to section
+ * 4.4.3.2 of the XAdES 1.4.1 specification.
+ */
+ int chainSize = signingCertificateChain.size();
+ if (chainSize > 1) {
+ for (X509Certificate cert : signingCertificateChain.subList(1, chainSize)) {
+ CertIDType certId = certIdList.addNewCert();
+ XAdESSignatureFacet.setCertID(certId, signatureConfig, false, cert);
+ }
+ }
+
+ // XAdES-C: complete revocation refs
+ CompleteRevocationRefsType completeRevocationRefs =
+ unsignedSigProps.addNewCompleteRevocationRefs();
+ RevocationData revocationData = signatureConfig.getRevocationDataService()
+ .getRevocationData(signingCertificateChain);
+ if (revocationData.hasCRLs()) {
+ CRLRefsType crlRefs = completeRevocationRefs.addNewCRLRefs();
+ completeRevocationRefs.setCRLRefs(crlRefs);
+
+ for (byte[] encodedCrl : revocationData.getCRLs()) {
+ CRLRefType crlRef = crlRefs.addNewCRLRef();
+ X509CRL crl;
+ try {
+ crl = (X509CRL) this.certificateFactory
+ .generateCRL(new ByteArrayInputStream(encodedCrl));
+ } catch (CRLException e) {
+ throw new RuntimeException("CRL parse error: "
+ + e.getMessage(), e);
+ }
+
+ CRLIdentifierType crlIdentifier = crlRef.addNewCRLIdentifier();
+ String issuerName = crl.getIssuerDN().getName().replace(",", ", ");
+ crlIdentifier.setIssuer(issuerName);
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(crl.getThisUpdate());
+ crlIdentifier.setIssueTime(cal);
+ crlIdentifier.setNumber(getCrlNumber(crl));
+
+ DigestAlgAndValueType digestAlgAndValue = crlRef.addNewDigestAlgAndValue();
+ XAdESSignatureFacet.setDigestAlgAndValue(digestAlgAndValue, encodedCrl, signatureConfig.getDigestAlgo());
+ }
+ }
+ if (revocationData.hasOCSPs()) {
+ OCSPRefsType ocspRefs = completeRevocationRefs.addNewOCSPRefs();
+ for (byte[] ocsp : revocationData.getOCSPs()) {
+ try {
+ OCSPRefType ocspRef = ocspRefs.addNewOCSPRef();
+
+ DigestAlgAndValueType digestAlgAndValue = ocspRef.addNewDigestAlgAndValue();
+ XAdESSignatureFacet.setDigestAlgAndValue(digestAlgAndValue, ocsp, signatureConfig.getDigestAlgo());
+
+ OCSPIdentifierType ocspIdentifier = ocspRef.addNewOCSPIdentifier();
+
+ OCSPResp ocspResp = new OCSPResp(ocsp);
+
+ BasicOCSPResp basicOcspResp = (BasicOCSPResp)ocspResp.getResponseObject();
+
+ Calendar cal = Calendar.getInstance();
+ cal.setTime(basicOcspResp.getProducedAt());
+ ocspIdentifier.setProducedAt(cal);
+
+ ResponderIDType responderId = ocspIdentifier.addNewResponderID();
+
+ RespID respId = basicOcspResp.getResponderId();
+ ResponderID ocspResponderId = respId.toASN1Object();
+ DERTaggedObject derTaggedObject = (DERTaggedObject)ocspResponderId.toASN1Primitive();
+ if (2 == derTaggedObject.getTagNo()) {
+ ASN1OctetString keyHashOctetString = (ASN1OctetString)derTaggedObject.getObject();
+ byte key[] = keyHashOctetString.getOctets();
+ responderId.setByKey(key);
+ } else {
+ X500Name name = X500Name.getInstance(derTaggedObject.getObject());
+ String nameStr = name.toString();
+ responderId.setByName(nameStr);
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("OCSP decoding error: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ // marshal XAdES-C
+
+ // XAdES-X Type 1 timestamp
+ List<Node> timeStampNodesXadesX1 = new ArrayList<Node>();
+ timeStampNodesXadesX1.add(nlSigVal.item(0));
+ timeStampNodesXadesX1.add(signatureTimeStamp.getDomNode());
+ timeStampNodesXadesX1.add(completeCertificateRefs.getDomNode());
+ timeStampNodesXadesX1.add(completeRevocationRefs.getDomNode());
+
+ RevocationData tsaRevocationDataXadesX1 = new RevocationData();
+ LOG.log(POILogger.DEBUG, "creating XAdES-X time-stamp");
+ XAdESTimeStampType timeStampXadesX1 = createXAdESTimeStamp
+ (timeStampNodesXadesX1, tsaRevocationDataXadesX1);
+ if (tsaRevocationDataXadesX1.hasRevocationDataEntries()) {
+ ValidationDataType timeStampXadesX1ValidationData = createValidationData(tsaRevocationDataXadesX1);
+ insertXChild(unsignedSigProps, timeStampXadesX1ValidationData);
+ }
+
+ // marshal XAdES-X
+ unsignedSigProps.addNewSigAndRefsTimeStamp().set(timeStampXadesX1);
+
+ // XAdES-X-L
+ CertificateValuesType certificateValues = unsignedSigProps.addNewCertificateValues();
+ for (X509Certificate certificate : signingCertificateChain) {
+ EncapsulatedPKIDataType encapsulatedPKIDataType = certificateValues.addNewEncapsulatedX509Certificate();
+ try {
+ encapsulatedPKIDataType.setByteArrayValue(certificate.getEncoded());
+ } catch (CertificateEncodingException e) {
+ throw new RuntimeException("certificate encoding error: " + e.getMessage(), e);
+ }
+ }
+
+ RevocationValuesType revocationValues = unsignedSigProps.addNewRevocationValues();
+ createRevocationValues(revocationValues, revocationData);
+
+ // marshal XAdES-X-L
+ Node n = document.importNode(qualProps.getDomNode(), true);
+ qualNl.item(0).getParentNode().replaceChild(n, qualNl.item(0));
+ }
+
+ public static byte[] getC14nValue(List<Node> nodeList, String c14nAlgoId) {
+ ByteArrayOutputStream c14nValue = new ByteArrayOutputStream();
+ try {
+ for (Node node : nodeList) {
+ /*
+ * Re-initialize the c14n else the namespaces will get cached
+ * and will be missing from the c14n resulting nodes.
+ */
+ Canonicalizer c14n = Canonicalizer.getInstance(c14nAlgoId);
+ c14nValue.write(c14n.canonicalizeSubtree(node));
+ }
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new RuntimeException("c14n error: " + e.getMessage(), e);
+ }
+ return c14nValue.toByteArray();
+ }
+
+ @Override
+ public void preSign(Document document,
+ XMLSignatureFactory signatureFactory,
+ List<Reference> references, List<XMLObject> objects)
+ throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
+ // nothing to do here
+ }
+
+ private BigInteger getCrlNumber(X509CRL crl) {
+ try {
+ byte[] crlNumberExtensionValue = crl.getExtensionValue(Extension.cRLNumber.getId());
+ if (null == crlNumberExtensionValue) {
+ return null;
+ }
+
+ @SuppressWarnings("resource")
+ ASN1InputStream asn1InputStream = new ASN1InputStream(crlNumberExtensionValue);
+ ASN1OctetString octetString = (ASN1OctetString)asn1InputStream.readObject();
+ byte[] octets = octetString.getOctets();
+ asn1InputStream = new ASN1InputStream(octets);
+ ASN1Integer integer = (ASN1Integer)asn1InputStream.readObject();
+ BigInteger crlNumber = integer.getPositiveValue();
+ return crlNumber;
+ } catch (Exception e) {
+ throw new RuntimeException("I/O error: " + e.getMessage(), e);
+ }
+ }
+
+ private XAdESTimeStampType createXAdESTimeStamp(
+ List<Node> nodeList,
+ RevocationData revocationData) {
+ byte[] c14nSignatureValueElement = getC14nValue(nodeList, c14nAlgoId);
+
+ return createXAdESTimeStamp(c14nSignatureValueElement, revocationData);
+ }
+
+ private XAdESTimeStampType createXAdESTimeStamp(byte[] data, RevocationData revocationData) {
+ // create the time-stamp
+ byte[] timeStampToken;
+ try {
+ timeStampToken = signatureConfig.getTspService().timeStamp(data, revocationData);
+ } catch (Exception e) {
+ throw new RuntimeException("error while creating a time-stamp: "
+ + e.getMessage(), e);
+ }
+
+ // create a XAdES time-stamp container
+ XAdESTimeStampType xadesTimeStamp = XAdESTimeStampType.Factory.newInstance();
+ xadesTimeStamp.setId("time-stamp-" + UUID.randomUUID().toString());
+ CanonicalizationMethodType c14nMethod = xadesTimeStamp.addNewCanonicalizationMethod();
+ c14nMethod.setAlgorithm(c14nAlgoId);
+
+ // embed the time-stamp
+ EncapsulatedPKIDataType encapsulatedTimeStamp = xadesTimeStamp.addNewEncapsulatedTimeStamp();
+ encapsulatedTimeStamp.setByteArrayValue(timeStampToken);
+ encapsulatedTimeStamp.setId("time-stamp-token-" + UUID.randomUUID().toString());
+
+ return xadesTimeStamp;
+ }
+
+ private ValidationDataType createValidationData(
+ RevocationData revocationData) {
+ ValidationDataType validationData = ValidationDataType.Factory.newInstance();
+ RevocationValuesType revocationValues = validationData.addNewRevocationValues();
+ createRevocationValues(revocationValues, revocationData);
+ return validationData;
+ }
+
+ private void createRevocationValues(
+ RevocationValuesType revocationValues, RevocationData revocationData) {
+ if (revocationData.hasCRLs()) {
+ CRLValuesType crlValues = revocationValues.addNewCRLValues();
+ for (byte[] crl : revocationData.getCRLs()) {
+ EncapsulatedPKIDataType encapsulatedCrlValue = crlValues.addNewEncapsulatedCRLValue();
+ encapsulatedCrlValue.setByteArrayValue(crl);
+ }
+ }
+ if (revocationData.hasOCSPs()) {
+ OCSPValuesType ocspValues = revocationValues.addNewOCSPValues();
+ for (byte[] ocsp : revocationData.getOCSPs()) {
+ EncapsulatedPKIDataType encapsulatedOcspValue = ocspValues.addNewEncapsulatedOCSPValue();
+ encapsulatedOcspValue.setByteArrayValue(ocsp);
+ }
+ }
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java new file mode 100644 index 0000000000..dea586463b --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RelationshipTransformService.java @@ -0,0 +1,246 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.Provider;
+import java.security.Security;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.xml.crypto.Data;
+import javax.xml.crypto.MarshalException;
+import javax.xml.crypto.OctetStreamData;
+import javax.xml.crypto.XMLCryptoContext;
+import javax.xml.crypto.XMLStructure;
+import javax.xml.crypto.dom.DOMStructure;
+import javax.xml.crypto.dsig.TransformException;
+import javax.xml.crypto.dsig.TransformService;
+import javax.xml.crypto.dsig.spec.TransformParameterSpec;
+
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.util.XmlSort;
+import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlObject;
+import org.apache.xmlbeans.XmlOptions;
+import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.CTRelationshipReference;
+import org.openxmlformats.schemas.xpackage.x2006.digitalSignature.RelationshipReferenceDocument;
+import org.openxmlformats.schemas.xpackage.x2006.relationships.CTRelationship;
+import org.openxmlformats.schemas.xpackage.x2006.relationships.CTRelationships;
+import org.openxmlformats.schemas.xpackage.x2006.relationships.RelationshipsDocument;
+import org.openxmlformats.schemas.xpackage.x2006.relationships.STTargetMode;
+import org.w3.x2000.x09.xmldsig.TransformDocument;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+/**
+ * JSR105 implementation of the RelationshipTransform transformation.
+ *
+ * <p>
+ * Specs: http://openiso.org/Ecma/376/Part2/12.2.4#26
+ * </p>
+ */
+public class RelationshipTransformService extends TransformService {
+
+ public static final String TRANSFORM_URI = "http://schemas.openxmlformats.org/package/2006/RelationshipTransform";
+
+ private final List<String> sourceIds;
+
+ private static final POILogger LOG = POILogFactory.getLogger(RelationshipTransformService.class);
+
+ /**
+ * Relationship Transform parameter specification class.
+ */
+ public static class RelationshipTransformParameterSpec implements TransformParameterSpec {
+ List<String> sourceIds = new ArrayList<String>();
+ public void addRelationshipReference(String relationshipId) {
+ sourceIds.add(relationshipId);
+ }
+ public boolean hasSourceIds() {
+ return !sourceIds.isEmpty();
+ }
+ }
+
+
+ public RelationshipTransformService() {
+ super();
+ LOG.log(POILogger.DEBUG, "constructor");
+ this.sourceIds = new ArrayList<String>();
+ }
+
+ /**
+ * Register the provider for this TransformService
+ *
+ * @see javax.xml.crypto.dsig.TransformService
+ */
+ public static synchronized void registerDsigProvider() {
+ // the xml signature classes will try to find a special TransformerService,
+ // which is ofcourse unknown to JCE before ...
+ final String dsigProvider = "POIXmlDsigProvider";
+ if (Security.getProperty(dsigProvider) == null) {
+ Provider p = new Provider(dsigProvider, 1.0, dsigProvider){
+ static final long serialVersionUID = 1L;
+ };
+ p.put("TransformService." + TRANSFORM_URI, RelationshipTransformService.class.getName());
+ p.put("TransformService." + TRANSFORM_URI + " MechanismType", "DOM");
+ Security.addProvider(p);
+ }
+ }
+
+
+ @Override
+ public void init(TransformParameterSpec params) throws InvalidAlgorithmParameterException {
+ LOG.log(POILogger.DEBUG, "init(params)");
+ if (!(params instanceof RelationshipTransformParameterSpec)) {
+ throw new InvalidAlgorithmParameterException();
+ }
+ RelationshipTransformParameterSpec relParams = (RelationshipTransformParameterSpec) params;
+ for (String sourceId : relParams.sourceIds) {
+ this.sourceIds.add(sourceId);
+ }
+ }
+
+ @Override
+ public void init(XMLStructure parent, XMLCryptoContext context) throws InvalidAlgorithmParameterException {
+ LOG.log(POILogger.DEBUG, "init(parent,context)");
+ LOG.log(POILogger.DEBUG, "parent java type: " + parent.getClass().getName());
+ DOMStructure domParent = (DOMStructure) parent;
+ Node parentNode = domParent.getNode();
+
+ try {
+ TransformDocument transDoc = TransformDocument.Factory.parse(parentNode);
+ XmlObject xoList[] = transDoc.getTransform().selectChildren(RelationshipReferenceDocument.type.getDocumentElementName());
+ if (xoList.length == 0) {
+ LOG.log(POILogger.WARN, "no RelationshipReference/@SourceId parameters present");
+ }
+ for (XmlObject xo : xoList) {
+ String sourceId = ((CTRelationshipReference)xo).getSourceId();
+ LOG.log(POILogger.DEBUG, "sourceId: ", sourceId);
+ this.sourceIds.add(sourceId);
+ }
+ } catch (XmlException e) {
+ throw new InvalidAlgorithmParameterException(e);
+ }
+ }
+
+ @Override
+ public void marshalParams(XMLStructure parent, XMLCryptoContext context) throws MarshalException {
+ LOG.log(POILogger.DEBUG, "marshallParams(parent,context)");
+ DOMStructure domParent = (DOMStructure) parent;
+ Element parentNode = (Element)domParent.getNode();
+ // parentNode.setAttributeNS(XML_NS, "xmlns:mdssi", XML_DIGSIG_NS);
+ Document doc = parentNode.getOwnerDocument();
+
+ for (String sourceId : this.sourceIds) {
+ RelationshipReferenceDocument relRef = RelationshipReferenceDocument.Factory.newInstance();
+ relRef.addNewRelationshipReference().setSourceId(sourceId);
+ Node n = relRef.getRelationshipReference().getDomNode();
+ n = doc.importNode(n, true);
+ parentNode.appendChild(n);
+ }
+ }
+
+ public AlgorithmParameterSpec getParameterSpec() {
+ LOG.log(POILogger.DEBUG, "getParameterSpec");
+ return null;
+ }
+
+ public Data transform(Data data, XMLCryptoContext context) throws TransformException {
+ LOG.log(POILogger.DEBUG, "transform(data,context)");
+ LOG.log(POILogger.DEBUG, "data java type: " + data.getClass().getName());
+ OctetStreamData octetStreamData = (OctetStreamData) data;
+ LOG.log(POILogger.DEBUG, "URI: " + octetStreamData.getURI());
+ InputStream octetStream = octetStreamData.getOctetStream();
+
+ RelationshipsDocument relDoc;
+ try {
+ relDoc = RelationshipsDocument.Factory.parse(octetStream);
+ } catch (Exception e) {
+ throw new TransformException(e.getMessage(), e);
+ }
+ LOG.log(POILogger.DEBUG, "relationships document", relDoc);
+
+ CTRelationships rels = relDoc.getRelationships();
+ List<CTRelationship> relList = rels.getRelationshipList();
+ Iterator<CTRelationship> relIter = rels.getRelationshipList().iterator();
+ while (relIter.hasNext()) {
+ CTRelationship rel = relIter.next();
+ /*
+ * See: ISO/IEC 29500-2:2008(E) - 13.2.4.24 Relationships Transform
+ * Algorithm.
+ */
+ if (!this.sourceIds.contains(rel.getId())) {
+ LOG.log(POILogger.DEBUG, "removing element: " + rel.getId());
+ relIter.remove();
+ } else {
+ if (!rel.isSetTargetMode()) {
+ rel.setTargetMode(STTargetMode.INTERNAL);
+ }
+ }
+ }
+
+ // TODO: remove non element nodes ???
+ LOG.log(POILogger.DEBUG, "# Relationship elements", relList.size());
+
+ XmlSort.sort(rels, new Comparator<XmlCursor>(){
+ public int compare(XmlCursor c1, XmlCursor c2) {
+ String id1 = ((CTRelationship)c1.getObject()).getId();
+ String id2 = ((CTRelationship)c2.getObject()).getId();
+ return id1.compareTo(id2);
+ }
+ });
+
+ try {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ XmlOptions xo = new XmlOptions();
+ xo.setSaveNoXmlDecl();
+ relDoc.save(bos, xo);
+ return new OctetStreamData(new ByteArrayInputStream(bos.toByteArray()));
+ } catch (IOException e) {
+ throw new TransformException(e.getMessage(), e);
+ }
+ }
+
+ public Data transform(Data data, XMLCryptoContext context, OutputStream os) throws TransformException {
+ LOG.log(POILogger.DEBUG, "transform(data,context,os)");
+ return null;
+ }
+
+ public boolean isFeatureSupported(String feature) {
+ LOG.log(POILogger.DEBUG, "isFeatureSupported(feature)");
+ return false;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationData.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationData.java new file mode 100644 index 0000000000..5f0089a53c --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationData.java @@ -0,0 +1,131 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.security.cert.CRLException;
+import java.security.cert.X509CRL;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Container class for PKI revocation data.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class RevocationData {
+
+ private final List<byte[]> crls;
+
+ private final List<byte[]> ocsps;
+
+ /**
+ * Default constructor.
+ */
+ public RevocationData() {
+ this.crls = new ArrayList<byte[]>();
+ this.ocsps = new ArrayList<byte[]>();
+ }
+
+ /**
+ * Adds a CRL to this revocation data set.
+ *
+ * @param encodedCrl
+ */
+ public void addCRL(byte[] encodedCrl) {
+ this.crls.add(encodedCrl);
+ }
+
+ /**
+ * Adds a CRL to this revocation data set.
+ *
+ * @param crl
+ */
+ public void addCRL(X509CRL crl) {
+ byte[] encodedCrl;
+ try {
+ encodedCrl = crl.getEncoded();
+ } catch (CRLException e) {
+ throw new IllegalArgumentException("CRL coding error: "
+ + e.getMessage(), e);
+ }
+ addCRL(encodedCrl);
+ }
+
+ /**
+ * Adds an OCSP response to this revocation data set.
+ *
+ * @param encodedOcsp
+ */
+ public void addOCSP(byte[] encodedOcsp) {
+ this.ocsps.add(encodedOcsp);
+ }
+
+ /**
+ * Gives back a list of all CRLs.
+ *
+ * @return
+ */
+ public List<byte[]> getCRLs() {
+ return this.crls;
+ }
+
+ /**
+ * Gives back a list of all OCSP responses.
+ *
+ * @return
+ */
+ public List<byte[]> getOCSPs() {
+ return this.ocsps;
+ }
+
+ /**
+ * Returns <code>true</code> if this revocation data set holds OCSP
+ * responses.
+ *
+ * @return
+ */
+ public boolean hasOCSPs() {
+ return false == this.ocsps.isEmpty();
+ }
+
+ /**
+ * Returns <code>true</code> if this revocation data set holds CRLs.
+ *
+ * @return
+ */
+ public boolean hasCRLs() {
+ return false == this.crls.isEmpty();
+ }
+
+ /**
+ * Returns <code>true</code> if this revocation data is not empty.
+ *
+ * @return
+ */
+ public boolean hasRevocationDataEntries() {
+ return hasOCSPs() || hasCRLs();
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationDataService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationDataService.java new file mode 100644 index 0000000000..b519c40e3d --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/RevocationDataService.java @@ -0,0 +1,47 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Interface for a service that retrieves revocation data about some given
+ * certificate chain.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface RevocationDataService {
+
+ /**
+ * Gives back the revocation data corresponding with the given certificate
+ * chain.
+ *
+ * @param certificateChain
+ * @return
+ */
+ RevocationData getRevocationData(List<X509Certificate> certificateChain);
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignaturePolicyService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignaturePolicyService.java new file mode 100644 index 0000000000..1dbe1b1a15 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/SignaturePolicyService.java @@ -0,0 +1,65 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+/**
+ * Interface for the signature policy service.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface SignaturePolicyService {
+
+ /**
+ * Gives back the signature policy identifier URI.
+ *
+ * @return
+ */
+ String getSignaturePolicyIdentifier();
+
+ /**
+ * Gives back the short description of the signature policy or
+ * <code>null</code> if a description is not available.
+ *
+ * @return the description, or <code>null</code>.
+ */
+ String getSignaturePolicyDescription();
+
+ /**
+ * Gives back the download URL where the signature policy document can be
+ * found. Can be <code>null</code> in case such a download location does not
+ * exist.
+ *
+ * @return the download URL, or <code>null</code>.
+ */
+ String getSignaturePolicyDownloadUrl();
+
+ /**
+ * Gives back the signature policy document.
+ *
+ * @return the bytes of the signature policy document.
+ */
+ byte[] getSignaturePolicyDocument();
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TSPTimeStampService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TSPTimeStampService.java new file mode 100644 index 0000000000..0937b0f360 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TSPTimeStampService.java @@ -0,0 +1,253 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.security.SecureRandom;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.bind.DatatypeConverter;
+
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.cmp.PKIFailureInfo;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder;
+import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.tsp.TimeStampRequest;
+import org.bouncycastle.tsp.TimeStampRequestGenerator;
+import org.bouncycastle.tsp.TimeStampResponse;
+import org.bouncycastle.tsp.TimeStampToken;
+
+/**
+ * A TSP time-stamp service implementation.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class TSPTimeStampService implements TimeStampService {
+
+ private static final POILogger LOG = POILogFactory.getLogger(TSPTimeStampService.class);
+
+ private SignatureConfig signatureConfig;
+
+ /**
+ * Maps the digest algorithm to corresponding OID value.
+ */
+ public ASN1ObjectIdentifier mapDigestAlgoToOID(HashAlgorithm digestAlgo) {
+ switch (digestAlgo) {
+ case sha1: return X509ObjectIdentifiers.id_SHA1;
+ case sha256: return NISTObjectIdentifiers.id_sha256;
+ case sha384: return NISTObjectIdentifiers.id_sha384;
+ case sha512: return NISTObjectIdentifiers.id_sha512;
+ default:
+ throw new IllegalArgumentException("unsupported digest algo: " + digestAlgo);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public byte[] timeStamp(byte[] data, RevocationData revocationData)
+ throws Exception {
+ // digest the message
+ MessageDigest messageDigest = CryptoFunctions.getMessageDigest(signatureConfig.getTspDigestAlgo());
+ byte[] digest = messageDigest.digest(data);
+
+ // generate the TSP request
+ BigInteger nonce = new BigInteger(128, new SecureRandom());
+ TimeStampRequestGenerator requestGenerator = new TimeStampRequestGenerator();
+ requestGenerator.setCertReq(true);
+ String requestPolicy = signatureConfig.getTspRequestPolicy();
+ if (requestPolicy != null) {
+ requestGenerator.setReqPolicy(new ASN1ObjectIdentifier(requestPolicy));
+ }
+ ASN1ObjectIdentifier digestAlgoOid = mapDigestAlgoToOID(signatureConfig.getTspDigestAlgo());
+ TimeStampRequest request = requestGenerator.generate(digestAlgoOid, digest, nonce);
+ byte[] encodedRequest = request.getEncoded();
+
+ // create the HTTP POST request
+ Proxy proxy = Proxy.NO_PROXY;
+ if (signatureConfig.getProxyUrl() != null) {
+ URL proxyUrl = new URL(signatureConfig.getProxyUrl());
+ String host = proxyUrl.getHost();
+ int port = proxyUrl.getPort();
+ proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, (port == -1 ? 80 : port)));
+ }
+
+ HttpURLConnection huc = (HttpURLConnection)new URL(signatureConfig.getTspUrl()).openConnection(proxy);
+
+ if (signatureConfig.getTspUser() != null) {
+ String userPassword = signatureConfig.getTspUser() + ":" + signatureConfig.getTspPass();
+ String encoding = DatatypeConverter.printBase64Binary(userPassword.getBytes(Charset.forName("iso-8859-1")));
+ huc.setRequestProperty("Authorization", "Basic " + encoding);
+ }
+
+ huc.setDoOutput(true); // also sets method to POST.
+ huc.setRequestProperty("User-Agent", signatureConfig.getUserAgent());
+ huc.setRequestProperty("Content-Type", signatureConfig.isTspOldProtocol()
+ ? "application/timestamp-request"
+ : "application/timestamp-query;charset=ISO-8859-1");
+
+ OutputStream hucOut = huc.getOutputStream();
+ hucOut.write(encodedRequest);
+
+ // invoke TSP service
+ huc.connect();
+
+ int statusCode = huc.getResponseCode();
+ if (statusCode != 200) {
+ LOG.log(POILogger.ERROR, "Error contacting TSP server ", signatureConfig.getTspUrl());
+ throw new IOException("Error contacting TSP server " + signatureConfig.getTspUrl());
+ }
+
+ // HTTP input validation
+ String contentType = huc.getHeaderField("Content-Type");
+ if (null == contentType) {
+ throw new RuntimeException("missing Content-Type header");
+ }
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ IOUtils.copy(huc.getInputStream(), bos);
+ LOG.log(POILogger.DEBUG, "response content: ", bos.toString());
+
+ if (!contentType.startsWith(signatureConfig.isTspOldProtocol()
+ ? "application/timestamp-response"
+ : "application/timestamp-reply"
+ )) {
+ throw new RuntimeException("invalid Content-Type: " + contentType);
+ }
+
+ if (bos.size() == 0) {
+ throw new RuntimeException("Content-Length is zero");
+ }
+
+ // TSP response parsing and validation
+ TimeStampResponse timeStampResponse = new TimeStampResponse(bos.toByteArray());
+ timeStampResponse.validate(request);
+
+ if (0 != timeStampResponse.getStatus()) {
+ LOG.log(POILogger.DEBUG, "status: " + timeStampResponse.getStatus());
+ LOG.log(POILogger.DEBUG, "status string: " + timeStampResponse.getStatusString());
+ PKIFailureInfo failInfo = timeStampResponse.getFailInfo();
+ if (null != failInfo) {
+ LOG.log(POILogger.DEBUG, "fail info int value: " + failInfo.intValue());
+ if (/*PKIFailureInfo.unacceptedPolicy*/(1 << 8) == failInfo.intValue()) {
+ LOG.log(POILogger.DEBUG, "unaccepted policy");
+ }
+ }
+ throw new RuntimeException("timestamp response status != 0: "
+ + timeStampResponse.getStatus());
+ }
+ TimeStampToken timeStampToken = timeStampResponse.getTimeStampToken();
+ SignerId signerId = timeStampToken.getSID();
+ BigInteger signerCertSerialNumber = signerId.getSerialNumber();
+ X500Name signerCertIssuer = signerId.getIssuer();
+ LOG.log(POILogger.DEBUG, "signer cert serial number: " + signerCertSerialNumber);
+ LOG.log(POILogger.DEBUG, "signer cert issuer: " + signerCertIssuer);
+
+ // TSP signer certificates retrieval
+ Collection<X509CertificateHolder> certificates = timeStampToken.getCertificates().getMatches(null);
+
+ X509CertificateHolder signerCert = null;
+ Map<X500Name, X509CertificateHolder> certificateMap = new HashMap<X500Name, X509CertificateHolder>();
+ for (X509CertificateHolder certificate : certificates) {
+ if (signerCertIssuer.equals(certificate.getIssuer())
+ && signerCertSerialNumber.equals(certificate.getSerialNumber())) {
+ signerCert = certificate;
+ }
+ certificateMap.put(certificate.getSubject(), certificate);
+ }
+
+ // TSP signer cert path building
+ if (signerCert == null) {
+ throw new RuntimeException("TSP response token has no signer certificate");
+ }
+ List<X509Certificate> tspCertificateChain = new ArrayList<X509Certificate>();
+ JcaX509CertificateConverter x509converter = new JcaX509CertificateConverter();
+ x509converter.setProvider("BC");
+ X509CertificateHolder certificate = signerCert;
+ do {
+ LOG.log(POILogger.DEBUG, "adding to certificate chain: " + certificate.getSubject());
+ tspCertificateChain.add(x509converter.getCertificate(certificate));
+ if (certificate.getSubject().equals(certificate.getIssuer())) {
+ break;
+ }
+ certificate = certificateMap.get(certificate.getIssuer());
+ } while (null != certificate);
+
+ // verify TSP signer signature
+ X509CertificateHolder holder = new X509CertificateHolder(tspCertificateChain.get(0).getEncoded());
+ DefaultCMSSignatureAlgorithmNameGenerator nameGen = new DefaultCMSSignatureAlgorithmNameGenerator();
+ DefaultSignatureAlgorithmIdentifierFinder sigAlgoFinder = new DefaultSignatureAlgorithmIdentifierFinder();
+ DefaultDigestAlgorithmIdentifierFinder hashAlgoFinder = new DefaultDigestAlgorithmIdentifierFinder();
+ BcDigestCalculatorProvider calculator = new BcDigestCalculatorProvider();
+ BcRSASignerInfoVerifierBuilder verifierBuilder = new BcRSASignerInfoVerifierBuilder(nameGen, sigAlgoFinder, hashAlgoFinder, calculator);
+ SignerInformationVerifier verifier = verifierBuilder.build(holder);
+
+ timeStampToken.validate(verifier);
+
+ // verify TSP signer certificate
+ if (signatureConfig.getTspValidator() != null) {
+ signatureConfig.getTspValidator().validate(tspCertificateChain, revocationData);
+ }
+
+ LOG.log(POILogger.DEBUG, "time-stamp token time: "
+ + timeStampToken.getTimeStampInfo().getGenTime());
+
+ byte[] timestamp = timeStampToken.getEncoded();
+ return timestamp;
+ }
+
+ public void setSignatureConfig(SignatureConfig signatureConfig) {
+ this.signatureConfig = signatureConfig;
+ }
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampService.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampService.java new file mode 100644 index 0000000000..84cde92377 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampService.java @@ -0,0 +1,54 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig.SignatureConfigurable;
+
+
+/**
+ * Interface for a time-stamp service.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface TimeStampService extends SignatureConfigurable {
+
+ /**
+ * Gives back the encoded time-stamp token for the given array of data
+ * bytes. We assume that the time-stamp token itself contains its full
+ * certificate chain required for proper validation.
+ *
+ * @param data
+ * the data to be time-stamped.
+ * @param revocationData
+ * the optional container that needs to be filled up with the
+ * revocation data used to validate the TSA certificate chain.
+ * @return the DER encoded time-stamp token.
+ * @throws Exception
+ * in case something went wrong.
+ */
+ byte[] timeStamp(byte[] data, RevocationData revocationData)
+ throws Exception;
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampServiceValidator.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampServiceValidator.java new file mode 100644 index 0000000000..4d36be9ec5 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/services/TimeStampServiceValidator.java @@ -0,0 +1,51 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.services;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+/**
+ * Interface for trust validator of a TSP.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public interface TimeStampServiceValidator {
+
+ /**
+ * Validates the given certificate chain.
+ *
+ * @param certificateChain
+ * @param revocationData
+ * the optional data container that should be filled with
+ * revocation data that was used to validate the given
+ * certificate chain.
+ * @throws Exception
+ * in case the certificate chain is invalid.
+ */
+ void validate(List<X509Certificate> certificateChain,
+ RevocationData revocationData) throws Exception;
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java new file mode 100644 index 0000000000..a164046319 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/AddressDTO.java @@ -0,0 +1,51 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+import java.io.Serializable;
+import java.security.Identity;
+
+/**
+ * Address Data Transfer Object.
+ *
+ * @author Frank Cornelis
+ * @see Identity
+ *
+ */
+public class AddressDTO implements Serializable {
+
+ /*
+ * We implement serializable to allow this class to be used in distributed
+ * containers as defined in the Servlet v2.4 specification.
+ */
+
+ private static final long serialVersionUID = 1L;
+
+ public String streetAndNumber;
+
+ public String zip;
+
+ public String city;
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java new file mode 100644 index 0000000000..2f7c58c338 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/DigestInfo.java @@ -0,0 +1,56 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+import java.io.Serializable;
+
+import org.apache.poi.poifs.crypt.HashAlgorithm;
+
+/**
+ * Digest Information data transfer class.
+ */
+public class DigestInfo implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Main constructor.
+ *
+ * @param digestValue
+ * @param hashAlgo
+ * @param description
+ */
+ public DigestInfo(byte[] digestValue, HashAlgorithm hashAlgo, String description) {
+ this.digestValue = digestValue;
+ this.hashAlgo = hashAlgo;
+ this.description = description;
+ }
+
+ public final byte[] digestValue;
+
+ public final String description;
+
+ public final HashAlgorithm hashAlgo;
+}
diff --git a/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java new file mode 100644 index 0000000000..9cfa0aae25 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/poifs/crypt/dsig/spi/IdentityDTO.java @@ -0,0 +1,75 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+
+package org.apache.poi.poifs.crypt.dsig.spi;
+
+import java.io.Serializable;
+import java.util.GregorianCalendar;
+
+/**
+ * Identity Data Transfer Object.
+ *
+ * @author Frank Cornelis
+ *
+ */
+public class IdentityDTO implements Serializable {
+
+ /*
+ * We implement serializable to allow this class to be used in distributed
+ * containers as defined in the Servlet v2.4 specification.
+ */
+ private static final long serialVersionUID = 1L;
+
+ public String cardNumber;
+
+ public String chipNumber;
+
+ public GregorianCalendar cardValidityDateBegin;
+
+ public GregorianCalendar cardValidityDateEnd;
+
+ public String cardDeliveryMunicipality;
+
+ public String nationalNumber;
+
+ public String name;
+
+ public String firstName;
+
+ public String middleName;
+
+ public String nationality;
+
+ public String placeOfBirth;
+
+ public GregorianCalendar dateOfBirth;
+
+ public boolean male;
+
+ public boolean female;
+
+ public String nobleCondition;
+
+ public String duplicate;
+}
\ No newline at end of file diff --git a/src/ooxml/java/org/apache/poi/util/MethodUtils.java b/src/ooxml/java/org/apache/poi/util/MethodUtils.java new file mode 100644 index 0000000000..c006c85462 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/util/MethodUtils.java @@ -0,0 +1,1334 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.util;
+
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+
+/**
+ * <p> Utility reflection methods focussed on methods in general rather than properties in particular. </p>
+ *
+ * <h3>Known Limitations</h3>
+ * <h4>Accessing Public Methods In A Default Access Superclass</h4>
+ * <p>There is an issue when invoking public methods contained in a default access superclass.
+ * Reflection locates these methods fine and correctly assigns them as public.
+ * However, an <code>IllegalAccessException</code> is thrown if the method is invoked.</p>
+ *
+ * <p><code>MethodUtils</code> contains a workaround for this situation.
+ * It will attempt to call <code>setAccessible</code> on this method.
+ * If this call succeeds, then the method can be invoked as normal.
+ * This call will only succeed when the application has sufficient security privilages.
+ * If this call fails then a warning will be logged and the method may fail.</p>
+ *
+ * @author Craig R. McClanahan
+ * @author Ralph Schaer
+ * @author Chris Audley
+ * @author Rey François
+ * @author Gregor Raýman
+ * @author Jan Sorensen
+ * @author Robert Burrell Donkin
+ */
+
+public class MethodUtils {
+
+ // --------------------------------------------------------- Private Methods
+
+ /**
+ * Only log warning about accessibility work around once.
+ * <p>
+ * Note that this is broken when this class is deployed via a shared
+ * classloader in a container, as the warning message will be emitted
+ * only once, not once per webapp. However making the warning appear
+ * once per webapp means having a map keyed by context classloader
+ * which introduces nasty memory-leak problems. As this warning is
+ * really optional we can ignore this problem; only one of the webapps
+ * will get the warning in its logs but that should be good enough.
+ */
+ private static boolean loggedAccessibleWarning = false;
+
+ /**
+ * Indicates whether methods should be cached for improved performance.
+ * <p>
+ * Note that when this class is deployed via a shared classloader in
+ * a container, this will affect all webapps. However making this
+ * configurable per webapp would mean having a map keyed by context classloader
+ * which may introduce memory-leak problems.
+ */
+ private static boolean CACHE_METHODS = true;
+
+ /** An empty class array */
+ private static final Class[] EMPTY_CLASS_PARAMETERS = new Class[0];
+ /** An empty object array */
+ private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];
+
+ // --------------------------------------------------------- Public Methods
+
+ /**
+ * <p>Invoke a named method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than <code>invokeExactMethod()</code>.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeMethod(Object object,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeMethod(
+ Object object,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeMethod(object, methodName, args);
+
+ }
+
+
+ /**
+ * <p>Invoke a named method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
+ * </p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeMethod(
+ Object object,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeMethod(object, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Invoke a named method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link
+ * #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeMethod(
+ Object object,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ Method method = getMatchingAccessibleMethod(
+ object.getClass(),
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on object: " + object.getClass().getName());
+ }
+ return method.invoke(object, args);
+ }
+
+
+ /**
+ * <p>Invoke a method whose parameter type matches exactly the object
+ * type.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(
+ Object object,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeExactMethod(object, methodName, args);
+
+ }
+
+
+ /**
+ * <p>Invoke a method whose parameter types match exactly the object
+ * types.</p>
+ *
+ * <p> This uses reflection to invoke the method obtained from a call to
+ * <code>getAccessibleMethod()</code>.</p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(
+ Object object,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeExactMethod(object, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Invoke a method whose parameter types match exactly the parameter
+ * types given.</p>
+ *
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * <code>getAccessibleMethod()</code>.</p>
+ *
+ * @param object invoke method on this object
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactMethod(
+ Object object,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+
+ Method method = getAccessibleMethod(
+ object.getClass(),
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on object: " + object.getClass().getName());
+ }
+ return method.invoke(object, args);
+
+ }
+
+ /**
+ * <p>Invoke a static method whose parameter types match exactly the parameter
+ * types given.</p>
+ *
+ * <p>This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+
+ Method method = getAccessibleMethod(
+ objectClass,
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on class: " + objectClass.getName());
+ }
+ return method.invoke(null, args);
+
+ }
+
+ /**
+ * <p>Invoke a named static method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link #invokeExactMethod(Object, String, Object[], Class[])}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeStaticMethod (objectClass, methodName, args);
+
+ }
+
+
+ /**
+ * <p>Invoke a named static method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
+ * </p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeStaticMethod (objectClass, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Invoke a named static method whose parameter type matches the object type.</p>
+ *
+ * <p>The behaviour of this method is less deterministic
+ * than {@link
+ * #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args,Class[] parameterTypes)}.
+ * It loops through all methods with names that match
+ * and then executes the first it finds with compatable parameters.</p>
+ *
+ * <p>This method supports calls to methods taking primitive parameters
+ * via passing in wrapping classes. So, for example, a <code>Boolean</code> class
+ * would match a <code>boolean</code> primitive.</p>
+ *
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @param parameterTypes match these parameters - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args,
+ Class[] parameterTypes)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ if (parameterTypes == null) {
+ parameterTypes = EMPTY_CLASS_PARAMETERS;
+ }
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+
+ Method method = getMatchingAccessibleMethod(
+ objectClass,
+ methodName,
+ parameterTypes);
+ if (method == null) {
+ throw new NoSuchMethodException("No such accessible method: " +
+ methodName + "() on class: " + objectClass.getName());
+ }
+ return method.invoke(null, args);
+ }
+
+
+ /**
+ * <p>Invoke a static method whose parameter type matches exactly the object
+ * type.</p>
+ *
+ * <p> This is a convenient wrapper for
+ * {@link #invokeExactStaticMethod(Class objectClass,String methodName,Object [] args)}.
+ * </p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param arg use this argument
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object arg)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+
+ Object[] args = {arg};
+ return invokeExactStaticMethod (objectClass, methodName, args);
+
+ }
+
+
+ /**
+ * <p>Invoke a static method whose parameter types match exactly the object
+ * types.</p>
+ *
+ * <p> This uses reflection to invoke the method obtained from a call to
+ * {@link #getAccessibleMethod(Class, String, Class[])}.</p>
+ *
+ * @param objectClass invoke static method on this class
+ * @param methodName get method with this name
+ * @param args use these arguments - treat null as empty array
+ * @return The value returned by the invoked method
+ *
+ * @throws NoSuchMethodException if there is no such accessible method
+ * @throws InvocationTargetException wraps an exception thrown by the
+ * method invoked
+ * @throws IllegalAccessException if the requested method is not accessible
+ * via reflection
+ */
+ public static Object invokeExactStaticMethod(
+ Class objectClass,
+ String methodName,
+ Object[] args)
+ throws
+ NoSuchMethodException,
+ IllegalAccessException,
+ InvocationTargetException {
+ if (args == null) {
+ args = EMPTY_OBJECT_ARRAY;
+ }
+ int arguments = args.length;
+ Class[] parameterTypes = new Class[arguments];
+ for (int i = 0; i < arguments; i++) {
+ parameterTypes[i] = args[i].getClass();
+ }
+ return invokeExactStaticMethod(objectClass, methodName, args, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) with given name and a single parameter. If no such method
+ * can be found, return <code>null</code>.
+ * Basically, a convenience wrapper that constructs a <code>Class</code>
+ * array for you.</p>
+ *
+ * @param clazz get method from this class
+ * @param methodName get method with this name
+ * @param parameterType taking this type of parameter
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(
+ Class clazz,
+ String methodName,
+ Class parameterType) {
+
+ Class[] parameterTypes = {parameterType};
+ return getAccessibleMethod(clazz, methodName, parameterTypes);
+
+ }
+
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) with given name and parameters. If no such method
+ * can be found, return <code>null</code>.
+ * This is just a convenient wrapper for
+ * {@link #getAccessibleMethod(Method method)}.</p>
+ *
+ * @param clazz get method from this class
+ * @param methodName get method with this name
+ * @param parameterTypes with these parameters types
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(
+ Class clazz,
+ String methodName,
+ Class[] parameterTypes) {
+
+ try {
+ MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, true);
+ Method method = getAccessibleMethod
+ (clazz, clazz.getMethod(methodName, parameterTypes));
+ return method;
+ } catch (NoSuchMethodException e) {
+ return (null);
+ }
+
+ }
+
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return <code>null</code>.</p>
+ *
+ * @param method The method that we wish to call
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(Method method) {
+
+ // Make sure we have a method to check
+ if (method == null) {
+ return (null);
+ }
+
+ return getAccessibleMethod(method.getDeclaringClass(), method);
+
+ }
+
+
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified Method. If no such method
+ * can be found, return <code>null</code>.</p>
+ *
+ * @param clazz The class of the object
+ * @param method The method that we wish to call
+ * @return The accessible method
+ */
+ public static Method getAccessibleMethod(Class clazz, Method method) {
+
+ // Make sure we have a method to check
+ if (method == null) {
+ return (null);
+ }
+
+ // If the requested method is not public we cannot call it
+ if (!Modifier.isPublic(method.getModifiers())) {
+ return (null);
+ }
+
+ boolean sameClass = true;
+ if (clazz == null) {
+ clazz = method.getDeclaringClass();
+ } else {
+ sameClass = clazz.equals(method.getDeclaringClass());
+ if (!method.getDeclaringClass().isAssignableFrom(clazz)) {
+ throw new IllegalArgumentException(clazz.getName() +
+ " is not assignable from " + method.getDeclaringClass().getName());
+ }
+ }
+
+ // If the class is public, we are done
+ if (Modifier.isPublic(clazz.getModifiers())) {
+ if (!sameClass && !Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
+ setMethodAccessible(method); // Default access superclass workaround
+ }
+ return (method);
+ }
+
+ String methodName = method.getName();
+ Class[] parameterTypes = method.getParameterTypes();
+
+ // Check the implemented interfaces and subinterfaces
+ method =
+ getAccessibleMethodFromInterfaceNest(clazz,
+ methodName,
+ parameterTypes);
+
+ // Check the superclass chain
+ if (method == null) {
+ method = getAccessibleMethodFromSuperclass(clazz,
+ methodName,
+ parameterTypes);
+ }
+
+ return (method);
+
+ }
+
+
+ // -------------------------------------------------------- Private Methods
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) by scanning through the superclasses. If no such method
+ * can be found, return <code>null</code>.</p>
+ *
+ * @param clazz Class to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
+ */
+ private static Method getAccessibleMethodFromSuperclass
+ (Class clazz, String methodName, Class[] parameterTypes) {
+
+ Class parentClazz = clazz.getSuperclass();
+ while (parentClazz != null) {
+ if (Modifier.isPublic(parentClazz.getModifiers())) {
+ try {
+ return parentClazz.getMethod(methodName, parameterTypes);
+ } catch (NoSuchMethodException e) {
+ return null;
+ }
+ }
+ parentClazz = parentClazz.getSuperclass();
+ }
+ return null;
+ }
+
+ /**
+ * <p>Return an accessible method (that is, one that can be invoked via
+ * reflection) that implements the specified method, by scanning through
+ * all implemented interfaces and subinterfaces. If no such method
+ * can be found, return <code>null</code>.</p>
+ *
+ * <p> There isn't any good reason why this method must be private.
+ * It is because there doesn't seem any reason why other classes should
+ * call this rather than the higher level methods.</p>
+ *
+ * @param clazz Parent class for the interfaces to be checked
+ * @param methodName Method name of the method we wish to call
+ * @param parameterTypes The parameter type signatures
+ */
+ private static Method getAccessibleMethodFromInterfaceNest
+ (Class clazz, String methodName, Class[] parameterTypes) {
+
+ Method method = null;
+
+ // Search up the superclass chain
+ for (; clazz != null; clazz = clazz.getSuperclass()) {
+
+ // Check the implemented interfaces of the parent class
+ Class[] interfaces = clazz.getInterfaces();
+ for (int i = 0; i < interfaces.length; i++) {
+
+ // Is this interface public?
+ if (!Modifier.isPublic(interfaces[i].getModifiers())) {
+ continue;
+ }
+
+ // Does the method exist on this interface?
+ try {
+ method = interfaces[i].getDeclaredMethod(methodName,
+ parameterTypes);
+ } catch (NoSuchMethodException e) {
+ /* Swallow, if no method is found after the loop then this
+ * method returns null.
+ */
+ }
+ if (method != null) {
+ return method;
+ }
+
+ // Recursively check our parent interfaces
+ method =
+ getAccessibleMethodFromInterfaceNest(interfaces[i],
+ methodName,
+ parameterTypes);
+ if (method != null) {
+ return method;
+ }
+
+ }
+
+ }
+
+ // If we found a method return it
+ if (method != null) {
+ return (method);
+ }
+
+ // We did not find anything
+ return (null);
+
+ }
+
+ /**
+ * <p>Find an accessible method that matches the given name and has compatible parameters.
+ * Compatible parameters mean that every method parameter is assignable from
+ * the given parameters.
+ * In other words, it finds a method with the given name
+ * that will take the parameters given.<p>
+ *
+ * <p>This method is slightly undeterminstic since it loops
+ * through methods names and return the first matching method.</p>
+ *
+ * <p>This method is used by
+ * {@link
+ * #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
+ *
+ * <p>This method can match primitive parameter by passing in wrapper classes.
+ * For example, a <code>Boolean</code> will match a primitive <code>boolean</code>
+ * parameter.
+ *
+ * @param clazz find method in this class
+ * @param methodName find method with this name
+ * @param parameterTypes find method with compatible parameters
+ * @return The accessible method
+ */
+ public static Method getMatchingAccessibleMethod(
+ Class clazz,
+ String methodName,
+ Class[] parameterTypes) {
+ // trace logging
+ Log log = LogFactory.getLog(MethodUtils.class);
+ if (log.isTraceEnabled()) {
+ log.trace("Matching name=" + methodName + " on " + clazz);
+ }
+ MethodDescriptor md = new MethodDescriptor(clazz, methodName, parameterTypes, false);
+
+ // see if we can find the method directly
+ // most of the time this works and it's much faster
+ try {
+ Method method = clazz.getMethod(methodName, parameterTypes);
+ if (log.isTraceEnabled()) {
+ log.trace("Found straight match: " + method);
+ log.trace("isPublic:" + Modifier.isPublic(method.getModifiers()));
+ }
+
+ setMethodAccessible(method); // Default access superclass workaround
+
+ return method;
+
+ } catch (NoSuchMethodException e) { /* SWALLOW */ }
+
+ // search through all methods
+ int paramSize = parameterTypes.length;
+ Method bestMatch = null;
+ Method[] methods = clazz.getMethods();
+ float bestMatchCost = Float.MAX_VALUE;
+ float myCost = Float.MAX_VALUE;
+ for (int i = 0, size = methods.length; i < size ; i++) {
+ if (methods[i].getName().equals(methodName)) {
+ // log some trace information
+ if (log.isTraceEnabled()) {
+ log.trace("Found matching name:");
+ log.trace(methods[i]);
+ }
+
+ // compare parameters
+ Class[] methodsParams = methods[i].getParameterTypes();
+ int methodParamSize = methodsParams.length;
+ if (methodParamSize == paramSize) {
+ boolean match = true;
+ for (int n = 0 ; n < methodParamSize; n++) {
+ if (log.isTraceEnabled()) {
+ log.trace("Param=" + parameterTypes[n].getName());
+ log.trace("Method=" + methodsParams[n].getName());
+ }
+ if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
+ if (log.isTraceEnabled()) {
+ log.trace(methodsParams[n] + " is not assignable from "
+ + parameterTypes[n]);
+ }
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ // get accessible version of method
+ Method method = getAccessibleMethod(clazz, methods[i]);
+ if (method != null) {
+ if (log.isTraceEnabled()) {
+ log.trace(method + " accessible version of "
+ + methods[i]);
+ }
+ setMethodAccessible(method); // Default access superclass workaround
+ myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
+ if ( myCost < bestMatchCost ) {
+ bestMatch = method;
+ bestMatchCost = myCost;
+ }
+ }
+
+ log.trace("Couldn't find accessible method.");
+ }
+ }
+ }
+ }
+ if ( bestMatch == null ){
+ // didn't find a match
+ log.trace("No match found.");
+ }
+
+ return bestMatch;
+ }
+
+ public static <T> Constructor<T> getMatchingAccessibleConstructor(
+ Class<T> clazz,
+ Class[] parameterTypes) {
+ // trace logging
+ Log log = LogFactory.getLog(MethodUtils.class);
+ MethodDescriptor md = new MethodDescriptor(clazz, "dummy", parameterTypes, false);
+
+ // see if we can find the method directly
+ // most of the time this works and it's much faster
+ try {
+ Constructor<T> constructor = clazz.getConstructor(parameterTypes);
+ if (log.isTraceEnabled()) {
+ log.trace("Found straight match: " + constructor);
+ log.trace("isPublic:" + Modifier.isPublic(constructor.getModifiers()));
+ }
+
+ setMethodAccessible(constructor); // Default access superclass workaround
+
+ return constructor;
+
+ } catch (NoSuchMethodException e) { /* SWALLOW */ }
+
+ // search through all methods
+ int paramSize = parameterTypes.length;
+ Constructor<T> bestMatch = null;
+ Constructor<?>[] constructors = clazz.getConstructors();
+ float bestMatchCost = Float.MAX_VALUE;
+ float myCost = Float.MAX_VALUE;
+ for (int i = 0, size = constructors.length; i < size ; i++) {
+ // compare parameters
+ Class[] methodsParams = constructors[i].getParameterTypes();
+ int methodParamSize = methodsParams.length;
+ if (methodParamSize == paramSize) {
+ boolean match = true;
+ for (int n = 0 ; n < methodParamSize; n++) {
+ if (log.isTraceEnabled()) {
+ log.trace("Param=" + parameterTypes[n].getName());
+ log.trace("Method=" + methodsParams[n].getName());
+ }
+ if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
+ if (log.isTraceEnabled()) {
+ log.trace(methodsParams[n] + " is not assignable from "
+ + parameterTypes[n]);
+ }
+ match = false;
+ break;
+ }
+ }
+
+ if (match) {
+ // get accessible version of method
+ Constructor<T> cons = (Constructor<T>)constructors[i];
+ myCost = getTotalTransformationCost(parameterTypes,cons.getParameterTypes());
+ if ( myCost < bestMatchCost ) {
+ bestMatch = cons;
+ bestMatchCost = myCost;
+ }
+ }
+ }
+ }
+ if ( bestMatch == null ){
+ // didn't find a match
+ log.trace("No match found.");
+ }
+
+ return bestMatch;
+ }
+
+ /**
+ * Try to make the method accessible
+ * @param method The source arguments
+ */
+ private static void setMethodAccessible(Object method) {
+ try {
+ //
+ // XXX Default access superclass workaround
+ //
+ // When a public class has a default access superclass
+ // with public methods, these methods are accessible.
+ // Calling them from compiled code works fine.
+ //
+ // Unfortunately, using reflection to invoke these methods
+ // seems to (wrongly) to prevent access even when the method
+ // modifer is public.
+ //
+ // The following workaround solves the problem but will only
+ // work from sufficiently privilages code.
+ //
+ // Better workarounds would be greatfully accepted.
+ //
+ if (method instanceof Method) {
+ ((Method)method).setAccessible(true);
+ } else if (method instanceof Constructor) {
+ ((Constructor)method).setAccessible(true);
+ } else {
+ throw new RuntimeException("invalid parameter");
+ }
+
+ } catch (SecurityException se) {
+ // log but continue just in case the method.invoke works anyway
+ Log log = LogFactory.getLog(MethodUtils.class);
+ if (!loggedAccessibleWarning) {
+ boolean vulnerableJVM = false;
+ try {
+ String specVersion = System.getProperty("java.specification.version");
+ if (specVersion.charAt(0) == '1' &&
+ (specVersion.charAt(2) == '0' ||
+ specVersion.charAt(2) == '1' ||
+ specVersion.charAt(2) == '2' ||
+ specVersion.charAt(2) == '3')) {
+
+ vulnerableJVM = true;
+ }
+ } catch (SecurityException e) {
+ // don't know - so display warning
+ vulnerableJVM = true;
+ }
+ if (vulnerableJVM) {
+ log.warn(
+ "Current Security Manager restricts use of workarounds for reflection bugs "
+ + " in pre-1.4 JVMs.");
+ }
+ loggedAccessibleWarning = true;
+ }
+ log.debug("Cannot setAccessible on method. Therefore cannot use jvm access bug workaround.", se);
+ }
+ }
+
+ /**
+ * Returns the sum of the object transformation cost for each class in the source
+ * argument list.
+ * @param srcArgs The source arguments
+ * @param destArgs The destination arguments
+ * @return The total transformation cost
+ */
+ private static float getTotalTransformationCost(Class[] srcArgs, Class[] destArgs) {
+
+ float totalCost = 0.0f;
+ for (int i = 0; i < srcArgs.length; i++) {
+ Class srcClass, destClass;
+ srcClass = srcArgs[i];
+ destClass = destArgs[i];
+ totalCost += getObjectTransformationCost(srcClass, destClass);
+ }
+
+ return totalCost;
+ }
+
+ /**
+ * Gets the number of steps required needed to turn the source class into the
+ * destination class. This represents the number of steps in the object hierarchy
+ * graph.
+ * @param srcClass The source class
+ * @param destClass The destination class
+ * @return The cost of transforming an object
+ */
+ private static float getObjectTransformationCost(Class srcClass, Class destClass) {
+ float cost = 0.0f;
+ while (destClass != null && !destClass.equals(srcClass)) {
+ if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
+ // slight penalty for interface match.
+ // we still want an exact match to override an interface match, but
+ // an interface match should override anything where we have to get a
+ // superclass.
+ cost += 0.25f;
+ break;
+ }
+ cost++;
+ destClass = destClass.getSuperclass();
+ }
+
+ /*
+ * If the destination class is null, we've travelled all the way up to
+ * an Object match. We'll penalize this by adding 1.5 to the cost.
+ */
+ if (destClass == null) {
+ cost += 1.5f;
+ }
+
+ return cost;
+ }
+
+
+ /**
+ * <p>Determine whether a type can be used as a parameter in a method invocation.
+ * This method handles primitive conversions correctly.</p>
+ *
+ * <p>In order words, it will match a <code>Boolean</code> to a <code>boolean</code>,
+ * a <code>Long</code> to a <code>long</code>,
+ * a <code>Float</code> to a <code>float</code>,
+ * a <code>Integer</code> to a <code>int</code>,
+ * and a <code>Double</code> to a <code>double</code>.
+ * Now logic widening matches are allowed.
+ * For example, a <code>Long</code> will not match a <code>int</code>.
+ *
+ * @param parameterType the type of parameter accepted by the method
+ * @param parameterization the type of parameter being tested
+ *
+ * @return true if the assignement is compatible.
+ */
+ public static final boolean isAssignmentCompatible(Class parameterType, Class parameterization) {
+ // try plain assignment
+ if (parameterType.isAssignableFrom(parameterization)) {
+ return true;
+ }
+
+ if (parameterType.isPrimitive()) {
+ // this method does *not* do widening - you must specify exactly
+ // is this the right behaviour?
+ Class parameterWrapperClazz = getPrimitiveWrapper(parameterType);
+ if (parameterWrapperClazz != null) {
+ return parameterWrapperClazz.equals(parameterization);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the wrapper object class for the given primitive type class.
+ * For example, passing <code>boolean.class</code> returns <code>Boolean.class</code>
+ * @param primitiveType the primitive type class for which a match is to be found
+ * @return the wrapper type associated with the given primitive
+ * or null if no match is found
+ */
+ public static Class getPrimitiveWrapper(Class primitiveType) {
+ // does anyone know a better strategy than comparing names?
+ if (boolean.class.equals(primitiveType)) {
+ return Boolean.class;
+ } else if (float.class.equals(primitiveType)) {
+ return Float.class;
+ } else if (long.class.equals(primitiveType)) {
+ return Long.class;
+ } else if (int.class.equals(primitiveType)) {
+ return Integer.class;
+ } else if (short.class.equals(primitiveType)) {
+ return Short.class;
+ } else if (byte.class.equals(primitiveType)) {
+ return Byte.class;
+ } else if (double.class.equals(primitiveType)) {
+ return Double.class;
+ } else if (char.class.equals(primitiveType)) {
+ return Character.class;
+ } else {
+
+ return null;
+ }
+ }
+
+ /**
+ * Gets the class for the primitive type corresponding to the primitive wrapper class given.
+ * For example, an instance of <code>Boolean.class</code> returns a <code>boolean.class</code>.
+ * @param wrapperType the
+ * @return the primitive type class corresponding to the given wrapper class,
+ * null if no match is found
+ */
+ public static Class getPrimitiveType(Class wrapperType) {
+ // does anyone know a better strategy than comparing names?
+ if (Boolean.class.equals(wrapperType)) {
+ return boolean.class;
+ } else if (Float.class.equals(wrapperType)) {
+ return float.class;
+ } else if (Long.class.equals(wrapperType)) {
+ return long.class;
+ } else if (Integer.class.equals(wrapperType)) {
+ return int.class;
+ } else if (Short.class.equals(wrapperType)) {
+ return short.class;
+ } else if (Byte.class.equals(wrapperType)) {
+ return byte.class;
+ } else if (Double.class.equals(wrapperType)) {
+ return double.class;
+ } else if (Character.class.equals(wrapperType)) {
+ return char.class;
+ } else {
+ Log log = LogFactory.getLog(MethodUtils.class);
+ if (log.isDebugEnabled()) {
+ log.debug("Not a known primitive wrapper class: " + wrapperType);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Find a non primitive representation for given primitive class.
+ *
+ * @param clazz the class to find a representation for, not null
+ * @return the original class if it not a primitive. Otherwise the wrapper class. Not null
+ */
+ public static Class toNonPrimitiveClass(Class clazz) {
+ if (clazz.isPrimitive()) {
+ Class primitiveClazz = MethodUtils.getPrimitiveWrapper(clazz);
+ // the above method returns
+ if (primitiveClazz != null) {
+ return primitiveClazz;
+ } else {
+ return clazz;
+ }
+ } else {
+ return clazz;
+ }
+ }
+
+
+ /**
+ * Represents the key to looking up a Method by reflection.
+ */
+ private static class MethodDescriptor {
+ private Class cls;
+ private String methodName;
+ private Class[] paramTypes;
+ private boolean exact;
+ private int hashCode;
+
+ /**
+ * The sole constructor.
+ *
+ * @param cls the class to reflect, must not be null
+ * @param methodName the method name to obtain
+ * @param paramTypes the array of classes representing the paramater types
+ * @param exact whether the match has to be exact.
+ */
+ public MethodDescriptor(Class cls, String methodName, Class[] paramTypes, boolean exact) {
+ if (cls == null) {
+ throw new IllegalArgumentException("Class cannot be null");
+ }
+ if (methodName == null) {
+ throw new IllegalArgumentException("Method Name cannot be null");
+ }
+ if (paramTypes == null) {
+ paramTypes = EMPTY_CLASS_PARAMETERS;
+ }
+
+ this.cls = cls;
+ this.methodName = methodName;
+ this.paramTypes = paramTypes;
+ this.exact= exact;
+
+ this.hashCode = methodName.length();
+ }
+ /**
+ * Checks for equality.
+ * @param obj object to be tested for equality
+ * @return true, if the object describes the same Method.
+ */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MethodDescriptor)) {
+ return false;
+ }
+ MethodDescriptor md = (MethodDescriptor)obj;
+
+ return (
+ exact == md.exact &&
+ methodName.equals(md.methodName) &&
+ cls.equals(md.cls) &&
+ java.util.Arrays.equals(paramTypes, md.paramTypes)
+ );
+ }
+ /**
+ * Returns the string length of method name. I.e. if the
+ * hashcodes are different, the objects are different. If the
+ * hashcodes are the same, need to use the equals method to
+ * determine equality.
+ * @return the string length of method name.
+ */
+ public int hashCode() {
+ return hashCode;
+ }
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/util/XmlSort.java b/src/ooxml/java/org/apache/poi/util/XmlSort.java new file mode 100644 index 0000000000..4e1ffa54f0 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/util/XmlSort.java @@ -0,0 +1,221 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Comparator;
+
+import javax.xml.namespace.QName;
+
+import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlObject;
+
+/**
+ */
+public final class XmlSort
+{
+ /**
+ * Receives an XML element instance and sorts the children of this
+ * element in lexicographical (by default) order.
+ *
+ * @param args An array in which the first item is a
+ * path to the XML instance file and the second item (optional) is
+ * an XPath inside the document identifying the element to be sorted
+ */
+ public static void main(String[] args)
+ {
+ if (args.length < 1 || args.length > 2)
+ {
+ System.out.println(" java XmlSort <XML_File> [<XPath>]");
+ return;
+ }
+ File f = new File(args[0]);
+ try
+ {
+ XmlObject docInstance = XmlObject.Factory.parse(f);
+ XmlObject element = null;
+ if (args.length > 1)
+ {
+ String xpath = args[1];
+ XmlObject[] result = docInstance.selectPath(xpath);
+ if (result.length == 0)
+ {
+ System.out.println("ERROR: XPath \"" + xpath + "\" did not return any results");
+ }
+ else if (result.length > 1)
+ {
+ System.out.println("ERROR: XPath \"" + xpath + "\" returned more than one " +
+ "node (" + result.length + ")");
+ }
+ else
+ element = result[0];
+ }
+ else
+ {
+ // Navigate to the root element
+ XmlCursor c = docInstance.newCursor();
+ c.toFirstChild();
+ element = c.getObject();
+ c.dispose();
+ }
+ if (element != null)
+ sort(element, new QNameComparator(QNameComparator.ASCENDING));
+ System.out.println(docInstance.xmlText());
+ }
+ catch (IOException ioe)
+ {
+ System.out.println("ERROR: Could not open file: \"" + args[0] + "\": " +
+ ioe.getMessage());
+ }
+ catch (XmlException xe)
+ {
+ System.out.println("ERROR: Could not parse file: \"" + args[0] + "\": " +
+ xe.getMessage());
+ }
+ }
+
+ /**
+ * Sorts the children of <code>element</code> according to the order indicated by the
+ * comparator.
+ * @param element the element whose content is to be sorted. Only element children are sorted,
+ * attributes are not touched. When elements are reordered, all the text, comments and PIs
+ * follow the element that they come immediately after.
+ * @param comp a comparator that is to be used when comparing the <code>QName</code>s of two
+ * elements. See {@link org.apache.xmlbeans.samples.cursor.XmlSort.QNameComparator} for a simple
+ * implementation that compares two elements based on the value of their QName, but more
+ * complicated implementations are possible, for instance, ones that compare two elements based
+ * on the value of a specifc attribute etc.
+ * @throws IllegalArgumentException if the input <code>XmlObject</code> does not represent
+ * an element
+ */
+ public static void sort(XmlObject element, Comparator<XmlCursor> comp)
+ {
+ XmlCursor headCursor = element.newCursor();
+ if (!headCursor.isStart())
+ throw new IllegalStateException("The element parameter must point to a STARTDOC");
+ // We use insertion sort to minimize the number of swaps, because each swap means
+ // moving a part of the document
+ /* headCursor points to the beginning of the list of the already sorted items and
+ listCursor points to the beginning of the list of unsorted items
+ At the beginning, headCursor points to the first element and listCursor points to the
+ second element. The algorithm ends when listCursor cannot be moved to the "next"
+ element in the unsorted list, i.e. the unsorted list becomes empty */
+ boolean moved = headCursor.toFirstChild();
+ if (!moved)
+ {
+ // Cursor was not moved, which means that the given element has no children and
+ // therefore there is nothing to sort
+ return;
+ }
+ XmlCursor listCursor = headCursor.newCursor();
+ boolean moreElements = listCursor.toNextSibling();
+ while (moreElements)
+ {
+ moved = false;
+ // While we can move the head of the unsorted list, it means that there are still
+ // items (elements) that need to be sorted
+ while (headCursor.comparePosition(listCursor) < 0)
+ {
+ if (comp.compare(headCursor, listCursor) > 0)
+ {
+ // We have found the position in the sorted list, insert the element and the
+ // text following the element in the current position
+ /*
+ * Uncomment this code to cause the text before the element to move along
+ * with the element, rather than the text after the element. Notice that this
+ * is more difficult to do, because the cursor's "type" refers to the position
+ * to the right of the cursor, so to get the type of the token to the left, the
+ * cursor needs to be first moved to the left (previous token)
+ *
+ headCursor.toPrevToken();
+ while (headCursor.isComment() || headCursor.isProcinst() || headCursor.isText())
+ headCursor.toPrevToken();
+ headCursor.toNextToken();
+ listCursor.toPrevToken();
+ while (listCursor.isComment() || listCursor.isProcinst() || listCursor.isText())
+ listCursor.toPrevToken();
+ listCursor.toNextToken();
+ while (!listCursor.isStart())
+ listCursor.moveXml(headCursor);
+ listCursor.moveXml(headCursor);
+ */
+ // Move the element
+ listCursor.moveXml(headCursor);
+ // Move the text following the element
+ while (!listCursor.isStart() && !listCursor.isEnd())
+ listCursor.moveXml(headCursor);
+ moreElements = listCursor.isStart();
+ moved = true;
+ break;
+ }
+ headCursor.toNextSibling();
+ }
+ if (!moved)
+ {
+ // Because during the move of a fragment of XML, the listCursor is also moved, in
+ // case we didn't need to move XML (the new element to be inserted happened to
+ // be the last one in order), we need to move this cursor
+ moreElements = listCursor.toNextSibling();
+ }
+ // Reposition the head of the sorted list
+ headCursor.toParent();
+ headCursor.toFirstChild();
+ }
+ }
+
+ /**
+ * Implements a <code>java.util.Comparator</code> for comparing <code>QName</code>values.
+ * The namespace URIs are compared first and if they are equal, the local parts are compared.
+ * <p/>
+ * The constructor accepts an argument indicating whether the comparison order is the same as
+ * the lexicographic order of the strings or the reverse.
+ */
+ public static final class QNameComparator implements Comparator
+ {
+ public static final int ASCENDING = 1;
+ public static final int DESCENDING = 2;
+
+ private int order;
+
+ public QNameComparator(int order)
+ {
+ this.order = order;
+ if (order != ASCENDING && order != DESCENDING)
+ throw new IllegalArgumentException("Please specify one of ASCENDING or DESCENDING "+
+ "comparison orders");
+ }
+
+ public int compare(Object o, Object o1)
+ {
+ XmlCursor cursor1 = (XmlCursor) o;
+ XmlCursor cursor2 = (XmlCursor) o1;
+ QName qname1 = cursor1.getName();
+ QName qname2 = cursor2.getName();
+ int qnameComparisonRes = qname1.getNamespaceURI().compareTo(qname2.getNamespaceURI());
+ if (qnameComparisonRes == 0)
+ return order == ASCENDING ?
+ qname1.getLocalPart().compareTo(qname2.getLocalPart()) :
+ -qname1.getLocalPart().compareTo(qname2.getLocalPart());
+ else
+ return order == ASCENDING ? qnameComparisonRes : -qnameComparisonRes;
+ }
+ }
+}
+
\ No newline at end of file diff --git a/src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd b/src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd new file mode 100644 index 0000000000..f7019f13f5 --- /dev/null +++ b/src/ooxml/resources/org/apache/poi/poifs/crypt/signatureInfo.xsd @@ -0,0 +1,103 @@ +<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ====================================================================
+-->
+<xsd:schema targetNamespace="http://schemas.microsoft.com/office/2006/digsig" elementFormDefault="qualified" xmlns="http://schemas.microsoft.com/office/2006/digsig" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:simpleType name="ST_PositiveInteger">
+ <xsd:restriction base="xsd:int">
+ <xsd:minExclusive value="0"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_SignatureComments">
+ <xsd:restriction base="xsd:string">
+ <xsd:maxLength value="255"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_SignatureProviderUrl">
+ <xsd:restriction base="xsd:string">
+ <xsd:maxLength value="2083"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_SignatureText">
+ <xsd:restriction base="xsd:string">
+ <xsd:maxLength value="100"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_SignatureType">
+ <xsd:restriction base="xsd:int">
+ <xsd:enumeration value="1"/>
+ <xsd:enumeration value="2"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_Version">
+ <xsd:restriction base="xsd:string">
+ <xsd:maxLength value="64"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:simpleType name="ST_UniqueIdentifierWithBraces">
+ <xsd:restriction base="xsd:string">
+ <xsd:pattern value="\{[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}\}|"/>
+ </xsd:restriction>
+ </xsd:simpleType>
+ <xsd:group name="EG_RequiredChildren">
+ <xsd:sequence>
+ <xsd:element name="SetupID" type="ST_UniqueIdentifierWithBraces"/>
+ <xsd:element name="SignatureText" type="ST_SignatureText"/>
+ <xsd:element name="SignatureImage" type="xsd:base64Binary"/>
+ <xsd:element name="SignatureComments" type="ST_SignatureComments"/>
+ <xsd:element name="WindowsVersion" type="ST_Version"/>
+ <xsd:element name="OfficeVersion" type="ST_Version"/>
+ <xsd:element name="ApplicationVersion" type="ST_Version"/>
+ <xsd:element name="Monitors" type="ST_PositiveInteger"/>
+ <xsd:element name="HorizontalResolution" type="ST_PositiveInteger"/>
+ <xsd:element name="VerticalResolution" type="ST_PositiveInteger"/>
+ <xsd:element name="ColorDepth" type="ST_PositiveInteger"/>
+ <xsd:element name="SignatureProviderId" type="ST_UniqueIdentifierWithBraces"/>
+ <xsd:element name="SignatureProviderUrl" type="ST_SignatureProviderUrl"/>
+ <xsd:element name="SignatureProviderDetails" type="xsd:int"/>
+ <xsd:element name="SignatureType" type="ST_SignatureType"/>
+ </xsd:sequence>
+ </xsd:group>
+ <xsd:group name="EG_OptionalChildren">
+ <xsd:sequence>
+ <xsd:element name="DelegateSuggestedSigner" type="xsd:string"/>
+ <xsd:element name="DelegateSuggestedSigner2" type="xsd:string"/>
+ <xsd:element name="DelegateSuggestedSignerEmail" type="xsd:string"/>
+ <xsd:element name="ManifestHashAlgorithm" type="xsd:anyURI" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:group>
+ <xsd:group name="EG_OptionalChildrenV2">
+ <xsd:sequence>
+ <xsd:element name="Address1" type="xsd:string"/>
+ <xsd:element name="Address2" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:group>
+ <xsd:complexType name="CT_SignatureInfoV1">
+ <xsd:sequence>
+ <xsd:group ref="EG_RequiredChildren"/>
+ <xsd:group ref="EG_OptionalChildren" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="CT_SignatureInfoV2">
+ <xsd:sequence>
+ <xsd:group ref="EG_OptionalChildrenV2" minOccurs="0"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="SignatureInfoV1" type="CT_SignatureInfoV1"/>
+ <xsd:element name="SignatureInfoV2" type="CT_SignatureInfoV2"/>
+</xsd:schema>
\ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java new file mode 100644 index 0000000000..afd70f08e5 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/PkiTestUtils.java @@ -0,0 +1,317 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.poifs.crypt;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringWriter;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.SecureRandom;
+import java.security.cert.CRLException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.Date;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Result;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.bouncycastle.asn1.DERIA5String;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AuthorityInformationAccess;
+import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier;
+import org.bouncycastle.asn1.x509.BasicConstraints;
+import org.bouncycastle.asn1.x509.CRLNumber;
+import org.bouncycastle.asn1.x509.CRLReason;
+import org.bouncycastle.asn1.x509.DistributionPoint;
+import org.bouncycastle.asn1.x509.DistributionPointName;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.Extensions;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
+import org.bouncycastle.cert.X509CRLHolder;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509ExtensionUtils;
+import org.bouncycastle.cert.X509v2CRLBuilder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CRLConverter;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.ocsp.BasicOCSPResp;
+import org.bouncycastle.cert.ocsp.BasicOCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.CertificateID;
+import org.bouncycastle.cert.ocsp.CertificateStatus;
+import org.bouncycastle.cert.ocsp.OCSPReq;
+import org.bouncycastle.cert.ocsp.OCSPReqBuilder;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+import org.bouncycastle.cert.ocsp.OCSPRespBuilder;
+import org.bouncycastle.cert.ocsp.Req;
+import org.bouncycastle.cert.ocsp.RevokedStatus;
+import org.bouncycastle.crypto.params.RSAKeyParameters;
+import org.bouncycastle.crypto.util.SubjectPublicKeyInfoFactory;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class PkiTestUtils {
+
+ private PkiTestUtils() {
+ super();
+ }
+
+ static KeyPair generateKeyPair() throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
+ SecureRandom random = new SecureRandom();
+ keyPairGenerator.initialize(new RSAKeyGenParameterSpec(1024,
+ RSAKeyGenParameterSpec.F4), random);
+ KeyPair keyPair = keyPairGenerator.generateKeyPair();
+ return keyPair;
+ }
+
+ static X509Certificate generateCertificate(PublicKey subjectPublicKey,
+ String subjectDn, Date notBefore, Date notAfter,
+ X509Certificate issuerCertificate, PrivateKey issuerPrivateKey,
+ boolean caFlag, int pathLength, String crlUri, String ocspUri,
+ KeyUsage keyUsage)
+ throws IOException, OperatorCreationException, CertificateException
+ {
+ String signatureAlgorithm = "SHA1withRSA";
+ X500Name issuerName;
+ if (issuerCertificate != null) {
+ issuerName = new X509CertificateHolder(issuerCertificate.getEncoded()).getIssuer();
+ } else {
+ issuerName = new X500Name(subjectDn);
+ }
+
+ RSAPublicKey rsaPubKey = (RSAPublicKey)subjectPublicKey;
+ RSAKeyParameters rsaSpec = new RSAKeyParameters(false, rsaPubKey.getModulus(), rsaPubKey.getPublicExponent());
+
+ SubjectPublicKeyInfo subjectPublicKeyInfo =
+ SubjectPublicKeyInfoFactory.createSubjectPublicKeyInfo(rsaSpec);
+
+ DigestCalculator digestCalc = new JcaDigestCalculatorProviderBuilder()
+ .setProvider("BC").build().get(CertificateID.HASH_SHA1);
+
+ X509v3CertificateBuilder certificateGenerator = new X509v3CertificateBuilder(
+ issuerName
+ , new BigInteger(128, new SecureRandom())
+ , notBefore
+ , notAfter
+ , new X500Name(subjectDn)
+ , subjectPublicKeyInfo
+ );
+
+ X509ExtensionUtils exUtils = new X509ExtensionUtils(digestCalc);
+ SubjectKeyIdentifier subKeyId = exUtils.createSubjectKeyIdentifier(subjectPublicKeyInfo);
+ AuthorityKeyIdentifier autKeyId = (issuerCertificate != null)
+ ? exUtils.createAuthorityKeyIdentifier(new X509CertificateHolder(issuerCertificate.getEncoded()))
+ : exUtils.createAuthorityKeyIdentifier(subjectPublicKeyInfo);
+
+ certificateGenerator.addExtension(Extension.subjectKeyIdentifier, false, subKeyId);
+ certificateGenerator.addExtension(Extension.authorityKeyIdentifier, false, autKeyId);
+
+ if (caFlag) {
+ BasicConstraints bc;
+
+ if (-1 == pathLength) {
+ bc = new BasicConstraints(true);
+ } else {
+ bc = new BasicConstraints(pathLength);
+ }
+ certificateGenerator.addExtension(Extension.basicConstraints, false, bc);
+ }
+
+ if (null != crlUri) {
+ int uri = GeneralName.uniformResourceIdentifier;
+ DERIA5String crlUriDer = new DERIA5String(crlUri);
+ GeneralName gn = new GeneralName(uri, crlUriDer);
+
+ DERSequence gnDer = new DERSequence(gn);
+ GeneralNames gns = GeneralNames.getInstance(gnDer);
+
+ DistributionPointName dpn = new DistributionPointName(0, gns);
+ DistributionPoint distp = new DistributionPoint(dpn, null, null);
+ DERSequence distpDer = new DERSequence(distp);
+ certificateGenerator.addExtension(Extension.cRLDistributionPoints, false, distpDer);
+ }
+
+ if (null != ocspUri) {
+ int uri = GeneralName.uniformResourceIdentifier;
+ GeneralName ocspName = new GeneralName(uri, ocspUri);
+
+ AuthorityInformationAccess authorityInformationAccess =
+ new AuthorityInformationAccess(X509ObjectIdentifiers.ocspAccessMethod, ocspName);
+
+ certificateGenerator.addExtension(Extension.authorityInfoAccess, false, authorityInformationAccess);
+ }
+
+ if (null != keyUsage) {
+ certificateGenerator.addExtension(Extension.keyUsage, true, keyUsage);
+ }
+
+ JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlgorithm);
+ signerBuilder.setProvider("BC");
+
+ X509CertificateHolder certHolder =
+ certificateGenerator.build(signerBuilder.build(issuerPrivateKey));
+
+ /*
+ * Next certificate factory trick is needed to make sure that the
+ * certificate delivered to the caller is provided by the default
+ * security provider instead of BouncyCastle. If we don't do this trick
+ * we might run into trouble when trying to use the CertPath validator.
+ */
+// CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
+// certificate = (X509Certificate) certificateFactory
+// .generateCertificate(new ByteArrayInputStream(certificate
+// .getEncoded()));
+ return new JcaX509CertificateConverter().getCertificate(certHolder);
+ }
+
+ static Document loadDocument(InputStream documentInputStream)
+ throws ParserConfigurationException, SAXException, IOException {
+ InputSource inputSource = new InputSource(documentInputStream);
+ DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory
+ .newInstance();
+ documentBuilderFactory.setNamespaceAware(true);
+ DocumentBuilder documentBuilder = documentBuilderFactory
+ .newDocumentBuilder();
+ Document document = documentBuilder.parse(inputSource);
+ return document;
+ }
+
+ static String toString(Node dom) throws TransformerException {
+ Source source = new DOMSource(dom);
+ StringWriter stringWriter = new StringWriter();
+ Result result = new StreamResult(stringWriter);
+ TransformerFactory transformerFactory = TransformerFactory
+ .newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ /*
+ * We have to omit the ?xml declaration if we want to embed the
+ * document.
+ */
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.transform(source, result);
+ return stringWriter.getBuffer().toString();
+ }
+
+ public static X509CRL generateCrl(X509Certificate issuer, PrivateKey issuerPrivateKey)
+ throws CertificateEncodingException, IOException, CRLException, OperatorCreationException {
+
+ X509CertificateHolder holder = new X509CertificateHolder(issuer.getEncoded());
+ X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(holder.getIssuer(), new Date());
+ crlBuilder.setNextUpdate(new Date(new Date().getTime() + 100000));
+ JcaContentSignerBuilder contentBuilder = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC");
+
+ CRLNumber crlNumber = new CRLNumber(new BigInteger("1234"));
+
+ crlBuilder.addExtension(Extension.cRLNumber, false, crlNumber);
+ X509CRLHolder x509Crl = crlBuilder.build(contentBuilder.build(issuerPrivateKey));
+ return new JcaX509CRLConverter().setProvider("BC").getCRL(x509Crl);
+ }
+
+ public static OCSPResp createOcspResp(X509Certificate certificate,
+ boolean revoked, X509Certificate issuerCertificate,
+ X509Certificate ocspResponderCertificate,
+ PrivateKey ocspResponderPrivateKey, String signatureAlgorithm,
+ long nonceTimeinMillis)
+ throws Exception {
+ DigestCalculator digestCalc = new JcaDigestCalculatorProviderBuilder()
+ .setProvider("BC").build().get(CertificateID.HASH_SHA1);
+ X509CertificateHolder issuerHolder = new X509CertificateHolder(issuerCertificate.getEncoded());
+ CertificateID certId = new CertificateID(digestCalc, issuerHolder, certificate.getSerialNumber());
+
+ // request
+ //create a nonce to avoid replay attack
+ BigInteger nonce = BigInteger.valueOf(nonceTimeinMillis);
+ DEROctetString nonceDer = new DEROctetString(nonce.toByteArray());
+ Extension ext = new Extension(OCSPObjectIdentifiers.id_pkix_ocsp_nonce, true, nonceDer);
+ Extensions exts = new Extensions(ext);
+
+ OCSPReqBuilder ocspReqBuilder = new OCSPReqBuilder();
+ ocspReqBuilder.addRequest(certId);
+ ocspReqBuilder.setRequestExtensions(exts);
+ OCSPReq ocspReq = ocspReqBuilder.build();
+
+
+ SubjectPublicKeyInfo keyInfo = new SubjectPublicKeyInfo
+ (CertificateID.HASH_SHA1, ocspResponderCertificate.getPublicKey().getEncoded());
+
+ BasicOCSPRespBuilder basicOCSPRespBuilder = new BasicOCSPRespBuilder(keyInfo, digestCalc);
+ basicOCSPRespBuilder.setResponseExtensions(exts);
+
+ // request processing
+ Req[] requestList = ocspReq.getRequestList();
+ for (Req ocspRequest : requestList) {
+ CertificateID certificateID = ocspRequest.getCertID();
+ CertificateStatus certificateStatus = CertificateStatus.GOOD;
+ if (revoked) {
+ certificateStatus = new RevokedStatus(new Date(), CRLReason.privilegeWithdrawn);
+ }
+ basicOCSPRespBuilder.addResponse(certificateID, certificateStatus);
+ }
+
+ // basic response generation
+ X509CertificateHolder[] chain = null;
+ if (!ocspResponderCertificate.equals(issuerCertificate)) {
+ // TODO: HorribleProxy can't convert array input params yet
+ chain = new X509CertificateHolder[] {
+ new X509CertificateHolder(ocspResponderCertificate.getEncoded()),
+ issuerHolder
+ };
+ }
+
+ ContentSigner contentSigner = new JcaContentSignerBuilder("SHA1withRSA")
+ .setProvider("BC").build(ocspResponderPrivateKey);
+ BasicOCSPResp basicOCSPResp = basicOCSPRespBuilder.build(contentSigner, chain, new Date(nonceTimeinMillis));
+
+
+ OCSPRespBuilder ocspRespBuilder = new OCSPRespBuilder();
+ OCSPResp ocspResp = ocspRespBuilder.build(OCSPRespBuilder.SUCCESSFUL, basicOCSPResp);
+
+ return ocspResp;
+ }
+}
diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java new file mode 100644 index 0000000000..4444abe89d --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestSignatureInfo.java @@ -0,0 +1,544 @@ +/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+/* ====================================================================
+ This product contains an ASLv2 licensed version of the OOXML signer
+ package from the eID Applet project
+ http://code.google.com/p/eid-applet/source/browse/trunk/README.txt
+ Copyright (C) 2008-2014 FedICT.
+ ================================================================= */
+package org.apache.poi.poifs.crypt;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.security.Key;
+import java.security.KeyPair;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.Certificate;
+import java.security.cert.X509CRL;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.TimeZone;
+
+import org.apache.poi.POIDataSamples;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackageAccess;
+import org.apache.poi.poifs.crypt.dsig.SignatureConfig;
+import org.apache.poi.poifs.crypt.dsig.SignatureInfo;
+import org.apache.poi.poifs.crypt.dsig.SignatureInfo.SignaturePart;
+import org.apache.poi.poifs.crypt.dsig.facets.EnvelopedSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.KeyInfoSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.XAdESSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.facets.XAdESXLSignatureFacet;
+import org.apache.poi.poifs.crypt.dsig.services.RevocationData;
+import org.apache.poi.poifs.crypt.dsig.services.RevocationDataService;
+import org.apache.poi.poifs.crypt.dsig.services.TimeStampService;
+import org.apache.poi.poifs.crypt.dsig.services.TimeStampServiceValidator;
+import org.apache.poi.poifs.crypt.dsig.spi.DigestInfo;
+import org.apache.poi.util.DocumentHelper;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.xmlbeans.XmlObject;
+import org.bouncycastle.asn1.x509.KeyUsage;
+import org.bouncycastle.cert.ocsp.OCSPResp;
+import org.etsi.uri.x01903.v13.DigestAlgAndValueType;
+import org.etsi.uri.x01903.v13.QualifyingPropertiesType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.w3.x2000.x09.xmldsig.ReferenceType;
+import org.w3.x2000.x09.xmldsig.SignatureDocument;
+import org.w3c.dom.Document;
+
+public class TestSignatureInfo {
+ private static final POILogger LOG = POILogFactory.getLogger(TestSignatureInfo.class);
+ private static final POIDataSamples testdata = POIDataSamples.getXmlDSignInstance();
+
+ private static Calendar cal;
+ private KeyPair keyPair = null;
+ private X509Certificate x509 = null;
+
+
+
+ @BeforeClass
+ public static void initBouncy() throws MalformedURLException {
+ File bcProvJar = new File("lib/bcprov-ext-jdk15on-1.51.jar");
+ File bcPkixJar = new File("lib/bcpkix-jdk15on-151.jar");
+ ClassLoader cl = Thread.currentThread().getContextClassLoader();
+ URLClassLoader ucl = new URLClassLoader(new URL[]{bcProvJar.toURI().toURL(),bcPkixJar.toURI().toURL()}, cl);
+ Thread.currentThread().setContextClassLoader(ucl);
+ CryptoFunctions.registerBouncyCastle();
+
+ /*** TODO : set cal to now ... only set to fixed date for debugging ... */
+ cal = Calendar.getInstance();
+ cal.clear();
+ cal.setTimeZone(TimeZone.getTimeZone("UTC"));
+ cal.set(2014, 7, 6, 21, 42, 12);
+ }
+
+ @Test
+ public void getSignerUnsigned() throws Exception {
+ String testFiles[] = {
+ "hello-world-unsigned.docx",
+ "hello-world-unsigned.pptx",
+ "hello-world-unsigned.xlsx",
+ "hello-world-office-2010-technical-preview-unsigned.docx"
+ };
+
+ for (String testFile : testFiles) {
+ OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ);
+ SignatureConfig sic = new SignatureConfig();
+ sic.setOpcPackage(pkg);
+ SignatureInfo si = new SignatureInfo();
+ si.setSignatureConfig(sic);
+ List<X509Certificate> result = new ArrayList<X509Certificate>();
+ for (SignaturePart sp : si.getSignatureParts()) {
+ if (sp.validate()) {
+ result.add(sp.getSigner());
+ }
+ }
+ pkg.revert();
+ pkg.close();
+ assertNotNull(result);
+ assertTrue(result.isEmpty());
+ }
+ }
+
+ @Test
+ public void getSigner() throws Exception {
+ String testFiles[] = {
+ "hyperlink-example-signed.docx",
+ "hello-world-signed.docx",
+ "hello-world-signed.pptx",
+ "hello-world-signed.xlsx",
+ "hello-world-office-2010-technical-preview.docx",
+ "ms-office-2010-signed.docx",
+ "ms-office-2010-signed.pptx",
+ "ms-office-2010-signed.xlsx",
+ "Office2010-SP1-XAdES-X-L.docx",
+ "signed.docx",
+ };
+
+ for (String testFile : testFiles) {
+ OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ);
+ SignatureConfig sic = new SignatureConfig();
+ sic.setOpcPackage(pkg);
+ SignatureInfo si = new SignatureInfo();
+ si.setSignatureConfig(sic);
+ List<X509Certificate> result = new ArrayList<X509Certificate>();
+ for (SignaturePart sp : si.getSignatureParts()) {
+ if (sp.validate()) {
+ result.add(sp.getSigner());
+ }
+ }
+
+ assertNotNull(result);
+ assertEquals("test-file: "+testFile, 1, result.size());
+ X509Certificate signer = result.get(0);
+ LOG.log(POILogger.DEBUG, "signer: " + signer.getSubjectX500Principal());
+
+ boolean b = si.verifySignature();
+ assertTrue("test-file: "+testFile, b);
+ pkg.revert();
+ }
+ }
+
+ @Test
+ public void getMultiSigners() throws Exception {
+ String testFile = "hello-world-signed-twice.docx";
+ OPCPackage pkg = OPCPackage.open(testdata.getFile(testFile), PackageAccess.READ);
+ SignatureConfig sic = new SignatureConfig();
+ sic.setOpcPackage(pkg);
+ SignatureInfo si = new SignatureInfo();
+ si.setSignatureConfig(sic);
+ List<X509Certificate> result = new ArrayList<X509Certificate>();
+ for (SignaturePart sp : si.getSignatureParts()) {
+ if (sp.validate()) {
+ result.add(sp.getSigner());
+ }
+ }
+
+ assertNotNull(result);
+ assertEquals("test-file: "+testFile, 2, result.size());
+ X509Certificate signer1 = result.get(0);
+ X509Certificate signer2 = result.get(1);
+ LOG.log(POILogger.DEBUG, "signer 1: " + signer1.getSubjectX500Principal());
+ LOG.log(POILogger.DEBUG, "signer 2: " + signer2.getSubjectX500Principal());
+
+ boolean b = si.verifySignature();
+ assertTrue("test-file: "+testFile, b);
+ pkg.revert();
+ }
+
+ @Test
+ public void testSignSpreadsheet() throws Exception {
+ String testFile = "hello-world-unsigned.xlsx";
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+ sign(pkg, "Test", "CN=Test", 1);
+ pkg.close();
+ }
+
+ @Test
+ public void testManipulation() throws Exception {
+ // sign & validate
+ String testFile = "hello-world-unsigned.xlsx";
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+ sign(pkg, "Test", "CN=Test", 1);
+
+ // manipulate
+ XSSFWorkbook wb = new XSSFWorkbook(pkg);
+ wb.setSheetName(0, "manipulated");
+ // ... I don't know, why commit is protected ...
+ Method m = XSSFWorkbook.class.getDeclaredMethod("commit");
+ m.setAccessible(true);
+ m.invoke(wb);
+
+ // todo: test a manipulation on a package part, which is not signed
+ // ... maybe in combination with #56164
+
+ // validate
+ SignatureConfig sic = new SignatureConfig();
+ sic.setOpcPackage(pkg);
+ SignatureInfo si = new SignatureInfo();
+ si.setSignatureConfig(sic);
+ boolean b = si.verifySignature();
+ assertFalse("signature should be broken", b);
+
+ wb.close();
+ }
+
+ @Test
+ public void testSignSpreadsheetWithSignatureInfo() throws Exception {
+ initKeyPair("Test", "CN=Test");
+ String testFile = "hello-world-unsigned.xlsx";
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+ SignatureConfig sic = new SignatureConfig();
+ sic.setOpcPackage(pkg);
+ sic.setKey(keyPair.getPrivate());
+ sic.setSigningCertificateChain(Collections.singletonList(x509));
+ SignatureInfo si = new SignatureInfo();
+ si.setSignatureConfig(sic);
+ // hash > sha1 doesn't work in excel viewer ...
+ si.confirmSignature();
+ List<X509Certificate> result = new ArrayList<X509Certificate>();
+ for (SignaturePart sp : si.getSignatureParts()) {
+ if (sp.validate()) {
+ result.add(sp.getSigner());
+ }
+ }
+ assertEquals(1, result.size());
+ pkg.close();
+ }
+
+ @Test
+ public void testSignEnvelopingDocument() throws Exception {
+ String testFile = "hello-world-unsigned.xlsx";
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+
+ initKeyPair("Test", "CN=Test");
+ final X509CRL crl = PkiTestUtils.generateCrl(x509, keyPair.getPrivate());
+
+ // setup
+ SignatureConfig signatureConfig = new SignatureConfig();
+ signatureConfig.setOpcPackage(pkg);
+ signatureConfig.setKey(keyPair.getPrivate());
+
+ /*
+ * We need at least 2 certificates for the XAdES-C complete certificate
+ * refs construction.
+ */
+ List<X509Certificate> certificateChain = new ArrayList<X509Certificate>();
+ certificateChain.add(x509);
+ certificateChain.add(x509);
+ signatureConfig.setSigningCertificateChain(certificateChain);
+
+ signatureConfig.addSignatureFacet(new EnvelopedSignatureFacet());
+ signatureConfig.addSignatureFacet(new KeyInfoSignatureFacet());
+ signatureConfig.addSignatureFacet(new XAdESSignatureFacet());
+ signatureConfig.addSignatureFacet(new XAdESXLSignatureFacet());
+
+ boolean mockTsp = false;
+ // http://timestamping.edelweb.fr/service/tsp
+ // http://tsa.belgium.be/connect
+ signatureConfig.setTspUrl("http://timestamping.edelweb.fr/service/tsp");
+ signatureConfig.setTspOldProtocol(true);
+
+ if (mockTsp) {
+ TimeStampService tspService = new TimeStampService(){
+ public byte[] timeStamp(byte[] data, RevocationData revocationData) throws Exception {
+ revocationData.addCRL(crl);
+ return "time-stamp-token".getBytes();
+ }
+ public void setSignatureConfig(SignatureConfig config) {}
+ };
+ signatureConfig.setTspService(tspService);
+ } else {
+ TimeStampServiceValidator tspValidator = new TimeStampServiceValidator() {
+ @Override
+ public void validate(List<X509Certificate> certificateChain,
+ RevocationData revocationData) throws Exception {
+ for (X509Certificate certificate : certificateChain) {
+ LOG.log(POILogger.DEBUG, "certificate: " + certificate.getSubjectX500Principal());
+ LOG.log(POILogger.DEBUG, "validity: " + certificate.getNotBefore() + " - " + certificate.getNotAfter());
+ }
+ }
+ };
+ signatureConfig.setTspValidator(tspValidator);
+ signatureConfig.setTspOldProtocol(signatureConfig.getTspUrl().contains("edelweb"));
+ }
+
+ final RevocationData revocationData = new RevocationData();
+ revocationData.addCRL(crl);
+ OCSPResp ocspResp = PkiTestUtils.createOcspResp(x509, false,
+ x509, x509, keyPair.getPrivate(), "SHA1withRSA", cal.getTimeInMillis());
+ revocationData.addOCSP(ocspResp.getEncoded());
+
+ RevocationDataService revocationDataService = new RevocationDataService(){
+ public RevocationData getRevocationData(List<X509Certificate> certificateChain) {
+ return revocationData;
+ }
+ };
+ signatureConfig.setRevocationDataService(revocationDataService);
+
+ // operate
+ SignatureInfo si = new SignatureInfo();
+ si.setSignatureConfig(signatureConfig);
+ si.confirmSignature();
+
+ // verify
+ Iterator<SignaturePart> spIter = si.getSignatureParts().iterator();
+ assertTrue(spIter.hasNext());
+ SignaturePart sp = spIter.next();
+ boolean valid = sp.validate();
+ assertTrue(valid);
+
+ SignatureDocument sigDoc = sp.getSignatureDocument();
+ String declareNS =
+ "declare namespace xades='http://uri.etsi.org/01903/v1.3.2#'; "
+ + "declare namespace ds='http://www.w3.org/2000/09/xmldsig#'; ";
+
+ String digestValXQuery = declareNS +
+ "$this/ds:Signature/ds:SignedInfo/ds:Reference";
+ for (ReferenceType rt : (ReferenceType[])sigDoc.selectPath(digestValXQuery)) {
+ assertNotNull(rt.getDigestValue());
+ assertEquals(signatureConfig.getDigestMethodUri(), rt.getDigestMethod().getAlgorithm());
+ }
+
+ String certDigestXQuery = declareNS +
+ "$this//xades:SigningCertificate/xades:Cert/xades:CertDigest";
+ XmlObject xoList[] = sigDoc.selectPath(certDigestXQuery);
+ assertEquals(xoList.length, 1);
+ DigestAlgAndValueType certDigest = (DigestAlgAndValueType)xoList[0];
+ assertNotNull(certDigest.getDigestValue());
+
+ String qualPropXQuery = declareNS +
+ "$this/ds:Signature/ds:Object/xades:QualifyingProperties";
+ xoList = sigDoc.selectPath(qualPropXQuery);
+ assertEquals(xoList.length, 1);
+ QualifyingPropertiesType qualProp = (QualifyingPropertiesType)xoList[0];
+ boolean qualPropXsdOk = qualProp.validate();
+ assertTrue(qualPropXsdOk);
+
+ pkg.close();
+ }
+
+ @Test
+ public void testCertChain() throws Exception {
+ KeyStore keystore = KeyStore.getInstance("PKCS12");
+ String password = "test";
+ InputStream is = testdata.openResourceAsStream("chaintest.pfx");
+ keystore.load(is, password.toCharArray());
+ is.close();
+
+ Key key = keystore.getKey("poitest", password.toCharArray());
+ Certificate chainList[] = keystore.getCertificateChain("poitest");
+ List<X509Certificate> certChain = new ArrayList<X509Certificate>();
+ for (Certificate c : chainList) {
+ certChain.add((X509Certificate)c);
+ }
+ x509 = certChain.get(0);
+ keyPair = new KeyPair(x509.getPublicKey(), (PrivateKey)key);
+
+ String testFile = "hello-world-unsigned.xlsx";
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+
+ SignatureConfig signatureConfig = new SignatureConfig();
+ signatureConfig.setKey(keyPair.getPrivate());
+ signatureConfig.setSigningCertificateChain(certChain);
+ Calendar cal = Calendar.getInstance();
+ cal.set(2007, 7, 1);
+ signatureConfig.setExecutionTime(cal.getTime());
+ signatureConfig.setDigestAlgo(HashAlgorithm.sha1);
+ signatureConfig.setOpcPackage(pkg);
+
+ SignatureInfo si = new SignatureInfo();
+ si.setSignatureConfig(signatureConfig);
+
+ si.confirmSignature();
+
+ for (SignaturePart sp : si.getSignatureParts()){
+ boolean b = sp.validate();
+ assertTrue(b);
+ X509Certificate signer = sp.getSigner();
+ assertNotNull("signer undefined?!", signer);
+ List<X509Certificate> certChainRes = sp.getCertChain();
+ assertEquals(3, certChainRes.size());
+ }
+
+ pkg.close();
+ }
+
+ @Test
+ public void testNonSha1() throws Exception {
+ String testFile = "hello-world-unsigned.xlsx";
+ initKeyPair("Test", "CN=Test");
+
+ SignatureConfig signatureConfig = new SignatureConfig();
+ signatureConfig.setKey(keyPair.getPrivate());
+ signatureConfig.setSigningCertificateChain(Collections.singletonList(x509));
+
+ HashAlgorithm testAlgo[] = { HashAlgorithm.sha224, HashAlgorithm.sha256
+ , HashAlgorithm.sha384, HashAlgorithm.sha512, HashAlgorithm.ripemd160 };
+
+ for (HashAlgorithm ha : testAlgo) {
+ signatureConfig.setDigestAlgo(ha);
+ OPCPackage pkg = OPCPackage.open(copy(testdata.getFile(testFile)), PackageAccess.READ_WRITE);
+ signatureConfig.setOpcPackage(pkg);
+
+ SignatureInfo si = new SignatureInfo();
+ si.setSignatureConfig(signatureConfig);
+
+ si.confirmSignature();
+ boolean b = si.verifySignature();
+ pkg.close();
+
+ assertTrue(b);
+ }
+ }
+
+
+ private void sign(OPCPackage pkgCopy, String alias, String signerDn, int signerCount) throws Exception {
+ initKeyPair(alias, signerDn);
+
+ SignatureConfig signatureConfig = new SignatureConfig();
+ signatureConfig.setKey(keyPair.getPrivate());
+ signatureConfig.setSigningCertificateChain(Collections.singletonList(x509));
+ signatureConfig.setExecutionTime(cal.getTime());
+ signatureConfig.setDigestAlgo(HashAlgorithm.sha1);
+ signatureConfig.setOpcPackage(pkgCopy);
+
+ SignatureInfo si = new SignatureInfo();
+ si.setSignatureConfig(signatureConfig);
+
+ Document document = DocumentHelper.createDocument();
+
+ // operate
+ DigestInfo digestInfo = si.preSign(document, null);
+
+ // verify
+ assertNotNull(digestInfo);
+ LOG.log(POILogger.DEBUG, "digest algo: " + digestInfo.hashAlgo);
+ LOG.log(POILogger.DEBUG, "digest description: " + digestInfo.description);
+ assertEquals("Office OpenXML Document", digestInfo.description);
+ assertNotNull(digestInfo.hashAlgo);
+ assertNotNull(digestInfo.digestValue);
+
+ // setup: key material, signature value
+ byte[] signatureValue = si.signDigest(digestInfo.digestValue);
+
+ // operate: postSign
+ si.postSign(document, signatureValue);
+
+ // verify: signature
+ si.getSignatureConfig().setOpcPackage(pkgCopy);
+ List<X509Certificate> result = new ArrayList<X509Certificate>();
+ for (SignaturePart sp : si.getSignatureParts()) {
+ if (sp.validate()) {
+ result.add(sp.getSigner());
+ }
+ }
+ assertEquals(signerCount, result.size());
+ }
+
+ private void initKeyPair(String alias, String subjectDN) throws Exception {
+ final char password[] = "test".toCharArray();
+ File file = new File("build/test.pfx");
+
+ KeyStore keystore = KeyStore.getInstance("PKCS12");
+
+ if (file.exists()) {
+ FileInputStream fis = new FileInputStream(file);
+ keystore.load(fis, password);
+ fis.close();
+ } else {
+ keystore.load(null, password);
+ }
+
+ if (keystore.isKeyEntry(alias)) {
+ Key key = keystore.getKey(alias, password);
+ x509 = (X509Certificate)keystore.getCertificate(alias);
+ keyPair = new KeyPair(x509.getPublicKey(), (PrivateKey)key);
+ } else {
+ keyPair = PkiTestUtils.generateKeyPair();
+ Calendar cal = Calendar.getInstance();
+ Date notBefore = cal.getTime();
+ cal.add(Calendar.YEAR, 1);
+ Date notAfter = cal.getTime();
+ KeyUsage keyUsage = new KeyUsage(KeyUsage.digitalSignature);
+
+ x509 = PkiTestUtils.generateCertificate(keyPair.getPublic(), subjectDN
+ , notBefore, notAfter, null, keyPair.getPrivate(), true, 0, null, null, keyUsage);
+
+ keystore.setKeyEntry(alias, keyPair.getPrivate(), password, new Certificate[]{x509});
+ FileOutputStream fos = new FileOutputStream(file);
+ keystore.store(fos, password);
+ fos.close();
+ }
+ }
+
+ private static File copy(File input) throws IOException {
+ String extension = input.getName().replaceAll(".*?(\\.[^.]+)?$", "$1");
+ if (extension == null || "".equals(extension)) extension = ".zip";
+ File tmpFile = new File("build", "sigtest"+extension);
+ FileOutputStream fos = new FileOutputStream(tmpFile);
+ FileInputStream fis = new FileInputStream(input);
+ IOUtils.copy(fis, fos);
+ fis.close();
+ fos.close();
+ return tmpFile;
+ }
+
+}
diff --git a/src/testcases/org/apache/poi/POIDataSamples.java b/src/testcases/org/apache/poi/POIDataSamples.java index b335215390..a62e664e1e 100644 --- a/src/testcases/org/apache/poi/POIDataSamples.java +++ b/src/testcases/org/apache/poi/POIDataSamples.java @@ -44,6 +44,7 @@ public final class POIDataSamples { private static POIDataSamples _instHPSF;
private static POIDataSamples _instHPBF;
private static POIDataSamples _instHSMF;
+ private static POIDataSamples _instXmlDSign;
private File _resolvedDataDir;
/** <code>true</code> if standard system propery is not set,
@@ -114,6 +115,12 @@ public final class POIDataSamples { if(_instHSMF == null) _instHSMF = new POIDataSamples("hsmf");
return _instHSMF;
}
+
+ public static POIDataSamples getXmlDSignInstance(){
+ if(_instXmlDSign == null) _instXmlDSign = new POIDataSamples("xmldsign");
+ return _instXmlDSign;
+ }
+
/**
* Opens a sample file from the test data directory
*
diff --git a/test-data/xmldsign/Office2010-SP1-XAdES-X-L.docx b/test-data/xmldsign/Office2010-SP1-XAdES-X-L.docx Binary files differnew file mode 100644 index 0000000000..4aaa772a0d --- /dev/null +++ b/test-data/xmldsign/Office2010-SP1-XAdES-X-L.docx diff --git a/test-data/xmldsign/chaintest.pfx b/test-data/xmldsign/chaintest.pfx Binary files differnew file mode 100644 index 0000000000..e92106d2bb --- /dev/null +++ b/test-data/xmldsign/chaintest.pfx 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 Binary files differnew file mode 100644 index 0000000000..5162b67c60 --- /dev/null +++ b/test-data/xmldsign/hello-world-office-2010-technical-preview-unsigned.docx diff --git a/test-data/xmldsign/hello-world-office-2010-technical-preview.docx b/test-data/xmldsign/hello-world-office-2010-technical-preview.docx Binary files differnew file mode 100644 index 0000000000..cbd4277564 --- /dev/null +++ b/test-data/xmldsign/hello-world-office-2010-technical-preview.docx diff --git a/test-data/xmldsign/hello-world-signed-twice.docx b/test-data/xmldsign/hello-world-signed-twice.docx Binary files differnew file mode 100644 index 0000000000..96c91e957e --- /dev/null +++ b/test-data/xmldsign/hello-world-signed-twice.docx diff --git a/test-data/xmldsign/hello-world-signed.docx b/test-data/xmldsign/hello-world-signed.docx Binary files differnew file mode 100644 index 0000000000..79a7bbb81f --- /dev/null +++ b/test-data/xmldsign/hello-world-signed.docx diff --git a/test-data/xmldsign/hello-world-signed.pptx b/test-data/xmldsign/hello-world-signed.pptx Binary files differnew file mode 100644 index 0000000000..9b37033f54 --- /dev/null +++ b/test-data/xmldsign/hello-world-signed.pptx diff --git a/test-data/xmldsign/hello-world-signed.xlsx b/test-data/xmldsign/hello-world-signed.xlsx Binary files differnew file mode 100644 index 0000000000..0d45c53ede --- /dev/null +++ b/test-data/xmldsign/hello-world-signed.xlsx diff --git a/test-data/xmldsign/hello-world-unsigned.docx b/test-data/xmldsign/hello-world-unsigned.docx Binary files differnew file mode 100644 index 0000000000..1790c961ce --- /dev/null +++ b/test-data/xmldsign/hello-world-unsigned.docx diff --git a/test-data/xmldsign/hello-world-unsigned.pptx b/test-data/xmldsign/hello-world-unsigned.pptx Binary files differnew file mode 100644 index 0000000000..ca42529a9a --- /dev/null +++ b/test-data/xmldsign/hello-world-unsigned.pptx diff --git a/test-data/xmldsign/hello-world-unsigned.xlsx b/test-data/xmldsign/hello-world-unsigned.xlsx Binary files differnew file mode 100644 index 0000000000..b99143e92c --- /dev/null +++ b/test-data/xmldsign/hello-world-unsigned.xlsx diff --git a/test-data/xmldsign/hyperlink-example-signed.docx b/test-data/xmldsign/hyperlink-example-signed.docx Binary files differnew file mode 100644 index 0000000000..f8698fe4d7 --- /dev/null +++ b/test-data/xmldsign/hyperlink-example-signed.docx diff --git a/test-data/xmldsign/ms-office-2010-signed.docx b/test-data/xmldsign/ms-office-2010-signed.docx Binary files differnew file mode 100644 index 0000000000..61e4e2a1b8 --- /dev/null +++ b/test-data/xmldsign/ms-office-2010-signed.docx diff --git a/test-data/xmldsign/ms-office-2010-signed.pptx b/test-data/xmldsign/ms-office-2010-signed.pptx Binary files differnew file mode 100644 index 0000000000..c70f153248 --- /dev/null +++ b/test-data/xmldsign/ms-office-2010-signed.pptx diff --git a/test-data/xmldsign/ms-office-2010-signed.xlsx b/test-data/xmldsign/ms-office-2010-signed.xlsx Binary files differnew file mode 100644 index 0000000000..d8ba05b1da --- /dev/null +++ b/test-data/xmldsign/ms-office-2010-signed.xlsx diff --git a/test-data/xmldsign/signed.docx b/test-data/xmldsign/signed.docx Binary files differnew file mode 100644 index 0000000000..98f3b8d841 --- /dev/null +++ b/test-data/xmldsign/signed.docx |