git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@825637 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_6
<?xml version="1.0"?> | <?xml version="1.0"?> | ||||
<!-- | |||||
<!-- | |||||
Licensed to the Apache Software Foundation (ASF) under one | Licensed to the Apache Software Foundation (ASF) under one | ||||
or more contributor license agreements. See the NOTICE file | or more contributor license agreements. See the NOTICE file | ||||
distributed with this work for additional information | distributed with this work for additional information | ||||
Bruno Girin brunogirin@gmail.com | Bruno Girin brunogirin@gmail.com | ||||
This build was tested with ant 1.6.2 although it will probably work with | This build was tested with ant 1.6.2 although it will probably work with | ||||
other versions. The following jar files should be available on the | |||||
other versions. The following jar files should be available on the | |||||
classpath when running ant: | classpath when running ant: | ||||
LIBRARY LOCATION | LIBRARY LOCATION | ||||
To build the documentation you will need to install forrest and set | To build the documentation you will need to install forrest and set | ||||
the FORREST_HOME environment variable. Forrest 0.5.1 required. | the FORREST_HOME environment variable. Forrest 0.5.1 required. | ||||
Since POI 3.5 you will need JDK 1.5 or newer to build POI. | |||||
Since POI 3.5 you will need JDK 1.5 or newer to build POI. | |||||
Some people may find the tests hang when run through Ant. If this | Some people may find the tests hang when run through Ant. If this | ||||
happens to you, try giving Ant some more memory when you run it, eg: | happens to you, try giving Ant some more memory when you run it, eg: | ||||
<property name="ooxml.output.test.dir" location="build/ooxml-test-classes"/> | <property name="ooxml.output.test.dir" location="build/ooxml-test-classes"/> | ||||
<property name="ooxml.testokfile" location="build/ooxml-testokfile.txt"/> | <property name="ooxml.testokfile" location="build/ooxml-testokfile.txt"/> | ||||
<!-- The following jars are downloaded by the fetch-ooxml-jars task --> | |||||
<!-- The following jars are downloaded by the fetch-ooxml-jars task --> | |||||
<property name="ooxml.dom4j.jar" location="${ooxml.lib}/dom4j-1.6.1.jar"/> | <property name="ooxml.dom4j.jar" location="${ooxml.lib}/dom4j-1.6.1.jar"/> | ||||
<property name="ooxml.dom4j.url" value="${repository.m2}/maven2/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar"/> | <property name="ooxml.dom4j.url" value="${repository.m2}/maven2/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar"/> | ||||
<property name="ooxml.xmlbeans.jar" location="${ooxml.lib}/xmlbeans-2.3.0.jar"/> | <property name="ooxml.xmlbeans.jar" location="${ooxml.lib}/xmlbeans-2.3.0.jar"/> | ||||
<property name="ooxml.jsr173.url" value="${repository.m2}/maven2/org/apache/geronimo/specs/geronimo-stax-api_1.0_spec/1.0/geronimo-stax-api_1.0_spec-1.0.jar"/> | <property name="ooxml.jsr173.url" value="${repository.m2}/maven2/org/apache/geronimo/specs/geronimo-stax-api_1.0_spec/1.0/geronimo-stax-api_1.0_spec-1.0.jar"/> | ||||
<property name="ooxml.schemas.jar" location="${ooxml.lib}/ooxml-schemas-1.0.jar"/> | <property name="ooxml.schemas.jar" location="${ooxml.lib}/ooxml-schemas-1.0.jar"/> | ||||
<property name="ooxml.schemas.url" value="${repository.m2}/maven2/org/apache/poi/ooxml-schemas/1.0/ooxml-schemas-1.0.jar"/> | <property name="ooxml.schemas.url" value="${repository.m2}/maven2/org/apache/poi/ooxml-schemas/1.0/ooxml-schemas-1.0.jar"/> | ||||
<property name="ooxml.commons-lang.jar" location="${ooxml.lib}/commons-lang-2.4.jar"/> | |||||
<property name="ooxml.commons-lang.url" value="${repository.m2}/maven2/commons-lang/commons-lang/2.4/commons-lang-2.4.jar"/> | |||||
<property name="ooxml.commons-io.jar" location="${ooxml.lib}/commons-io-1.4.jar"/> | |||||
<property name="ooxml.commons-io.url" value="${repository.m2}/maven2/commons-io/commons-io/1.4/commons-io-1.4.jar"/> | |||||
<property name="ooxml.xmlsec.jar" location="${ooxml.lib}/xmlsec-1.4.3.jar"/> | |||||
<property name="ooxml.xmlsec.url" value="${repository.m2}/maven2/org/apache/santuario/xmlsec/1.4.3/xmlsec-1.4.3.jar"/> | |||||
<property name="ooxml.xalan.jar" location="${ooxml.lib}/xalan-2.7.1.jar"/> | |||||
<property name="ooxml.xalan.url" value="${repository.m2}/maven2/xalan/xalan/2.7.1/xalan-2.7.1.jar"/> | |||||
<property name="ooxml.xalan-serializer.jar" location="${ooxml.lib}/serializer-2.7.1.jar"/> | |||||
<property name="ooxml.xalan-serializer.url" value="${repository.m2}/maven2/xalan/serializer/2.7.1/serializer-2.7.1.jar"/> | |||||
<!-- BouncyCastle is used only for OOXML Digital Signature tests --> | |||||
<property name="ooxml.bcprov.jar" location="${ooxml.lib}/bcprov-jdk15-140.jar"/> | |||||
<property name="ooxml.bcprov.url" value="${repository.m2}/maven2/bouncycastle/bcprov-jdk15/140/bcprov-jdk15-140.jar"/> | |||||
<!-- See http://www.ecma-international.org/publications/standards/Ecma-376.htm --> | <!-- See http://www.ecma-international.org/publications/standards/Ecma-376.htm --> | ||||
<!-- "Copy these file(s), free of charge" --> | <!-- "Copy these file(s), free of charge" --> | ||||
<property name="maven.ooxml.xsds.version.id" value="1.0"/> | <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"/> | <property name="maven.ooxml.xsds.jar" value="ooxml-schemas-${maven.ooxml.xsds.version.id}.jar"/> | ||||
<property name="build.site" location="build/tmp/site/build/site"/> | <property name="build.site" location="build/tmp/site/build/site"/> | ||||
<property name="build.site.src" location="build/tmp/site"/> | <property name="build.site.src" location="build/tmp/site"/> | ||||
<property name="junit.report.dir" location="${build.site}/junit"/> | <property name="junit.report.dir" location="${build.site}/junit"/> | ||||
<property name="jdk.version.source" value="1.5" | <property name="jdk.version.source" value="1.5" | ||||
description="JDK version of source code"/> | description="JDK version of source code"/> | ||||
<property name="jdk.version.class" value="1.5" | <property name="jdk.version.class" value="1.5" | ||||
description="JDK version of generated class files"/> | |||||
description="JDK version of generated class files"/> | |||||
<path id="main.classpath"> | <path id="main.classpath"> | ||||
<fileset dir="${main.lib}"> | |||||
<include name="*.jar"/> | |||||
</fileset> | |||||
<fileset dir="${main.lib}"> | |||||
<include name="*.jar"/> | |||||
</fileset> | |||||
<pathelement location="${main.resource1.dir}"/> | <pathelement location="${main.resource1.dir}"/> | ||||
</path> | </path> | ||||
<pathelement location="${scratchpad.output.test.dir}"/> | <pathelement location="${scratchpad.output.test.dir}"/> | ||||
<pathelement location="${contrib.output.dir}"/> | <pathelement location="${contrib.output.dir}"/> | ||||
<pathelement location="${contrib.output.test.dir}"/> | <pathelement location="${contrib.output.test.dir}"/> | ||||
<fileset dir="${ooxml.lib}"> | |||||
<include name="*.jar" /> | |||||
</fileset> | |||||
</path> | </path> | ||||
<path id="ooxml.classpath"> | <path id="ooxml.classpath"> | ||||
<pathelement location="${ooxml.output.dir}"/> | <pathelement location="${ooxml.output.dir}"/> | ||||
<pathelement location="${ooxml.output.test.dir}"/> | <pathelement location="${ooxml.output.test.dir}"/> | ||||
<pathelement location="${main.output.test.dir}"/> <!-- ooxml tests use some utilities from main tests --> | <pathelement location="${main.output.test.dir}"/> <!-- ooxml tests use some utilities from main tests --> | ||||
<pathelement location="${scratchpad.output.test.dir}"/> | |||||
<pathelement location="${scratchpad.output.test.dir}"/> | |||||
<pathelement location="${junit.jar1.dir}"/> | <pathelement location="${junit.jar1.dir}"/> | ||||
<pathelement location="${ooxml.src.test}"/> | |||||
</path> | </path> | ||||
<available resource="clovertasks" property="clover.present"/> | <available resource="clovertasks" property="clover.present"/> | ||||
<antcall target="with.clover"/> | <antcall target="with.clover"/> | ||||
<mkdir dir="build"/> | <mkdir dir="build"/> | ||||
<mkdir dir="build/non-ant-classes"/> | <mkdir dir="build/non-ant-classes"/> | ||||
<mkdir dir="${main.output.dir}"/> | <mkdir dir="${main.output.dir}"/> | ||||
<available file="${ooxml.xmlbeans.jar}"/> | <available file="${ooxml.xmlbeans.jar}"/> | ||||
<available file="${ooxml.jsr173.jar}"/> | <available file="${ooxml.jsr173.jar}"/> | ||||
<available file="${ooxml.schemas.jar}"/> | <available file="${ooxml.schemas.jar}"/> | ||||
<available file="${ooxml.commons-lang.jar}"/> | |||||
<available file="${ooxml.commons-io.jar}"/> | |||||
<available file="${ooxml.xmlsec.jar}"/> | |||||
<available file="${ooxml.xalan.jar}"/> | |||||
<available file="${ooxml.xalan-serializer.jar}"/> | |||||
<available file="${ooxml.bcprov.jar}"/> | |||||
</and> | </and> | ||||
<isset property="disconnected"/> | <isset property="disconnected"/> | ||||
</or> | </or> | ||||
<param name="sourcefile" value="${ooxml.schemas.url}"/> | <param name="sourcefile" value="${ooxml.schemas.url}"/> | ||||
<param name="destfile" value="${ooxml.schemas.jar}"/> | <param name="destfile" value="${ooxml.schemas.jar}"/> | ||||
</antcall> | </antcall> | ||||
<antcall target="downloadfile"> | |||||
<param name="sourcefile" value="${ooxml.commons-lang.url}"/> | |||||
<param name="destfile" value="${ooxml.commons-lang.jar}"/> | |||||
</antcall> | |||||
<antcall target="downloadfile"> | |||||
<param name="sourcefile" value="${ooxml.commons-io.url}"/> | |||||
<param name="destfile" value="${ooxml.commons-io.jar}"/> | |||||
</antcall> | |||||
<antcall target="downloadfile"> | |||||
<param name="sourcefile" value="${ooxml.xmlsec.url}"/> | |||||
<param name="destfile" value="${ooxml.xmlsec.jar}"/> | |||||
</antcall> | |||||
<antcall target="downloadfile"> | |||||
<param name="sourcefile" value="${ooxml.xalan.url}"/> | |||||
<param name="destfile" value="${ooxml.xalan.jar}"/> | |||||
</antcall> | |||||
<antcall target="downloadfile"> | |||||
<param name="sourcefile" value="${ooxml.xalan-serializer.url}"/> | |||||
<param name="destfile" value="${ooxml.xalan-serializer.jar}"/> | |||||
</antcall> | |||||
<antcall target="downloadfile"> | |||||
<param name="sourcefile" value="${ooxml.bcprov.url}"/> | |||||
<param name="destfile" value="${ooxml.bcprov.jar}"/> | |||||
</antcall> | |||||
</target> | </target> | ||||
<target name="check-ooxml-xsds"> | <target name="check-ooxml-xsds"> | ||||
</xmlbean> | </xmlbean> | ||||
</target> | </target> | ||||
<target name="compile" depends="init, compile-main, | |||||
<target name="compile" depends="init, compile-main, | |||||
compile-scratchpad, compile-contrib, compile-examples, compile-scratchpad-examples" | compile-scratchpad, compile-contrib, compile-examples, compile-scratchpad-examples" | ||||
description="Compiles the POI main classes, scratchpad, contrib, examples, and scratchpad examples"/> | description="Compiles the POI main classes, scratchpad, contrib, examples, and scratchpad examples"/> | ||||
<!-- Generate the .java file --> | <!-- Generate the .java file --> | ||||
<property name="version.java" value="${main.output.dir}/org/apache/poi/Version.java" /> | <property name="version.java" value="${main.output.dir}/org/apache/poi/Version.java" /> | ||||
<delete file="${version.java}" /> | <delete file="${version.java}" /> | ||||
<copy | |||||
<copy | |||||
file="src/resources/version/Version.java.template" | file="src/resources/version/Version.java.template" | ||||
tofile="${version.java}"> | tofile="${version.java}"> | ||||
<filterset> | <filterset> | ||||
<junit fork="yes" forkmode="once" printsummary="yes" haltonfailure="${halt.on.test.failure}" | <junit fork="yes" forkmode="once" printsummary="yes" haltonfailure="${halt.on.test.failure}" | ||||
failureproperty="main.test.failed" showoutput="true"> | failureproperty="main.test.failed" showoutput="true"> | ||||
<classpath refid="test.classpath"/> | <classpath refid="test.classpath"/> | ||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="java.awt.headless" value="true"/> | <sysproperty key="java.awt.headless" value="true"/> | ||||
<formatter type="plain"/> | <formatter type="plain"/> | ||||
<formatter type="xml"/> | <formatter type="xml"/> | ||||
<pathelement location="${scratchpad.output.test.dir}"/> | <pathelement location="${scratchpad.output.test.dir}"/> | ||||
<pathelement location="${junit.jar1.dir}"/> | <pathelement location="${junit.jar1.dir}"/> | ||||
</classpath> | </classpath> | ||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="java.awt.headless" value="true"/> | <sysproperty key="java.awt.headless" value="true"/> | ||||
<formatter type="plain" usefile="no"/> | <formatter type="plain" usefile="no"/> | ||||
<batchtest todir="${main.reports.test}"> | <batchtest todir="${main.reports.test}"> | ||||
</fileset> | </fileset> | ||||
</batchtest> | </batchtest> | ||||
</junit> | </junit> | ||||
</target> | |||||
</target> | |||||
<pathelement location="${scratchpad.output.test.dir}"/> | <pathelement location="${scratchpad.output.test.dir}"/> | ||||
<pathelement location="${junit.jar1.dir}"/> | <pathelement location="${junit.jar1.dir}"/> | ||||
</classpath> | </classpath> | ||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="java.awt.headless" value="true"/> | <sysproperty key="java.awt.headless" value="true"/> | ||||
<formatter type="plain" usefile="no"/> | <formatter type="plain" usefile="no"/> | ||||
<formatter type="xml"/> | <formatter type="xml"/> | ||||
<pathelement location="${scratchpad.output.test.dir}"/> | <pathelement location="${scratchpad.output.test.dir}"/> | ||||
<pathelement location="${junit.jar1.dir}"/> | <pathelement location="${junit.jar1.dir}"/> | ||||
</classpath> | </classpath> | ||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="java.awt.headless" value="true"/> | <sysproperty key="java.awt.headless" value="true"/> | ||||
<formatter type="plain"/> | <formatter type="plain"/> | ||||
<formatter type="xml"/> | <formatter type="xml"/> | ||||
<pathelement location="${scratchpad.output.test.dir}"/> | <pathelement location="${scratchpad.output.test.dir}"/> | ||||
<pathelement location="${junit.jar1.dir}"/> | <pathelement location="${junit.jar1.dir}"/> | ||||
</classpath> | </classpath> | ||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="java.awt.headless" value="true"/> | <sysproperty key="java.awt.headless" value="true"/> | ||||
<sysproperty key="java.awt.headless" value="true"/> | <sysproperty key="java.awt.headless" value="true"/> | ||||
<formatter type="plain" usefile="no"/> | <formatter type="plain" usefile="no"/> | ||||
<test name="${testcase}"/> | <test name="${testcase}"/> | ||||
</junit> | </junit> | ||||
</target> | </target> | ||||
<target name="-test-contrib-check"> | <target name="-test-contrib-check"> | ||||
<uptodate property="contrib.test.notRequired" targetfile="${contrib.testokfile}"> | <uptodate property="contrib.test.notRequired" targetfile="${contrib.testokfile}"> | ||||
<srcfiles dir="${contrib.src}"/> | <srcfiles dir="${contrib.src}"/> | ||||
<pathelement location="${contrib.output.test.dir}"/> | <pathelement location="${contrib.output.test.dir}"/> | ||||
<pathelement location="${junit.jar1.dir}"/> | <pathelement location="${junit.jar1.dir}"/> | ||||
</classpath> | </classpath> | ||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="java.awt.headless" value="true"/> | <sysproperty key="java.awt.headless" value="true"/> | ||||
<formatter type="plain"/> | <formatter type="plain"/> | ||||
<formatter type="xml"/> | <formatter type="xml"/> | ||||
<target name="test-ooxml" depends="compile-main,compile-ooxml,-test-ooxml-check" unless="ooxml.test.notRequired"> | <target name="test-ooxml" depends="compile-main,compile-ooxml,-test-ooxml-check" unless="ooxml.test.notRequired"> | ||||
<junit printsummary="yes" fork="yes" forkmode="once" haltonfailure="${halt.on.test.failure}" failureproperty="ooxml.test.failed"> | <junit printsummary="yes" fork="yes" forkmode="once" haltonfailure="${halt.on.test.failure}" failureproperty="ooxml.test.failed"> | ||||
<classpath refid="test.ooxml.classpath" /> | <classpath refid="test.ooxml.classpath" /> | ||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="java.awt.headless" value="true"/> | <sysproperty key="java.awt.headless" value="true"/> | ||||
<formatter type="plain"/> | <formatter type="plain"/> | ||||
<formatter type="xml"/> | <formatter type="xml"/> | ||||
<target name="single-test-ooxml" depends="-test-property-check,compile-main,compile-ooxml" description="Runs a single ooxml test case specified with -Dtestcase=classname"> | <target name="single-test-ooxml" depends="-test-property-check,compile-main,compile-ooxml" description="Runs a single ooxml test case specified with -Dtestcase=classname"> | ||||
<junit printsummary="yes" showoutput="true" filtertrace="no" haltonfailure="false" > | <junit printsummary="yes" showoutput="true" filtertrace="no" haltonfailure="false" > | ||||
<classpath refid="test.ooxml.classpath" /> | <classpath refid="test.ooxml.classpath" /> | ||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="user.language" value="en"/> | |||||
<sysproperty key="user.country" value="US"/> | |||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/> | |||||
<sysproperty key="java.awt.headless" value="true"/> | <sysproperty key="java.awt.headless" value="true"/> | ||||
<formatter type="plain" usefile="no"/> | <formatter type="plain" usefile="no"/> | ||||
<formatter type="xml"/> | <formatter type="xml"/> | ||||
<attribute name="Implementation-Title" value="Apache POI"/> | <attribute name="Implementation-Title" value="Apache POI"/> | ||||
<attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | <attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | ||||
<attribute name="Implementation-Vendor" value="Apache"/> | <attribute name="Implementation-Vendor" value="Apache"/> | ||||
</manifest> | |||||
</manifest> | |||||
</jar> | </jar> | ||||
<jar destfile="${dist.dir}/${jar.name}-contrib-${version.id}-${DSTAMP}.jar"> | <jar destfile="${dist.dir}/${jar.name}-contrib-${version.id}-${DSTAMP}.jar"> | ||||
<fileset dir="${contrib.output.dir}" /> | <fileset dir="${contrib.output.dir}" /> | ||||
<attribute name="Implementation-Title" value="Apache POI"/> | <attribute name="Implementation-Title" value="Apache POI"/> | ||||
<attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | <attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | ||||
<attribute name="Implementation-Vendor" value="Apache"/> | <attribute name="Implementation-Vendor" value="Apache"/> | ||||
</manifest> | |||||
</manifest> | |||||
</jar> | </jar> | ||||
<jar destfile="${dist.dir}/${jar.name}-scratchpad-${version.id}-${DSTAMP}.jar"> | <jar destfile="${dist.dir}/${jar.name}-scratchpad-${version.id}-${DSTAMP}.jar"> | ||||
<fileset dir="${scratchpad.output.dir}" /> | <fileset dir="${scratchpad.output.dir}" /> | ||||
<attribute name="Implementation-Title" value="Apache POI"/> | <attribute name="Implementation-Title" value="Apache POI"/> | ||||
<attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | <attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | ||||
<attribute name="Implementation-Vendor" value="Apache"/> | <attribute name="Implementation-Vendor" value="Apache"/> | ||||
</manifest> | |||||
</manifest> | |||||
</jar> | </jar> | ||||
<jar destfile="${dist.dir}/${jar.name}-ooxml-${version.id}-${DSTAMP}.jar"> | <jar destfile="${dist.dir}/${jar.name}-ooxml-${version.id}-${DSTAMP}.jar"> | ||||
<fileset dir="${ooxml.output.dir}" /> | <fileset dir="${ooxml.output.dir}" /> | ||||
<attribute name="Implementation-Title" value="Apache POI"/> | <attribute name="Implementation-Title" value="Apache POI"/> | ||||
<attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | <attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | ||||
<attribute name="Implementation-Vendor" value="Apache"/> | <attribute name="Implementation-Vendor" value="Apache"/> | ||||
</manifest> | |||||
</manifest> | |||||
</jar> | </jar> | ||||
</target> | </target> | ||||
<target name="jar-examples" depends="compile, compile-version" description="Creates a jar file of the examples, in case people want to use them as-is"> | <target name="jar-examples" depends="compile, compile-version" description="Creates a jar file of the examples, in case people want to use them as-is"> | ||||
<attribute name="Implementation-Title" value="Apache POI"/> | <attribute name="Implementation-Title" value="Apache POI"/> | ||||
<attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | <attribute name="Implementation-Version" value="${version.id}-${DSTAMP}"/> | ||||
<attribute name="Implementation-Vendor" value="Apache"/> | <attribute name="Implementation-Vendor" value="Apache"/> | ||||
</manifest> | |||||
</manifest> | |||||
</jar> | </jar> | ||||
</target> | </target> | ||||
JDepend is not available. You must download JDepend from | JDepend is not available. You must download JDepend from | ||||
<http://www.clarkware.com/software/JDepend.html> and include the | <http://www.clarkware.com/software/JDepend.html> and include the | ||||
JAR file in your classpath. | JAR file in your classpath. | ||||
</echo> | |||||
</echo> | |||||
<fail message="JDepend is not available."/> | <fail message="JDepend is not available."/> | ||||
</target> | </target> | ||||
This product contains the chunks_parse_cmds.tbl file from the vsdump program. | This product contains the chunks_parse_cmds.tbl file from the vsdump program. | ||||
Copyright (C) 2006-2007 Valek Filippov (frob@df.ru) | Copyright (C) 2006-2007 Valek Filippov (frob@df.ru) | ||||
This product contains parts that were originally based on the eID Applet project | |||||
(http://code.google.com/p/eid-applet/). Copyright (C) 2008-2009 FedICT. |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.io.ByteArrayOutputStream; | |||||
import java.io.File; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.io.OutputStream; | |||||
import java.net.MalformedURLException; | |||||
import java.security.InvalidAlgorithmParameterException; | |||||
import java.security.Key; | |||||
import java.security.MessageDigest; | |||||
import java.security.NoSuchAlgorithmException; | |||||
import java.security.cert.X509Certificate; | |||||
import java.util.LinkedList; | |||||
import java.util.List; | |||||
import java.util.UUID; | |||||
import javax.xml.crypto.MarshalException; | |||||
import javax.xml.crypto.URIDereferencer; | |||||
import javax.xml.crypto.XMLStructure; | |||||
import javax.xml.crypto.dom.DOMCryptoContext; | |||||
import javax.xml.crypto.dsig.CanonicalizationMethod; | |||||
import javax.xml.crypto.dsig.DigestMethod; | |||||
import javax.xml.crypto.dsig.Manifest; | |||||
import javax.xml.crypto.dsig.Reference; | |||||
import javax.xml.crypto.dsig.SignatureMethod; | |||||
import javax.xml.crypto.dsig.SignedInfo; | |||||
import javax.xml.crypto.dsig.Transform; | |||||
import javax.xml.crypto.dsig.XMLObject; | |||||
import javax.xml.crypto.dsig.XMLSignContext; | |||||
import javax.xml.crypto.dsig.XMLSignatureException; | |||||
import javax.xml.crypto.dsig.XMLSignatureFactory; | |||||
import javax.xml.crypto.dsig.dom.DOMSignContext; | |||||
import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec; | |||||
import javax.xml.crypto.dsig.spec.TransformParameterSpec; | |||||
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.TransformerConfigurationException; | |||||
import javax.xml.transform.TransformerException; | |||||
import javax.xml.transform.TransformerFactory; | |||||
import javax.xml.transform.TransformerFactoryConfigurationError; | |||||
import javax.xml.transform.dom.DOMSource; | |||||
import javax.xml.transform.stream.StreamResult; | |||||
import org.apache.commons.io.FilenameUtils; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import org.apache.poi.ooxml.signature.service.spi.DigestInfo; | |||||
import org.apache.poi.ooxml.signature.service.spi.SignatureService; | |||||
import org.apache.xml.security.signature.XMLSignature; | |||||
import org.apache.xml.security.utils.Base64; | |||||
import org.apache.xml.security.utils.Constants; | |||||
import org.apache.xpath.XPathAPI; | |||||
import org.jcp.xml.dsig.internal.dom.DOMReference; | |||||
import org.jcp.xml.dsig.internal.dom.DOMSignedInfo; | |||||
import org.jcp.xml.dsig.internal.dom.DOMXMLSignature; | |||||
import org.w3c.dom.Document; | |||||
import org.w3c.dom.Element; | |||||
import org.w3c.dom.Node; | |||||
import org.w3c.dom.NodeList; | |||||
import org.xml.sax.InputSource; | |||||
import org.xml.sax.SAXException; | |||||
/** | |||||
* Abstract base class for an XML Signature Service implementation. | |||||
*/ | |||||
public abstract class AbstractXmlSignatureService implements SignatureService { | |||||
static final Log LOG = LogFactory.getLog(AbstractXmlSignatureService.class); | |||||
private static final String SIGNATURE_ID_ATTRIBUTE = "signature-id"; | |||||
// TODO refactor everything using the signature aspect design pattern | |||||
private final List<SignatureAspect> signatureAspects; | |||||
/** | |||||
* Main constructor. | |||||
*/ | |||||
public AbstractXmlSignatureService() { | |||||
this.signatureAspects = new LinkedList<SignatureAspect>(); | |||||
} | |||||
/** | |||||
* Adds a signature aspect to this XML signature service. | |||||
* | |||||
* @param signatureAspect | |||||
*/ | |||||
protected void addSignatureAspect(SignatureAspect signatureAspect) { | |||||
this.signatureAspects.add(signatureAspect); | |||||
} | |||||
/** | |||||
* Gives back the signature digest algorithm. Allowed values are SHA-1, | |||||
* SHA-256, SHA-384, SHA-512, RIPEND160. The default algorithm is SHA-1. | |||||
* Override this method to select another signature digest algorithm. | |||||
* | |||||
* @return | |||||
*/ | |||||
protected String getSignatureDigestAlgorithm() { | |||||
return "SHA-1"; | |||||
} | |||||
/** | |||||
* Gives back a list of service digest infos. Override this method to | |||||
* provide digest infos of files located in the service itself. | |||||
* | |||||
* @return | |||||
*/ | |||||
protected List<DigestInfo> getServiceDigestInfos() { | |||||
return new LinkedList<DigestInfo>(); | |||||
} | |||||
/** | |||||
* Gives back the enveloping document. Return <code>null</code> in case | |||||
* ds:Signature should be the top-level element. Implementations can | |||||
* override this method to provide a custom enveloping document. | |||||
* | |||||
* @return | |||||
* @throws SAXException | |||||
* @throws IOException | |||||
*/ | |||||
protected Document getEnvelopingDocument() throws ParserConfigurationException, IOException, SAXException { | |||||
return null; | |||||
} | |||||
/** | |||||
* Gives back a list of reference URIs that need to be signed. These URIs | |||||
* can refer to elements inside the enveloping document or to external | |||||
* resources. Override this method to feed in other ds:Reference URIs. | |||||
* | |||||
* @return | |||||
*/ | |||||
protected List<String> getReferenceUris() { | |||||
return new LinkedList<String>(); | |||||
} | |||||
public static class ReferenceInfo { | |||||
private final String uri; | |||||
private final String transform; | |||||
public ReferenceInfo(String uri, String transform) { | |||||
this.uri = uri; | |||||
this.transform = transform; | |||||
} | |||||
public ReferenceInfo(String uri) { | |||||
this(uri, null); | |||||
} | |||||
public String getUri() { | |||||
return this.uri; | |||||
} | |||||
public String getTransform() { | |||||
return this.transform; | |||||
} | |||||
} | |||||
/** | |||||
* Gives back a list of references that need to be signed. Implementation | |||||
* can override this method. | |||||
* | |||||
* @return | |||||
*/ | |||||
protected List<ReferenceInfo> getReferences() { | |||||
return new LinkedList<ReferenceInfo>(); | |||||
} | |||||
/** | |||||
* Override this method to change the URI dereferener used by the signing | |||||
* engine. | |||||
* | |||||
* @return | |||||
*/ | |||||
protected URIDereferencer getURIDereferencer() { | |||||
return null; | |||||
} | |||||
/** | |||||
* Gives back the human-readable description of what the citizen will be | |||||
* signing. The default value is "XML Signature". Override this method to | |||||
* provide the citizen with another description. | |||||
* | |||||
* @return | |||||
*/ | |||||
protected String getSignatureDescription() { | |||||
return "XML Signature"; | |||||
} | |||||
/** | |||||
* Gives back a temporary data storage component. This component is used for | |||||
* temporary storage of the XML signature documents. | |||||
* | |||||
* @return | |||||
*/ | |||||
protected abstract TemporaryDataStorage getTemporaryDataStorage(); | |||||
/** | |||||
* Gives back the output stream to which to write the signed XML document. | |||||
* | |||||
* @return | |||||
*/ | |||||
protected abstract OutputStream getSignedDocumentOutputStream(); | |||||
public DigestInfo preSign(List<DigestInfo> digestInfos, List<X509Certificate> signingCertificateChain) throws NoSuchAlgorithmException { | |||||
LOG.debug("preSign"); | |||||
String digestAlgo = getSignatureDigestAlgorithm(); | |||||
byte[] digestValue; | |||||
try { | |||||
digestValue = getXmlSignatureDigestValue(digestAlgo, digestInfos); | |||||
} catch (Exception e) { | |||||
throw new RuntimeException("XML signature error: " + e.getMessage(), e); | |||||
} | |||||
String description = getSignatureDescription(); | |||||
return new DigestInfo(digestValue, digestAlgo, description); | |||||
} | |||||
/** | |||||
* Can be overridden by XML signature service implementation to further | |||||
* process the signed XML document. | |||||
* | |||||
* @param sinatureElement | |||||
* @param signingCertificateChain | |||||
*/ | |||||
protected void postSign(Element sinatureElement, List<X509Certificate> signingCertificateChain) { | |||||
// empty | |||||
} | |||||
public void postSign(byte[] signatureValue, List<X509Certificate> signingCertificateChain) { | |||||
LOG.debug("postSign"); | |||||
/* | |||||
* Retrieve the intermediate XML signature document from the temporary | |||||
* data storage. | |||||
*/ | |||||
TemporaryDataStorage temporaryDataStorage = getTemporaryDataStorage(); | |||||
InputStream documentInputStream = temporaryDataStorage.getTempInputStream(); | |||||
String signatureId = (String) temporaryDataStorage.getAttribute(SIGNATURE_ID_ATTRIBUTE); | |||||
LOG.debug("signature Id: " + signatureId); | |||||
/* | |||||
* Load the signature DOM document. | |||||
*/ | |||||
Document document; | |||||
try { | |||||
document = loadDocument(documentInputStream); | |||||
} catch (Exception e) { | |||||
throw new RuntimeException("DOM error: " + e.getMessage(), e); | |||||
} | |||||
/* | |||||
* Locate the correct ds:Signature node. | |||||
*/ | |||||
Element nsElement = document.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS); | |||||
Element signatureElement; | |||||
try { | |||||
signatureElement = (Element) XPathAPI.selectSingleNode(document, "//ds:Signature[@Id='" + signatureId + "']", nsElement); | |||||
} catch (TransformerException e) { | |||||
throw new RuntimeException("XPATH error: " + e.getMessage(), e); | |||||
} | |||||
if (null == signatureElement) { | |||||
throw new RuntimeException("ds:Signature not found for @Id: " + signatureId); | |||||
} | |||||
/* | |||||
* Insert signature value into the ds:SignatureValue element | |||||
*/ | |||||
NodeList signatureValueNodeList = signatureElement.getElementsByTagNameNS(javax.xml.crypto.dsig.XMLSignature.XMLNS, "SignatureValue"); | |||||
Element signatureValueElement = (Element) signatureValueNodeList.item(0); | |||||
signatureValueElement.setTextContent(Base64.encode(signatureValue)); | |||||
/* | |||||
* Allow implementation classes to inject their own stuff. | |||||
*/ | |||||
postSign(signatureElement, signingCertificateChain); | |||||
OutputStream signedDocumentOutputStream = getSignedDocumentOutputStream(); | |||||
if (null == signedDocumentOutputStream) { | |||||
throw new IllegalArgumentException("signed document output stream is null"); | |||||
} | |||||
try { | |||||
writeDocument(document, signedDocumentOutputStream); | |||||
} catch (Exception e) { | |||||
LOG.debug("error writing the signed XML document: " + e.getMessage(), e); | |||||
throw new RuntimeException("error writing the signed XML document: " + e.getMessage(), e); | |||||
} | |||||
} | |||||
protected String getCanonicalizationMethod() { | |||||
// CanonicalizationMethod.INCLUSIVE fails for OOo | |||||
return CanonicalizationMethod.EXCLUSIVE; | |||||
} | |||||
private byte[] getXmlSignatureDigestValue(String digestAlgo, List<DigestInfo> digestInfos) throws ParserConfigurationException, NoSuchAlgorithmException, | |||||
InvalidAlgorithmParameterException, MarshalException, javax.xml.crypto.dsig.XMLSignatureException, | |||||
TransformerFactoryConfigurationError, TransformerException, IOException, SAXException { | |||||
/* | |||||
* DOM Document construction. | |||||
*/ | |||||
Document document = getEnvelopingDocument(); | |||||
if (null == document) { | |||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||||
documentBuilderFactory.setNamespaceAware(true); | |||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||||
document = documentBuilder.newDocument(); | |||||
} | |||||
/* | |||||
* Signature context construction. | |||||
*/ | |||||
Key key = new Key() { | |||||
private static final long serialVersionUID = 1L; | |||||
public String getAlgorithm() { | |||||
return null; | |||||
} | |||||
public byte[] getEncoded() { | |||||
return null; | |||||
} | |||||
public String getFormat() { | |||||
return null; | |||||
} | |||||
}; | |||||
XMLSignContext xmlSignContext = new DOMSignContext(key, document); | |||||
URIDereferencer uriDereferencer = getURIDereferencer(); | |||||
if (null != uriDereferencer) { | |||||
xmlSignContext.setURIDereferencer(uriDereferencer); | |||||
} | |||||
// OOo doesn't like ds namespaces. | |||||
// xmlSignContext.putNamespacePrefix( | |||||
// javax.xml.crypto.dsig.XMLSignature.XMLNS, "ds"); | |||||
XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM", new org.jcp.xml.dsig.internal.dom.XMLDSigRI()); | |||||
/* | |||||
* ds:Reference | |||||
*/ | |||||
List<Reference> references = new LinkedList<Reference>(); | |||||
addDigestInfosAsReferences(digestInfos, signatureFactory, references); | |||||
List<DigestInfo> serviceDigestInfos = getServiceDigestInfos(); | |||||
addDigestInfosAsReferences(serviceDigestInfos, signatureFactory, references); | |||||
addReferenceIds(signatureFactory, xmlSignContext, references); | |||||
addReferences(signatureFactory, references); | |||||
/* | |||||
* Invoke the signature aspects. | |||||
*/ | |||||
String signatureId = "xmldsig-" + UUID.randomUUID().toString(); | |||||
List<XMLObject> objects = new LinkedList<XMLObject>(); | |||||
for (SignatureAspect signatureAspect : this.signatureAspects) { | |||||
LOG.debug("invoking signature aspect: " + signatureAspect.getClass().getSimpleName()); | |||||
signatureAspect.preSign(signatureFactory, document, signatureId, references, objects); | |||||
} | |||||
/* | |||||
* ds:SignedInfo | |||||
*/ | |||||
SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(getSignatureMethod(digestAlgo), null); | |||||
CanonicalizationMethod canonicalizationMethod = signatureFactory.newCanonicalizationMethod(getCanonicalizationMethod(), (C14NMethodParameterSpec) null); | |||||
SignedInfo signedInfo = signatureFactory.newSignedInfo(canonicalizationMethod, signatureMethod, references); | |||||
/* | |||||
* JSR105 ds:Signature creation | |||||
*/ | |||||
String signatureValueId = signatureId + "-signature-value"; | |||||
javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory.newXMLSignature(signedInfo, null, objects, signatureId, signatureValueId); | |||||
/* | |||||
* ds:Signature Marshalling. | |||||
*/ | |||||
DOMXMLSignature domXmlSignature = (DOMXMLSignature) xmlSignature; | |||||
Node documentNode = document.getDocumentElement(); | |||||
if (null == documentNode) { | |||||
/* | |||||
* In case of an empty DOM document. | |||||
*/ | |||||
documentNode = document; | |||||
} | |||||
String dsPrefix = null; | |||||
// String dsPrefix = "ds"; | |||||
domXmlSignature.marshal(documentNode, dsPrefix, (DOMCryptoContext) xmlSignContext); | |||||
/* | |||||
* Completion of undigested ds:References in the ds:Manifests. | |||||
*/ | |||||
for (XMLObject object : objects) { | |||||
LOG.debug("object java type: " + object.getClass().getName()); | |||||
List<XMLStructure> objectContentList = object.getContent(); | |||||
for (XMLStructure objectContent : objectContentList) { | |||||
LOG.debug("object content java type: " + objectContent.getClass().getName()); | |||||
if (false == objectContent instanceof Manifest) { | |||||
continue; | |||||
} | |||||
Manifest manifest = (Manifest) objectContent; | |||||
List<Reference> manifestReferences = manifest.getReferences(); | |||||
for (Reference manifestReference : manifestReferences) { | |||||
if (null != manifestReference.getDigestValue()) { | |||||
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; | |||||
if (null != domReference.getDigestValue()) { | |||||
// ds:Reference with external digest value | |||||
continue; | |||||
} | |||||
domReference.digest(xmlSignContext); | |||||
} | |||||
/* | |||||
* Store the intermediate XML signature document. | |||||
*/ | |||||
TemporaryDataStorage temporaryDataStorage = getTemporaryDataStorage(); | |||||
OutputStream tempDocumentOutputStream = temporaryDataStorage.getTempOutputStream(); | |||||
writeDocument(document, tempDocumentOutputStream); | |||||
temporaryDataStorage.setAttribute(SIGNATURE_ID_ATTRIBUTE, signatureId); | |||||
/* | |||||
* 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 jcaMessageDigest = MessageDigest.getInstance(digestAlgo); | |||||
byte[] digestValue = jcaMessageDigest.digest(octets); | |||||
return digestValue; | |||||
} | |||||
private void addReferenceIds(XMLSignatureFactory signatureFactory, XMLSignContext xmlSignContext, List<Reference> references) | |||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, XMLSignatureException { | |||||
List<String> referenceUris = getReferenceUris(); | |||||
if (null == referenceUris) { | |||||
return; | |||||
} | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null); | |||||
for (String referenceUri : referenceUris) { | |||||
Reference reference = signatureFactory.newReference(referenceUri, digestMethod); | |||||
references.add(reference); | |||||
} | |||||
} | |||||
private void addReferences(XMLSignatureFactory xmlSignatureFactory, List<Reference> references) throws NoSuchAlgorithmException, | |||||
InvalidAlgorithmParameterException { | |||||
List<ReferenceInfo> referenceInfos = getReferences(); | |||||
if (null == referenceInfos) { | |||||
return; | |||||
} | |||||
if (referenceInfos.isEmpty()) { | |||||
return; | |||||
} | |||||
DigestMethod digestMethod = xmlSignatureFactory.newDigestMethod(DigestMethod.SHA1, null); | |||||
for (ReferenceInfo referenceInfo : referenceInfos) { | |||||
List<Transform> transforms = new LinkedList<Transform>(); | |||||
if (null != referenceInfo.getTransform()) { | |||||
Transform transform = xmlSignatureFactory.newTransform(referenceInfo.getTransform(), (TransformParameterSpec) null); | |||||
transforms.add(transform); | |||||
} | |||||
LOG.debug("adding ds:Reference " + referenceInfo.getUri()); | |||||
Reference reference = xmlSignatureFactory.newReference(referenceInfo.getUri(), digestMethod, transforms, null, null); | |||||
references.add(reference); | |||||
} | |||||
} | |||||
private void addDigestInfosAsReferences(List<DigestInfo> digestInfos, XMLSignatureFactory signatureFactory, List<Reference> references) | |||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException, MalformedURLException { | |||||
if (null == digestInfos) { | |||||
return; | |||||
} | |||||
for (DigestInfo digestInfo : digestInfos) { | |||||
byte[] documentDigestValue = digestInfo.digestValue; | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(getXmlDigestAlgo(digestInfo.digestAlgo), null); | |||||
String uri = FilenameUtils.getName(new File(digestInfo.description).toURI().toURL().getFile()); | |||||
Reference reference = signatureFactory.newReference(uri, digestMethod, null, null, null, documentDigestValue); | |||||
references.add(reference); | |||||
} | |||||
} | |||||
private String getXmlDigestAlgo(String digestAlgo) { | |||||
if ("SHA-1".equals(digestAlgo)) { | |||||
return DigestMethod.SHA1; | |||||
} | |||||
if ("SHA-256".equals(digestAlgo)) { | |||||
return DigestMethod.SHA256; | |||||
} | |||||
if ("SHA-512".equals(digestAlgo)) { | |||||
return DigestMethod.SHA512; | |||||
} | |||||
throw new RuntimeException("unsupported digest algo: " + digestAlgo); | |||||
} | |||||
private String getSignatureMethod(String digestAlgo) { | |||||
if (null == digestAlgo) { | |||||
throw new RuntimeException("digest algo is null"); | |||||
} | |||||
if ("SHA-1".equals(digestAlgo)) { | |||||
return SignatureMethod.RSA_SHA1; | |||||
} | |||||
if ("SHA-256".equals(digestAlgo)) { | |||||
return XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256; | |||||
} | |||||
if ("SHA-512".equals(digestAlgo)) { | |||||
return XMLSignature.ALGO_ID_MAC_HMAC_SHA512; | |||||
} | |||||
if ("SHA-384".equals(digestAlgo)) { | |||||
return XMLSignature.ALGO_ID_MAC_HMAC_SHA384; | |||||
} | |||||
if ("RIPEMD160".equals(digestAlgo)) { | |||||
return XMLSignature.ALGO_ID_MAC_HMAC_RIPEMD160; | |||||
} | |||||
throw new RuntimeException("unsupported sign algo: " + digestAlgo); | |||||
} | |||||
protected void writeDocument(Document document, OutputStream documentOutputStream) throws TransformerConfigurationException, | |||||
TransformerFactoryConfigurationError, TransformerException, IOException { | |||||
writeDocumentNoClosing(document, documentOutputStream); | |||||
documentOutputStream.close(); | |||||
} | |||||
protected void writeDocumentNoClosing(Document document, OutputStream documentOutputStream) throws TransformerConfigurationException, | |||||
TransformerFactoryConfigurationError, TransformerException, IOException { | |||||
// we need the XML processing initial line for OOXML | |||||
writeDocumentNoClosing(document, documentOutputStream, false); | |||||
} | |||||
protected void writeDocumentNoClosing(Document document, OutputStream documentOutputStream, boolean omitXmlDeclaration) | |||||
throws TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException, IOException { | |||||
NoCloseOutputStream outputStream = new NoCloseOutputStream(documentOutputStream); | |||||
Result result = new StreamResult(outputStream); | |||||
Transformer xformer = TransformerFactory.newInstance().newTransformer(); | |||||
if (omitXmlDeclaration) { | |||||
xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); | |||||
} | |||||
Source source = new DOMSource(document); | |||||
xformer.transform(source, result); | |||||
} | |||||
protected 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; | |||||
} | |||||
protected Document loadDocumentNoClose(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException { | |||||
NoCloseInputStream noCloseInputStream = new NoCloseInputStream(documentInputStream); | |||||
InputSource inputSource = new InputSource(noCloseInputStream); | |||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||||
documentBuilderFactory.setNamespaceAware(true); | |||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||||
Document document = documentBuilder.parse(inputSource); | |||||
return document; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.security.Key; | |||||
import java.security.cert.X509Certificate; | |||||
import java.util.List; | |||||
import javax.xml.crypto.AlgorithmMethod; | |||||
import javax.xml.crypto.KeySelector; | |||||
import javax.xml.crypto.KeySelectorException; | |||||
import javax.xml.crypto.KeySelectorResult; | |||||
import javax.xml.crypto.XMLCryptoContext; | |||||
import javax.xml.crypto.XMLStructure; | |||||
import javax.xml.crypto.dsig.keyinfo.KeyInfo; | |||||
import javax.xml.crypto.dsig.keyinfo.X509Data; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
/** | |||||
* JSR105 key selector implementation using the ds:KeyInfo data of the signature | |||||
* itself. | |||||
*/ | |||||
public class KeyInfoKeySelector extends KeySelector implements KeySelectorResult { | |||||
private static final Log LOG = LogFactory.getLog(KeyInfoKeySelector.class); | |||||
private X509Certificate certificate; | |||||
@Override | |||||
public KeySelectorResult select(KeyInfo keyInfo, Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException { | |||||
LOG.debug("select key"); | |||||
if (null == keyInfo) { | |||||
throw new KeySelectorException("no ds:KeyInfo present"); | |||||
} | |||||
List<XMLStructure> keyInfoContent = keyInfo.getContent(); | |||||
this.certificate = null; | |||||
for (XMLStructure keyInfoStructure : keyInfoContent) { | |||||
if (false == (keyInfoStructure instanceof X509Data)) { | |||||
continue; | |||||
} | |||||
X509Data x509Data = (X509Data) keyInfoStructure; | |||||
List<Object> x509DataList = x509Data.getContent(); | |||||
for (Object x509DataObject : x509DataList) { | |||||
if (false == (x509DataObject instanceof X509Certificate)) { | |||||
continue; | |||||
} | |||||
X509Certificate certificate = (X509Certificate) x509DataObject; | |||||
LOG.debug("certificate: " + certificate.getSubjectX500Principal()); | |||||
if (null == this.certificate) { | |||||
/* | |||||
* The first certificate is presumably the signer. | |||||
*/ | |||||
this.certificate = certificate; | |||||
} | |||||
} | |||||
if (null != this.certificate) { | |||||
return this; | |||||
} | |||||
} | |||||
throw new KeySelectorException("No key found!"); | |||||
} | |||||
public Key getKey() { | |||||
return this.certificate.getPublicKey(); | |||||
} | |||||
/** | |||||
* Gives back the X509 certificate used during the last signature | |||||
* verification operation. | |||||
* | |||||
* @return | |||||
*/ | |||||
public X509Certificate getCertificate() { | |||||
return this.certificate; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import org.apache.commons.io.input.ProxyInputStream; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
/** | |||||
* Input Stream proxy that doesn't close the underlying input stream. | |||||
*/ | |||||
public class NoCloseInputStream extends ProxyInputStream { | |||||
private static final Log LOG = LogFactory.getLog(NoCloseInputStream.class); | |||||
/** | |||||
* Main constructor. | |||||
* | |||||
* @param proxy | |||||
*/ | |||||
public NoCloseInputStream(InputStream proxy) { | |||||
super(proxy); | |||||
} | |||||
@Override | |||||
public void close() throws IOException { | |||||
LOG.debug("close"); | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.io.IOException; | |||||
import java.io.OutputStream; | |||||
import org.apache.commons.io.output.ProxyOutputStream; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
/** | |||||
* Output Stream proxy that doesn't close the underlying stream. | |||||
*/ | |||||
public class NoCloseOutputStream extends ProxyOutputStream { | |||||
private static final Log LOG = LogFactory.getLog(NoCloseOutputStream.class); | |||||
/** | |||||
* Main constructor. | |||||
* | |||||
* @param proxy | |||||
*/ | |||||
public NoCloseOutputStream(OutputStream proxy) { | |||||
super(proxy); | |||||
} | |||||
@Override | |||||
public void close() throws IOException { | |||||
LOG.debug("close"); | |||||
// empty | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.security.InvalidAlgorithmParameterException; | |||||
import java.security.NoSuchAlgorithmException; | |||||
import java.util.List; | |||||
import javax.xml.crypto.dsig.Reference; | |||||
import javax.xml.crypto.dsig.XMLObject; | |||||
import javax.xml.crypto.dsig.XMLSignatureFactory; | |||||
import org.w3c.dom.Document; | |||||
/** | |||||
* JSR105 Signature Aspect interface. | |||||
*/ | |||||
public interface SignatureAspect { | |||||
/** | |||||
* This method is being invoked by the XML signature service engine during | |||||
* pre-sign phase. Via this method a signature aspect implementation can add | |||||
* signature aspects to an XML signature. | |||||
* | |||||
* @param signatureFactory | |||||
* @param document | |||||
* @param signatureId | |||||
* @param references | |||||
* @param objects | |||||
* @throws InvalidAlgorithmParameterException | |||||
* @throws NoSuchAlgorithmException | |||||
*/ | |||||
void preSign(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<Reference> references, List<XMLObject> objects) | |||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException; | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.io.InputStream; | |||||
import java.io.OutputStream; | |||||
import java.io.Serializable; | |||||
/** | |||||
* Interface for temporary data storage. | |||||
*/ | |||||
public interface TemporaryDataStorage { | |||||
/** | |||||
* Gives back the temporary output stream that can be used for data storage. | |||||
* | |||||
* @return | |||||
*/ | |||||
OutputStream getTempOutputStream(); | |||||
/** | |||||
* Gives back the temporary input stream for retrieval of the previously | |||||
* stored data. | |||||
* | |||||
* @return | |||||
*/ | |||||
InputStream getTempInputStream(); | |||||
/** | |||||
* Stores an attribute to the temporary data storage. | |||||
* | |||||
* @param attributeName | |||||
* @param attributeValue | |||||
*/ | |||||
void setAttribute(String attributeName, Serializable attributeValue); | |||||
/** | |||||
* Retrieves an attribute from the temporary data storage. | |||||
* | |||||
* @param attributeName | |||||
* @return | |||||
*/ | |||||
Serializable getAttribute(String attributeName); | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer.ooxml; | |||||
import java.io.ByteArrayOutputStream; | |||||
import java.io.IOException; | |||||
import java.io.OutputStream; | |||||
import java.net.URL; | |||||
import java.security.Key; | |||||
import java.security.KeyException; | |||||
import java.security.cert.X509Certificate; | |||||
import java.util.LinkedList; | |||||
import java.util.List; | |||||
import java.util.UUID; | |||||
import java.util.zip.ZipEntry; | |||||
import java.util.zip.ZipInputStream; | |||||
import java.util.zip.ZipOutputStream; | |||||
import javax.xml.crypto.MarshalException; | |||||
import javax.xml.crypto.URIDereferencer; | |||||
import javax.xml.crypto.dom.DOMCryptoContext; | |||||
import javax.xml.crypto.dsig.CanonicalizationMethod; | |||||
import javax.xml.crypto.dsig.XMLSignContext; | |||||
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 javax.xml.parsers.DocumentBuilder; | |||||
import javax.xml.parsers.DocumentBuilderFactory; | |||||
import javax.xml.parsers.ParserConfigurationException; | |||||
import javax.xml.transform.TransformerConfigurationException; | |||||
import javax.xml.transform.TransformerException; | |||||
import javax.xml.transform.TransformerFactoryConfigurationError; | |||||
import org.apache.commons.io.FilenameUtils; | |||||
import org.apache.commons.io.IOUtils; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import org.apache.poi.ooxml.signature.service.signer.AbstractXmlSignatureService; | |||||
import org.apache.xml.security.utils.Constants; | |||||
import org.apache.xpath.XPathAPI; | |||||
import org.jcp.xml.dsig.internal.dom.DOMKeyInfo; | |||||
import org.w3c.dom.Document; | |||||
import org.w3c.dom.Element; | |||||
import org.w3c.dom.Node; | |||||
import org.w3c.dom.NodeList; | |||||
import org.xml.sax.SAXException; | |||||
/** | |||||
* Signature Service implementation for Office OpenXML document format XML | |||||
* signatures. | |||||
*/ | |||||
public abstract class AbstractOOXMLSignatureService extends AbstractXmlSignatureService { | |||||
static final Log LOG = LogFactory.getLog(AbstractOOXMLSignatureService.class); | |||||
protected AbstractOOXMLSignatureService() { | |||||
addSignatureAspect(new OOXMLSignatureAspect(this)); | |||||
} | |||||
@Override | |||||
protected String getSignatureDescription() { | |||||
return "Office OpenXML Document"; | |||||
} | |||||
public String getFilesDigestAlgorithm() { | |||||
return null; | |||||
} | |||||
@Override | |||||
protected final URIDereferencer getURIDereferencer() { | |||||
URL ooxmlUrl = getOfficeOpenXMLDocumentURL(); | |||||
return new OOXMLURIDereferencer(ooxmlUrl); | |||||
} | |||||
@Override | |||||
protected String getCanonicalizationMethod() { | |||||
return CanonicalizationMethod.INCLUSIVE; | |||||
} | |||||
@Override | |||||
protected void postSign(Element signatureElement, List<X509Certificate> signingCertificateChain) { | |||||
// TODO: implement as SignatureAspect | |||||
LOG.debug("postSign: adding ds:KeyInfo"); | |||||
/* | |||||
* Make sure we insert right after the ds:SignatureValue element. | |||||
*/ | |||||
Node nextSibling; | |||||
NodeList objectNodeList = signatureElement.getElementsByTagNameNS("http://www.w3.org/2000/09/xmldsig#", "Object"); | |||||
if (0 == objectNodeList.getLength()) { | |||||
nextSibling = null; | |||||
} else { | |||||
nextSibling = objectNodeList.item(0); | |||||
} | |||||
/* | |||||
* Add a ds:KeyInfo entry. | |||||
*/ | |||||
KeyInfoFactory keyInfoFactory = CryptoFactoryFactory.getKeyInfoFactory(); | |||||
List<Object> x509DataObjects = new LinkedList<Object>(); | |||||
X509Certificate signingCertificate = signingCertificateChain.get(0); | |||||
KeyValue keyValue; | |||||
try { | |||||
keyValue = keyInfoFactory.newKeyValue(signingCertificate.getPublicKey()); | |||||
} catch (KeyException e) { | |||||
throw new RuntimeException("key exception: " + e.getMessage(), e); | |||||
} | |||||
for (X509Certificate certificate : signingCertificateChain) { | |||||
x509DataObjects.add(certificate); | |||||
} | |||||
X509Data x509Data = keyInfoFactory.newX509Data(x509DataObjects); | |||||
List<Object> keyInfoContent = new LinkedList<Object>(); | |||||
keyInfoContent.add(keyValue); | |||||
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; | |||||
} | |||||
}; | |||||
XMLSignContext xmlSignContext = new DOMSignContext(key, signatureElement); | |||||
DOMCryptoContext domCryptoContext = (DOMCryptoContext) xmlSignContext; | |||||
String dsPrefix = null; | |||||
// String dsPrefix = "ds"; | |||||
try { | |||||
domKeyInfo.marshal(signatureElement, nextSibling, dsPrefix, domCryptoContext); | |||||
} catch (MarshalException e) { | |||||
throw new RuntimeException("marshall error: " + e.getMessage(), e); | |||||
} | |||||
} | |||||
private class OOXMLSignedDocumentOutputStream extends ByteArrayOutputStream { | |||||
@Override | |||||
public void close() throws IOException { | |||||
LOG.debug("close OOXML signed document output stream"); | |||||
super.close(); | |||||
try { | |||||
outputSignedOfficeOpenXMLDocument(this.toByteArray()); | |||||
} catch (Exception e) { | |||||
throw new IOException(e.getMessage()); | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* @return The output stream to which to write the signed Office OpenXML file. | |||||
*/ | |||||
abstract protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream(); | |||||
/** | |||||
* @return the URL of the OOXML to be signed. | |||||
*/ | |||||
abstract protected URL getOfficeOpenXMLDocumentURL(); | |||||
private void outputSignedOfficeOpenXMLDocument(byte[] signatureData) throws IOException, ParserConfigurationException, SAXException, TransformerException { | |||||
LOG.debug("output signed Office OpenXML document"); | |||||
OutputStream signedOOXMLOutputStream = getSignedOfficeOpenXMLDocumentOutputStream(); | |||||
if (null == signedOOXMLOutputStream) { | |||||
throw new NullPointerException("signedOOXMLOutputStream is null"); | |||||
} | |||||
String signatureZipEntryName = "_xmlsignatures/sig-" + UUID.randomUUID().toString() + ".xml"; | |||||
LOG.debug("signature ZIP entry name: " + signatureZipEntryName); | |||||
/* | |||||
* Copy the original OOXML content to the signed OOXML package. During | |||||
* copying some files need to changed. | |||||
*/ | |||||
ZipOutputStream zipOutputStream = copyOOXMLContent(signatureZipEntryName, signedOOXMLOutputStream); | |||||
/* | |||||
* Add the OOXML XML signature file to the OOXML package. | |||||
*/ | |||||
ZipEntry zipEntry = new ZipEntry(signatureZipEntryName); | |||||
zipOutputStream.putNextEntry(zipEntry); | |||||
IOUtils.write(signatureData, zipOutputStream); | |||||
zipOutputStream.close(); | |||||
} | |||||
private ZipOutputStream copyOOXMLContent(String signatureZipEntryName, OutputStream signedOOXMLOutputStream) throws IOException, | |||||
ParserConfigurationException, SAXException, TransformerConfigurationException, TransformerFactoryConfigurationError, | |||||
TransformerException { | |||||
ZipOutputStream zipOutputStream = new ZipOutputStream(signedOOXMLOutputStream); | |||||
ZipInputStream zipInputStream = new ZipInputStream(this.getOfficeOpenXMLDocumentURL().openStream()); | |||||
ZipEntry zipEntry; | |||||
boolean hasOriginSigsRels = false; | |||||
while (null != (zipEntry = zipInputStream.getNextEntry())) { | |||||
LOG.debug("copy ZIP entry: " + zipEntry.getName()); | |||||
ZipEntry newZipEntry = new ZipEntry(zipEntry.getName()); | |||||
zipOutputStream.putNextEntry(newZipEntry); | |||||
if ("[Content_Types].xml".equals(zipEntry.getName())) { | |||||
Document contentTypesDocument = loadDocumentNoClose(zipInputStream); | |||||
Element typesElement = contentTypesDocument.getDocumentElement(); | |||||
/* | |||||
* We need to add an Override element. | |||||
*/ | |||||
Element overrideElement = contentTypesDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/content-types", "Override"); | |||||
overrideElement.setAttribute("PartName", "/" + signatureZipEntryName); | |||||
overrideElement.setAttribute("ContentType", "application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"); | |||||
typesElement.appendChild(overrideElement); | |||||
Element nsElement = contentTypesDocument.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/content-types"); | |||||
NodeList nodeList = XPathAPI.selectNodeList(contentTypesDocument, "/tns:Types/tns:Default[@Extension='sigs']", nsElement); | |||||
if (0 == nodeList.getLength()) { | |||||
/* | |||||
* Add Default element for 'sigs' extension. | |||||
*/ | |||||
Element defaultElement = contentTypesDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/content-types", "Default"); | |||||
defaultElement.setAttribute("Extension", "sigs"); | |||||
defaultElement.setAttribute("ContentType", "application/vnd.openxmlformats-package.digital-signature-origin"); | |||||
typesElement.appendChild(defaultElement); | |||||
} | |||||
writeDocumentNoClosing(contentTypesDocument, zipOutputStream, false); | |||||
} else if ("_rels/.rels".equals(zipEntry.getName())) { | |||||
Document relsDocument = loadDocumentNoClose(zipInputStream); | |||||
Element nsElement = relsDocument.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/relationships"); | |||||
NodeList nodeList = XPathAPI.selectNodeList(relsDocument, "/tns:Relationships/tns:Relationship[@Target='_xmlsignatures/origin.sigs']", | |||||
nsElement); | |||||
if (0 == nodeList.getLength()) { | |||||
Element relationshipElement = relsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationship"); | |||||
relationshipElement.setAttribute("Id", "rel-id-" + UUID.randomUUID().toString()); | |||||
relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin"); | |||||
relationshipElement.setAttribute("Target", "_xmlsignatures/origin.sigs"); | |||||
relsDocument.getDocumentElement().appendChild(relationshipElement); | |||||
} | |||||
writeDocumentNoClosing(relsDocument, zipOutputStream, false); | |||||
} else if ("_xmlsignatures/_rels/origin.sigs.rels".equals(zipEntry.getName())) { | |||||
hasOriginSigsRels = true; | |||||
Document originSignRelsDocument = loadDocumentNoClose(zipInputStream); | |||||
Element relationshipElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", | |||||
"Relationship"); | |||||
String relationshipId = "rel-" + UUID.randomUUID().toString(); | |||||
relationshipElement.setAttribute("Id", relationshipId); | |||||
relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature"); | |||||
String target = FilenameUtils.getName(signatureZipEntryName); | |||||
LOG.debug("target: " + target); | |||||
relationshipElement.setAttribute("Target", target); | |||||
originSignRelsDocument.getDocumentElement().appendChild(relationshipElement); | |||||
writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false); | |||||
} else { | |||||
IOUtils.copy(zipInputStream, zipOutputStream); | |||||
} | |||||
} | |||||
if (false == hasOriginSigsRels) { | |||||
/* | |||||
* Add signature relationships document. | |||||
*/ | |||||
addOriginSigsRels(signatureZipEntryName, zipOutputStream); | |||||
addOriginSigs(zipOutputStream); | |||||
} | |||||
/* | |||||
* Return. | |||||
*/ | |||||
zipInputStream.close(); | |||||
return zipOutputStream; | |||||
} | |||||
private void addOriginSigs(ZipOutputStream zipOutputStream) throws IOException { | |||||
zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/origin.sigs")); | |||||
} | |||||
private void addOriginSigsRels(String signatureZipEntryName, ZipOutputStream zipOutputStream) throws ParserConfigurationException, IOException, | |||||
TransformerConfigurationException, TransformerFactoryConfigurationError, TransformerException { | |||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||||
documentBuilderFactory.setNamespaceAware(true); | |||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||||
Document originSignRelsDocument = documentBuilder.newDocument(); | |||||
Element relationshipsElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationships"); | |||||
relationshipsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "http://schemas.openxmlformats.org/package/2006/relationships"); | |||||
originSignRelsDocument.appendChild(relationshipsElement); | |||||
Element relationshipElement = originSignRelsDocument.createElementNS("http://schemas.openxmlformats.org/package/2006/relationships", "Relationship"); | |||||
String relationshipId = "rel-" + UUID.randomUUID().toString(); | |||||
relationshipElement.setAttribute("Id", relationshipId); | |||||
relationshipElement.setAttribute("Type", "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature"); | |||||
String target = FilenameUtils.getName(signatureZipEntryName); | |||||
LOG.debug("target: " + target); | |||||
relationshipElement.setAttribute("Target", target); | |||||
relationshipsElement.appendChild(relationshipElement); | |||||
zipOutputStream.putNextEntry(new ZipEntry("_xmlsignatures/_rels/origin.sigs.rels")); | |||||
writeDocumentNoClosing(originSignRelsDocument, zipOutputStream, false); | |||||
} | |||||
@Override | |||||
protected OutputStream getSignedDocumentOutputStream() { | |||||
LOG.debug("get signed document output stream"); | |||||
/* | |||||
* Create each time a new object; we want an empty output stream to | |||||
* start with. | |||||
*/ | |||||
OutputStream signedDocumentOutputStream = new OOXMLSignedDocumentOutputStream(); | |||||
return signedDocumentOutputStream; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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.ooxml.signature.service.signer.ooxml; | |||||
import java.security.Provider; | |||||
import javax.xml.crypto.dsig.XMLSignatureFactory; | |||||
import javax.xml.crypto.dsig.keyinfo.KeyInfoFactory; | |||||
/** | |||||
* Creates {@link XMLSignatureFactory} and {@link KeyInfoFactory} instances | |||||
* as used by the ooxml signature service. | |||||
*/ | |||||
final class CryptoFactoryFactory { | |||||
private static final Provider _provider = new org.jcp.xml.dsig.internal.dom.XMLDSigRI(); | |||||
private CryptoFactoryFactory() { | |||||
// no instances of this class | |||||
} | |||||
public static XMLSignatureFactory getSignatureFactory() { | |||||
return XMLSignatureFactory.getInstance("DOM", _provider); | |||||
} | |||||
public static KeyInfoFactory getKeyInfoFactory() { | |||||
return KeyInfoFactory.getInstance("DOM", _provider); | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer.ooxml; | |||||
import java.security.Provider; | |||||
import java.security.Security; | |||||
/** | |||||
* Security Provider for Office OpenXML. | |||||
*/ | |||||
public class OOXMLProvider extends Provider { | |||||
private static final long serialVersionUID = 1L; | |||||
public static final String NAME = "OOXMLProvider"; | |||||
private OOXMLProvider() { | |||||
super(NAME, 1.0, "OOXML Security Provider"); | |||||
put("TransformService." + RelationshipTransformService.TRANSFORM_URI, RelationshipTransformService.class.getName()); | |||||
put("TransformService." + RelationshipTransformService.TRANSFORM_URI + " MechanismType", "DOM"); | |||||
} | |||||
/** | |||||
* Installs this security provider. | |||||
*/ | |||||
public static void install() { | |||||
Provider provider = Security.getProvider(NAME); | |||||
if (null == provider) { | |||||
Security.addProvider(new OOXMLProvider()); | |||||
} | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer.ooxml; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.net.URL; | |||||
import java.security.InvalidAlgorithmParameterException; | |||||
import java.security.NoSuchAlgorithmException; | |||||
import java.util.Calendar; | |||||
import java.util.LinkedList; | |||||
import java.util.List; | |||||
import java.util.TimeZone; | |||||
import java.util.UUID; | |||||
import java.util.zip.ZipEntry; | |||||
import java.util.zip.ZipInputStream; | |||||
import javax.xml.crypto.XMLStructure; | |||||
import javax.xml.crypto.dom.DOMStructure; | |||||
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 javax.xml.parsers.DocumentBuilder; | |||||
import javax.xml.parsers.DocumentBuilderFactory; | |||||
import javax.xml.parsers.ParserConfigurationException; | |||||
import javax.xml.transform.TransformerException; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import org.apache.poi.ooxml.signature.service.signer.NoCloseInputStream; | |||||
import org.apache.poi.ooxml.signature.service.signer.SignatureAspect; | |||||
import org.apache.xml.security.utils.Constants; | |||||
import org.apache.xpath.XPathAPI; | |||||
import org.w3c.dom.Document; | |||||
import org.w3c.dom.Element; | |||||
import org.w3c.dom.Node; | |||||
import org.w3c.dom.NodeList; | |||||
import org.xml.sax.InputSource; | |||||
import org.xml.sax.SAXException; | |||||
/** | |||||
* Office OpenXML Signature Aspect implementation. | |||||
*/ | |||||
final class OOXMLSignatureAspect implements SignatureAspect { | |||||
private static final Log LOG = LogFactory.getLog(OOXMLSignatureAspect.class); | |||||
private final AbstractOOXMLSignatureService _signatureService; | |||||
public OOXMLSignatureAspect(AbstractOOXMLSignatureService signatureService) { | |||||
_signatureService = signatureService; | |||||
} | |||||
public void preSign(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<Reference> references, List<XMLObject> objects) | |||||
throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { | |||||
LOG.debug("pre sign"); | |||||
addManifestObject(signatureFactory, document, signatureId, references, objects); | |||||
addSignatureInfo(signatureFactory, document, signatureId, references, objects); | |||||
} | |||||
private void addManifestObject(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<Reference> references, | |||||
List<XMLObject> objects) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { | |||||
Manifest manifest = constructManifest(signatureFactory); | |||||
String objectId = "idPackageObject"; // really has to be this value. | |||||
List<XMLStructure> objectContent = new LinkedList<XMLStructure>(); | |||||
objectContent.add(manifest); | |||||
addSignatureTime(signatureFactory, document, signatureId, objectContent); | |||||
objects.add(signatureFactory.newXMLObject(objectContent, objectId, null, null)); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null); | |||||
Reference reference = signatureFactory.newReference("#" + objectId, digestMethod, null, "http://www.w3.org/2000/09/xmldsig#Object", null); | |||||
references.add(reference); | |||||
} | |||||
private Manifest constructManifest(XMLSignatureFactory signatureFactory) throws NoSuchAlgorithmException, | |||||
InvalidAlgorithmParameterException { | |||||
List<Reference> manifestReferences = new LinkedList<Reference>(); | |||||
try { | |||||
addRelationshipsReferences(signatureFactory, manifestReferences); | |||||
} catch (Exception e) { | |||||
throw new RuntimeException("error: " + e.getMessage(), e); | |||||
} | |||||
/* | |||||
* Word | |||||
*/ | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml", manifestReferences); | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml", manifestReferences); | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml", manifestReferences); | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml", manifestReferences); | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.theme+xml", manifestReferences); | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml", manifestReferences); | |||||
/* | |||||
* Powerpoint | |||||
*/ | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml", manifestReferences); | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml", manifestReferences); | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml", manifestReferences); | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.slide+xml", manifestReferences); | |||||
addParts(signatureFactory, "application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml", manifestReferences); | |||||
Manifest manifest = signatureFactory.newManifest(manifestReferences); | |||||
return manifest; | |||||
} | |||||
private static void addSignatureTime(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<XMLStructure> objectContent) { | |||||
/* | |||||
* SignatureTime | |||||
*/ | |||||
Element signatureTimeElement = document.createElementNS("http://schemas.openxmlformats.org/package/2006/digital-signature", "mdssi:SignatureTime"); | |||||
signatureTimeElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:mdssi", "http://schemas.openxmlformats.org/package/2006/digital-signature"); | |||||
Element formatElement = document.createElementNS("http://schemas.openxmlformats.org/package/2006/digital-signature", "mdssi:Format"); | |||||
formatElement.setTextContent("YYYY-MM-DDThh:mm:ssTZD"); | |||||
signatureTimeElement.appendChild(formatElement); | |||||
Element valueElement = document.createElementNS("http://schemas.openxmlformats.org/package/2006/digital-signature", "mdssi:Value"); | |||||
String now = formatTimestampAsISO8601(System.currentTimeMillis()); | |||||
LOG.debug("now: " + now); | |||||
valueElement.setTextContent(now); | |||||
signatureTimeElement.appendChild(valueElement); | |||||
List<XMLStructure> signatureTimeContent = new LinkedList<XMLStructure>(); | |||||
signatureTimeContent.add(new DOMStructure(signatureTimeElement)); | |||||
SignatureProperty signatureTimeSignatureProperty = signatureFactory.newSignatureProperty(signatureTimeContent, "#" + signatureId, "idSignatureTime"); | |||||
List<SignatureProperty> signaturePropertyContent = new LinkedList<SignatureProperty>(); | |||||
signaturePropertyContent.add(signatureTimeSignatureProperty); | |||||
SignatureProperties signatureProperties = signatureFactory.newSignatureProperties(signaturePropertyContent, "id-signature-time-" | |||||
+ UUID.randomUUID().toString()); | |||||
objectContent.add(signatureProperties); | |||||
} | |||||
/** | |||||
* @return text formatted "YYYY-MM-DDThh:mm:ssTZD" | |||||
*/ | |||||
static String formatTimestampAsISO8601(long ts) { | |||||
Calendar c = Calendar.getInstance(); | |||||
c.setTimeInMillis(ts); | |||||
c.setTimeZone(TimeZone.getTimeZone("UTC")); | |||||
char[] buf = "yyyy-mm-ddThh:mm:ssZ".toCharArray(); | |||||
itoa(buf, 0, 4, c.get(Calendar.YEAR)); | |||||
itoa(buf, 5, 2, c.get(Calendar.MONTH)+1); | |||||
itoa(buf, 8, 2, c.get(Calendar.DAY_OF_MONTH)); | |||||
itoa(buf, 11, 2, c.get(Calendar.HOUR_OF_DAY)); | |||||
itoa(buf, 14, 2, c.get(Calendar.MINUTE)); | |||||
itoa(buf, 17, 2, c.get(Calendar.SECOND)); | |||||
return new String(buf); | |||||
} | |||||
private static void itoa(char[] buf, int start, int len, int value) { | |||||
int acc = value; | |||||
int i=start+len-1; | |||||
while (i>=start) { | |||||
int d = acc % 10; | |||||
acc /= 10; | |||||
buf[i] = (char) ('0' + d); | |||||
i--; | |||||
} | |||||
} | |||||
private void addSignatureInfo(XMLSignatureFactory signatureFactory, Document document, String signatureId, List<Reference> references, | |||||
List<XMLObject> objects) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { | |||||
List<XMLStructure> objectContent = new LinkedList<XMLStructure>(); | |||||
Element signatureInfoElement = document.createElementNS("http://schemas.microsoft.com/office/2006/digsig", "SignatureInfoV1"); | |||||
signatureInfoElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns", "http://schemas.microsoft.com/office/2006/digsig"); | |||||
Element manifestHashAlgorithmElement = document.createElementNS("http://schemas.microsoft.com/office/2006/digsig", "ManifestHashAlgorithm"); | |||||
manifestHashAlgorithmElement.setTextContent("http://www.w3.org/2000/09/xmldsig#sha1"); | |||||
signatureInfoElement.appendChild(manifestHashAlgorithmElement); | |||||
List<XMLStructure> signatureInfoContent = new LinkedList<XMLStructure>(); | |||||
signatureInfoContent.add(new DOMStructure(signatureInfoElement)); | |||||
SignatureProperty signatureInfoSignatureProperty = signatureFactory.newSignatureProperty(signatureInfoContent, "#" + signatureId, "idOfficeV1Details"); | |||||
List<SignatureProperty> signaturePropertyContent = new LinkedList<SignatureProperty>(); | |||||
signaturePropertyContent.add(signatureInfoSignatureProperty); | |||||
SignatureProperties signatureProperties = signatureFactory.newSignatureProperties(signaturePropertyContent, null); | |||||
objectContent.add(signatureProperties); | |||||
String objectId = "idOfficeObject"; | |||||
objects.add(signatureFactory.newXMLObject(objectContent, objectId, null, null)); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null); | |||||
Reference reference = signatureFactory.newReference("#" + objectId, digestMethod, null, "http://www.w3.org/2000/09/xmldsig#Object", null); | |||||
references.add(reference); | |||||
} | |||||
private void addRelationshipsReferences(XMLSignatureFactory signatureFactory, List<Reference> manifestReferences) throws IOException, | |||||
ParserConfigurationException, SAXException, NoSuchAlgorithmException, | |||||
InvalidAlgorithmParameterException { | |||||
URL ooxmlUrl = _signatureService.getOfficeOpenXMLDocumentURL(); | |||||
InputStream inputStream = ooxmlUrl.openStream(); | |||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream); | |||||
ZipEntry zipEntry; | |||||
while (null != (zipEntry = zipInputStream.getNextEntry())) { | |||||
if (false == zipEntry.getName().endsWith(".rels")) { | |||||
continue; | |||||
} | |||||
Document relsDocument = loadDocumentNoClose(zipInputStream); | |||||
addRelationshipsReference(signatureFactory, zipEntry.getName(), relsDocument, manifestReferences); | |||||
} | |||||
} | |||||
private void addRelationshipsReference(XMLSignatureFactory signatureFactory, String zipEntryName, Document relsDocument, | |||||
List<Reference> manifestReferences) throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { | |||||
LOG.debug("relationships: " + zipEntryName); | |||||
RelationshipTransformParameterSpec parameterSpec = new RelationshipTransformParameterSpec(); | |||||
NodeList nodeList = relsDocument.getDocumentElement().getChildNodes(); | |||||
for (int nodeIdx = 0; nodeIdx < nodeList.getLength(); nodeIdx++) { | |||||
Node node = nodeList.item(nodeIdx); | |||||
if (node.getNodeType() != Node.ELEMENT_NODE) { | |||||
continue; | |||||
} | |||||
Element element = (Element) node; | |||||
String relationshipType = element.getAttribute("Type"); | |||||
/* | |||||
* We skip some relationship types. | |||||
*/ | |||||
if ("http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties".equals(relationshipType)) { | |||||
continue; | |||||
} | |||||
if ("http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties".equals(relationshipType)) { | |||||
continue; | |||||
} | |||||
if ("http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin".equals(relationshipType)) { | |||||
continue; | |||||
} | |||||
if ("http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail".equals(relationshipType)) { | |||||
continue; | |||||
} | |||||
if ("http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps".equals(relationshipType)) { | |||||
continue; | |||||
} | |||||
if ("http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps".equals(relationshipType)) { | |||||
continue; | |||||
} | |||||
String relationshipId = element.getAttribute("Id"); | |||||
parameterSpec.addRelationshipReference(relationshipId); | |||||
} | |||||
List<Transform> transforms = new LinkedList<Transform>(); | |||||
transforms.add(signatureFactory.newTransform(RelationshipTransformService.TRANSFORM_URI, parameterSpec)); | |||||
transforms.add(signatureFactory.newTransform("http://www.w3.org/TR/2001/REC-xml-c14n-20010315", (TransformParameterSpec) null)); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null); | |||||
Reference reference = signatureFactory.newReference("/" + zipEntryName + "?ContentType=application/vnd.openxmlformats-package.relationships+xml", | |||||
digestMethod, transforms, null, null); | |||||
manifestReferences.add(reference); | |||||
} | |||||
private void addParts(XMLSignatureFactory signatureFactory, String contentType, List<Reference> references) throws NoSuchAlgorithmException, | |||||
InvalidAlgorithmParameterException { | |||||
List<String> documentResourceNames; | |||||
try { | |||||
documentResourceNames = getResourceNames(_signatureService.getOfficeOpenXMLDocumentURL(), contentType); | |||||
} catch (Exception e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null); | |||||
for (String documentResourceName : documentResourceNames) { | |||||
LOG.debug("document resource: " + documentResourceName); | |||||
Reference reference = signatureFactory.newReference("/" + documentResourceName + "?ContentType=" + contentType, digestMethod); | |||||
references.add(reference); | |||||
} | |||||
} | |||||
private List<String> getResourceNames(URL url, String contentType) throws IOException, ParserConfigurationException, SAXException, TransformerException { | |||||
List<String> signatureResourceNames = new LinkedList<String>(); | |||||
if (null == url) { | |||||
throw new RuntimeException("OOXML URL is null"); | |||||
} | |||||
InputStream inputStream = url.openStream(); | |||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream); | |||||
ZipEntry zipEntry; | |||||
while (null != (zipEntry = zipInputStream.getNextEntry())) { | |||||
if (false == "[Content_Types].xml".equals(zipEntry.getName())) { | |||||
continue; | |||||
} | |||||
Document contentTypesDocument = loadDocument(zipInputStream); | |||||
Element nsElement = contentTypesDocument.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/content-types"); | |||||
NodeList nodeList = XPathAPI.selectNodeList(contentTypesDocument, "/tns:Types/tns:Override[@ContentType='" + contentType + "']/@PartName", | |||||
nsElement); | |||||
for (int nodeIdx = 0; nodeIdx < nodeList.getLength(); nodeIdx++) { | |||||
String partName = nodeList.item(nodeIdx).getTextContent(); | |||||
LOG.debug("part name: " + partName); | |||||
partName = partName.substring(1); // remove '/' | |||||
signatureResourceNames.add(partName); | |||||
} | |||||
break; | |||||
} | |||||
return signatureResourceNames; | |||||
} | |||||
protected Document loadDocument(String zipEntryName) throws IOException, ParserConfigurationException, SAXException { | |||||
Document document = findDocument(zipEntryName); | |||||
if (null != document) { | |||||
return document; | |||||
} | |||||
throw new RuntimeException("ZIP entry not found: " + zipEntryName); | |||||
} | |||||
protected Document findDocument(String zipEntryName) throws IOException, ParserConfigurationException, SAXException { | |||||
URL ooxmlUrl = _signatureService.getOfficeOpenXMLDocumentURL(); | |||||
InputStream inputStream = ooxmlUrl.openStream(); | |||||
ZipInputStream zipInputStream = new ZipInputStream(inputStream); | |||||
ZipEntry zipEntry; | |||||
while (null != (zipEntry = zipInputStream.getNextEntry())) { | |||||
if (false == zipEntryName.equals(zipEntry.getName())) { | |||||
continue; | |||||
} | |||||
Document document = loadDocument(zipInputStream); | |||||
return document; | |||||
} | |||||
return null; | |||||
} | |||||
private Document loadDocumentNoClose(InputStream documentInputStream) throws ParserConfigurationException, SAXException, IOException { | |||||
NoCloseInputStream noCloseInputStream = new NoCloseInputStream(documentInputStream); | |||||
InputSource inputSource = new InputSource(noCloseInputStream); | |||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||||
documentBuilderFactory.setNamespaceAware(true); | |||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||||
Document document = documentBuilder.parse(inputSource); | |||||
return document; | |||||
} | |||||
private 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; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer.ooxml; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.net.URL; | |||||
import java.security.cert.X509Certificate; | |||||
import java.util.LinkedList; | |||||
import java.util.List; | |||||
import java.util.zip.ZipEntry; | |||||
import java.util.zip.ZipInputStream; | |||||
import javax.xml.crypto.MarshalException; | |||||
import javax.xml.crypto.dsig.XMLSignature; | |||||
import javax.xml.crypto.dsig.XMLSignatureException; | |||||
import javax.xml.crypto.dsig.XMLSignatureFactory; | |||||
import javax.xml.crypto.dsig.dom.DOMValidateContext; | |||||
import javax.xml.parsers.DocumentBuilder; | |||||
import javax.xml.parsers.DocumentBuilderFactory; | |||||
import javax.xml.parsers.ParserConfigurationException; | |||||
import javax.xml.transform.TransformerException; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import org.apache.poi.POIXMLDocument; | |||||
import org.apache.poi.ooxml.signature.service.signer.KeyInfoKeySelector; | |||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; | |||||
import org.apache.poi.openxml4j.opc.OPCPackage; | |||||
import org.apache.poi.openxml4j.opc.PackagePart; | |||||
import org.apache.poi.openxml4j.opc.PackagePartName; | |||||
import org.apache.poi.openxml4j.opc.PackageRelationship; | |||||
import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; | |||||
import org.apache.poi.openxml4j.opc.PackageRelationshipTypes; | |||||
import org.apache.poi.openxml4j.opc.PackagingURIHelper; | |||||
import org.w3c.dom.Document; | |||||
import org.w3c.dom.Node; | |||||
import org.w3c.dom.NodeList; | |||||
import org.xml.sax.InputSource; | |||||
import org.xml.sax.SAXException; | |||||
/** | |||||
* Signature verifier util class for Office Open XML file format. | |||||
*/ | |||||
public final class OOXMLSignatureVerifier { | |||||
private static final Log LOG = LogFactory.getLog(OOXMLSignatureVerifier.class); | |||||
private OOXMLSignatureVerifier() { | |||||
// no instances of this class; | |||||
} | |||||
/** | |||||
* @return <code>true</code> if the file referred by the given URL is an OOXML document. | |||||
*/ | |||||
public static boolean isOOXML(URL url) throws IOException { | |||||
ZipInputStream zipInputStream = new ZipInputStream(url.openStream()); | |||||
ZipEntry zipEntry; | |||||
while (null != (zipEntry = zipInputStream.getNextEntry())) { | |||||
if (false == "[Content_Types].xml".equals(zipEntry.getName())) { | |||||
continue; | |||||
} | |||||
if (zipEntry.getSize() > 0) { | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
public static List<X509Certificate> getSigners(URL url) throws IOException, ParserConfigurationException, SAXException, TransformerException, | |||||
MarshalException, XMLSignatureException, InvalidFormatException { | |||||
List<X509Certificate> signers = new LinkedList<X509Certificate>(); | |||||
List<PackagePart> signatureParts = getSignatureParts(url); | |||||
if (signatureParts.isEmpty()) { | |||||
LOG.debug("no signature resources"); | |||||
} | |||||
for (PackagePart signaturePart : signatureParts) { | |||||
Document signatureDocument = loadDocument(signaturePart); | |||||
if (null == signatureDocument) { | |||||
continue; | |||||
} | |||||
NodeList signatureNodeList = signatureDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); | |||||
if (0 == signatureNodeList.getLength()) { | |||||
return null; | |||||
} | |||||
Node signatureNode = signatureNodeList.item(0); | |||||
KeyInfoKeySelector keySelector = new KeyInfoKeySelector(); | |||||
DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, signatureNode); | |||||
domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE); | |||||
OOXMLURIDereferencer dereferencer = new OOXMLURIDereferencer(url); | |||||
domValidateContext.setURIDereferencer(dereferencer); | |||||
XMLSignatureFactory xmlSignatureFactory = CryptoFactoryFactory.getSignatureFactory(); | |||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext); | |||||
boolean validity = xmlSignature.validate(domValidateContext); | |||||
if (false == validity) { | |||||
continue; | |||||
} | |||||
// TODO: check what has been signed. | |||||
X509Certificate signer = keySelector.getCertificate(); | |||||
signers.add(signer); | |||||
} | |||||
return signers; | |||||
} | |||||
public static boolean verifySignature(URL url) throws InvalidFormatException, IOException, ParserConfigurationException, SAXException, MarshalException, | |||||
XMLSignatureException { | |||||
PackagePart signaturePart = getSignaturePart(url); | |||||
if (signaturePart == null) { | |||||
LOG.info(url + " does not contain a signature"); | |||||
return false; | |||||
} | |||||
LOG.debug("signature resource name: " + signaturePart.getPartName()); | |||||
OOXMLProvider.install(); | |||||
Document signatureDocument = loadDocument(signaturePart); | |||||
LOG.debug("signature loaded"); | |||||
NodeList signatureNodeList = signatureDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); | |||||
Node signatureNode = signatureNodeList.item(0); | |||||
KeyInfoKeySelector keySelector = new KeyInfoKeySelector(); | |||||
DOMValidateContext domValidateContext = new DOMValidateContext(keySelector, signatureNode); | |||||
domValidateContext.setProperty("org.jcp.xml.dsig.validateManifests", Boolean.TRUE); | |||||
OOXMLURIDereferencer dereferencer = new OOXMLURIDereferencer(url); | |||||
domValidateContext.setURIDereferencer(dereferencer); | |||||
XMLSignatureFactory xmlSignatureFactory = CryptoFactoryFactory.getSignatureFactory(); | |||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext); | |||||
return xmlSignature.validate(domValidateContext); | |||||
} | |||||
private static PackagePart getSignaturePart(URL url) throws IOException, InvalidFormatException { | |||||
List<PackagePart> packageParts = getSignatureParts(url); | |||||
if (packageParts.isEmpty()) { | |||||
return null; | |||||
} else { | |||||
return packageParts.get(0); | |||||
} | |||||
} | |||||
private static List<PackagePart> getSignatureParts(URL url) throws IOException, InvalidFormatException { | |||||
List<PackagePart> packageParts = new LinkedList<PackagePart>(); | |||||
OPCPackage pkg = POIXMLDocument.openPackage(url.getPath()); | |||||
PackageRelationshipCollection sigOrigRels = pkg.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE_ORIGIN); | |||||
for (PackageRelationship rel : sigOrigRels) { | |||||
PackagePartName relName = PackagingURIHelper.createPartName(rel.getTargetURI()); | |||||
PackagePart sigPart = pkg.getPart(relName); | |||||
if (LOG.isDebugEnabled()) { | |||||
LOG.debug("Digital Signature Origin part = " + sigPart); | |||||
} | |||||
PackageRelationshipCollection sigRels = sigPart.getRelationshipsByType(PackageRelationshipTypes.DIGITAL_SIGNATURE); | |||||
for (PackageRelationship sigRel : sigRels) { | |||||
PackagePartName sigRelName = PackagingURIHelper.createPartName(sigRel.getTargetURI()); | |||||
PackagePart sigRelPart = pkg.getPart(sigRelName); | |||||
if (LOG.isDebugEnabled()) { | |||||
LOG.debug("XML Signature part = " + sigRelPart); | |||||
} | |||||
packageParts.add(sigRelPart); | |||||
} | |||||
} | |||||
return packageParts; | |||||
} | |||||
private static Document loadDocument(PackagePart part) throws ParserConfigurationException, SAXException, IOException { | |||||
InputStream documentInputStream = part.getInputStream(); | |||||
return loadDocument(documentInputStream); | |||||
} | |||||
private 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; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer.ooxml; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.io.UnsupportedEncodingException; | |||||
import java.net.URL; | |||||
import java.net.URLDecoder; | |||||
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.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import org.apache.poi.POIXMLDocument; | |||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException; | |||||
import org.apache.poi.openxml4j.opc.OPCPackage; | |||||
import org.apache.poi.openxml4j.opc.PackagePart; | |||||
/** | |||||
* JSR105 URI dereferencer for Office Open XML documents. | |||||
*/ | |||||
public class OOXMLURIDereferencer implements URIDereferencer { | |||||
private static final Log LOG = LogFactory.getLog(OOXMLURIDereferencer.class); | |||||
private final URL ooxmlUrl; | |||||
private final URIDereferencer baseUriDereferencer; | |||||
public OOXMLURIDereferencer(URL ooxmlUrl) { | |||||
if (null == ooxmlUrl) { | |||||
throw new IllegalArgumentException("ooxmlUrl is null"); | |||||
} | |||||
this.ooxmlUrl = ooxmlUrl; | |||||
XMLSignatureFactory xmlSignatureFactory = CryptoFactoryFactory.getSignatureFactory(); | |||||
this.baseUriDereferencer = xmlSignatureFactory.getURIDereferencer(); | |||||
} | |||||
public Data dereference(URIReference uriReference, XMLCryptoContext context) throws URIReferenceException { | |||||
if (null == uriReference) { | |||||
throw new NullPointerException("URIReference cannot be null"); | |||||
} | |||||
if (null == context) { | |||||
throw new NullPointerException("XMLCrytoContext cannot be null"); | |||||
} | |||||
String uri = uriReference.getURI(); | |||||
try { | |||||
uri = URLDecoder.decode(uri, "UTF-8"); | |||||
} catch (UnsupportedEncodingException e) { | |||||
LOG.warn("could not URL decode the uri: " + uri); | |||||
} | |||||
LOG.debug("dereference: " + uri); | |||||
try { | |||||
InputStream dataInputStream = findDataInputStream(uri); | |||||
if (null == dataInputStream) { | |||||
LOG.debug("cannot resolve, delegating to base DOM URI dereferencer: " + uri); | |||||
return this.baseUriDereferencer.dereference(uriReference, context); | |||||
} | |||||
return new OctetStreamData(dataInputStream, uri, null); | |||||
} catch (IOException e) { | |||||
throw new URIReferenceException("I/O error: " + e.getMessage(), e); | |||||
} catch (InvalidFormatException e) { | |||||
throw new URIReferenceException("Invalid format error: " + e.getMessage(), e); | |||||
} | |||||
} | |||||
private InputStream findDataInputStream(String uri) throws IOException, InvalidFormatException { | |||||
if (-1 != uri.indexOf("?")) { | |||||
uri = uri.substring(0, uri.indexOf("?")); | |||||
} | |||||
OPCPackage pkg = POIXMLDocument.openPackage(this.ooxmlUrl.getPath()); | |||||
for (PackagePart part : pkg.getParts()) { | |||||
if (uri.equals(part.getPartName().getURI().toString())) { | |||||
LOG.debug("Part name: " + part.getPartName()); | |||||
return part.getInputStream(); | |||||
} | |||||
} | |||||
LOG.debug("No part found for URI: " + uri); | |||||
return null; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer.ooxml; | |||||
import java.util.Comparator; | |||||
import org.w3c.dom.Element; | |||||
/** | |||||
* Comparator for Relationship DOM elements. | |||||
*/ | |||||
public class RelationshipComparator implements Comparator<Element> { | |||||
public int compare(Element element1, Element element2) { | |||||
String id1 = element1.getAttribute("Id"); | |||||
String id2 = element2.getAttribute("Id"); | |||||
return id1.compareTo(id2); | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer.ooxml; | |||||
import java.util.LinkedList; | |||||
import java.util.List; | |||||
import javax.xml.crypto.dsig.spec.TransformParameterSpec; | |||||
/** | |||||
* Relationship Transform parameter specification class. | |||||
*/ | |||||
public class RelationshipTransformParameterSpec implements TransformParameterSpec { | |||||
private final List<String> sourceIds; | |||||
/** | |||||
* Main constructor. | |||||
*/ | |||||
public RelationshipTransformParameterSpec() { | |||||
this.sourceIds = new LinkedList<String>(); | |||||
} | |||||
/** | |||||
* Adds a relationship reference for the given source identifier. | |||||
* | |||||
* @param sourceId | |||||
*/ | |||||
public void addRelationshipReference(String sourceId) { | |||||
this.sourceIds.add(sourceId); | |||||
} | |||||
List<String> getSourceIds() { | |||||
return this.sourceIds; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer.ooxml; | |||||
import java.io.ByteArrayInputStream; | |||||
import java.io.ByteArrayOutputStream; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.io.OutputStream; | |||||
import java.io.StringWriter; | |||||
import java.security.InvalidAlgorithmParameterException; | |||||
import java.security.spec.AlgorithmParameterSpec; | |||||
import java.util.Collections; | |||||
import java.util.LinkedList; | |||||
import java.util.List; | |||||
import javax.xml.crypto.Data; | |||||
import javax.xml.crypto.MarshalException; | |||||
import javax.xml.crypto.OctetStreamData; | |||||
import javax.xml.crypto.XMLCryptoContext; | |||||
import javax.xml.crypto.XMLStructure; | |||||
import javax.xml.crypto.dom.DOMStructure; | |||||
import javax.xml.crypto.dsig.TransformException; | |||||
import javax.xml.crypto.dsig.TransformService; | |||||
import javax.xml.crypto.dsig.spec.TransformParameterSpec; | |||||
import javax.xml.parsers.DocumentBuilder; | |||||
import javax.xml.parsers.DocumentBuilderFactory; | |||||
import javax.xml.parsers.ParserConfigurationException; | |||||
import javax.xml.transform.OutputKeys; | |||||
import javax.xml.transform.Result; | |||||
import javax.xml.transform.Source; | |||||
import javax.xml.transform.Transformer; | |||||
import javax.xml.transform.TransformerException; | |||||
import javax.xml.transform.TransformerFactory; | |||||
import javax.xml.transform.dom.DOMSource; | |||||
import javax.xml.transform.stream.StreamResult; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import org.apache.xml.security.utils.Constants; | |||||
import org.apache.xpath.XPathAPI; | |||||
import org.w3c.dom.Document; | |||||
import org.w3c.dom.Element; | |||||
import org.w3c.dom.Node; | |||||
import org.w3c.dom.NodeList; | |||||
import org.xml.sax.InputSource; | |||||
import org.xml.sax.SAXException; | |||||
/** | |||||
* 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 Log LOG = LogFactory.getLog(RelationshipTransformService.class); | |||||
public RelationshipTransformService() { | |||||
super(); | |||||
LOG.debug("constructor"); | |||||
this.sourceIds = new LinkedList<String>(); | |||||
} | |||||
@Override | |||||
public void init(TransformParameterSpec params) throws InvalidAlgorithmParameterException { | |||||
LOG.debug("init(params)"); | |||||
if (false == params instanceof RelationshipTransformParameterSpec) { | |||||
throw new InvalidAlgorithmParameterException(); | |||||
} | |||||
RelationshipTransformParameterSpec relParams = (RelationshipTransformParameterSpec) params; | |||||
for (String sourceId : relParams.getSourceIds()) { | |||||
this.sourceIds.add(sourceId); | |||||
} | |||||
} | |||||
@Override | |||||
public void init(XMLStructure parent, XMLCryptoContext context) throws InvalidAlgorithmParameterException { | |||||
LOG.debug("init(parent,context)"); | |||||
LOG.debug("parent java type: " + parent.getClass().getName()); | |||||
DOMStructure domParent = (DOMStructure) parent; | |||||
Node parentNode = domParent.getNode(); | |||||
try { | |||||
LOG.debug("parent: " + toString(parentNode)); | |||||
} catch (TransformerException e) { | |||||
throw new InvalidAlgorithmParameterException(); | |||||
} | |||||
Element nsElement = parentNode.getOwnerDocument().createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:mdssi", "http://schemas.openxmlformats.org/package/2006/digital-signature"); | |||||
NodeList nodeList; | |||||
try { | |||||
nodeList = XPathAPI.selectNodeList(parentNode, "mdssi:RelationshipReference/@SourceId", nsElement); | |||||
} catch (TransformerException e) { | |||||
LOG.error("transformer exception: " + e.getMessage(), e); | |||||
throw new InvalidAlgorithmParameterException(); | |||||
} | |||||
if (0 == nodeList.getLength()) { | |||||
LOG.warn("no RelationshipReference/@SourceId parameters present"); | |||||
} | |||||
for (int nodeIdx = 0; nodeIdx < nodeList.getLength(); nodeIdx++) { | |||||
Node node = nodeList.item(nodeIdx); | |||||
String sourceId = node.getTextContent(); | |||||
LOG.debug("sourceId: " + sourceId); | |||||
this.sourceIds.add(sourceId); | |||||
} | |||||
} | |||||
@Override | |||||
public void marshalParams(XMLStructure parent, XMLCryptoContext context) throws MarshalException { | |||||
LOG.debug("marshallParams(parent,context)"); | |||||
DOMStructure domParent = (DOMStructure) parent; | |||||
Node parentNode = domParent.getNode(); | |||||
Element parentElement = (Element) parentNode; | |||||
parentElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:mdssi", "http://schemas.openxmlformats.org/package/2006/digital-signature"); | |||||
Document document = parentNode.getOwnerDocument(); | |||||
for (String sourceId : this.sourceIds) { | |||||
Element relationshipReferenceElement = document.createElementNS("http://schemas.openxmlformats.org/package/2006/digital-signature", | |||||
"mdssi:RelationshipReference"); | |||||
relationshipReferenceElement.setAttribute("SourceId", sourceId); | |||||
parentElement.appendChild(relationshipReferenceElement); | |||||
} | |||||
} | |||||
public AlgorithmParameterSpec getParameterSpec() { | |||||
LOG.debug("getParameterSpec"); | |||||
return null; | |||||
} | |||||
public Data transform(Data data, XMLCryptoContext context) throws TransformException { | |||||
LOG.debug("transform(data,context)"); | |||||
LOG.debug("data java type: " + data.getClass().getName()); | |||||
OctetStreamData octetStreamData = (OctetStreamData) data; | |||||
LOG.debug("URI: " + octetStreamData.getURI()); | |||||
InputStream octetStream = octetStreamData.getOctetStream(); | |||||
Document relationshipsDocument; | |||||
try { | |||||
relationshipsDocument = loadDocument(octetStream); | |||||
} catch (Exception e) { | |||||
throw new TransformException(e.getMessage(), e); | |||||
} | |||||
try { | |||||
LOG.debug("relationships document: " + toString(relationshipsDocument)); | |||||
} catch (TransformerException e) { | |||||
throw new TransformException(e.getMessage(), e); | |||||
} | |||||
Element nsElement = relationshipsDocument.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "http://schemas.openxmlformats.org/package/2006/relationships"); | |||||
Element relationshipsElement = relationshipsDocument.getDocumentElement(); | |||||
NodeList childNodes = relationshipsElement.getChildNodes(); | |||||
for (int nodeIdx = 0; nodeIdx < childNodes.getLength(); nodeIdx++) { | |||||
Node childNode = childNodes.item(nodeIdx); | |||||
if (Node.ELEMENT_NODE != childNode.getNodeType()) { | |||||
LOG.debug("removing node"); | |||||
relationshipsElement.removeChild(childNode); | |||||
nodeIdx--; | |||||
continue; | |||||
} | |||||
Element childElement = (Element) childNode; | |||||
String idAttribute = childElement.getAttribute("Id"); | |||||
LOG.debug("Relationship id attribute: " + idAttribute); | |||||
if (false == this.sourceIds.contains(idAttribute)) { | |||||
LOG.debug("removing element: " + idAttribute); | |||||
relationshipsElement.removeChild(childNode); | |||||
nodeIdx--; | |||||
} | |||||
/* | |||||
* See: ISO/IEC 29500-2:2008(E) - 13.2.4.24 Relationships Transform | |||||
* Algorithm. | |||||
*/ | |||||
if (null == childElement.getAttributeNode("TargetMode")) { | |||||
childElement.setAttribute("TargetMode", "Internal"); | |||||
} | |||||
} | |||||
LOG.debug("# Relationship elements: " + relationshipsElement.getElementsByTagName("*").getLength()); | |||||
sortRelationshipElements(relationshipsElement); | |||||
try { | |||||
return toOctetStreamData(relationshipsDocument); | |||||
} catch (TransformerException e) { | |||||
throw new TransformException(e.getMessage(), e); | |||||
} | |||||
} | |||||
private void sortRelationshipElements(Element relationshipsElement) { | |||||
List<Element> relationshipElements = new LinkedList<Element>(); | |||||
NodeList relationshipNodes = relationshipsElement.getElementsByTagName("*"); | |||||
int nodeCount = relationshipNodes.getLength(); | |||||
for (int nodeIdx = 0; nodeIdx < nodeCount; nodeIdx++) { | |||||
Node relationshipNode = relationshipNodes.item(0); | |||||
Element relationshipElement = (Element) relationshipNode; | |||||
LOG.debug("unsorted Id: " + relationshipElement.getAttribute("Id")); | |||||
relationshipElements.add(relationshipElement); | |||||
relationshipsElement.removeChild(relationshipNode); | |||||
} | |||||
Collections.sort(relationshipElements, new RelationshipComparator()); | |||||
for (Element relationshipElement : relationshipElements) { | |||||
LOG.debug("sorted Id: " + relationshipElement.getAttribute("Id")); | |||||
relationshipsElement.appendChild(relationshipElement); | |||||
} | |||||
} | |||||
private 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(); | |||||
} | |||||
private OctetStreamData toOctetStreamData(Node node) throws TransformerException { | |||||
Source source = new DOMSource(node); | |||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |||||
Result result = new StreamResult(outputStream); | |||||
TransformerFactory transformerFactory = TransformerFactory.newInstance(); | |||||
Transformer transformer = transformerFactory.newTransformer(); | |||||
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); | |||||
transformer.transform(source, result); | |||||
LOG.debug("result: " + new String(outputStream.toByteArray())); | |||||
return new OctetStreamData(new ByteArrayInputStream(outputStream.toByteArray())); | |||||
} | |||||
private 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; | |||||
} | |||||
public Data transform(Data data, XMLCryptoContext context, OutputStream os) throws TransformException { | |||||
LOG.debug("transform(data,context,os)"); | |||||
return null; | |||||
} | |||||
public boolean isFeatureSupported(String feature) { | |||||
LOG.debug("isFeatureSupported(feature)"); | |||||
return false; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
/** | |||||
* This package contains implementation classes for the Office Open XML Signature Service. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer.ooxml; | |||||
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
/** | |||||
* This package contains implementation classes for the Signature Service SPI. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.spi; | |||||
import java.security.cert.X509Certificate; | |||||
import java.util.List; | |||||
/** | |||||
* Interface for authentication service components. | |||||
*/ | |||||
public interface AuthenticationService { | |||||
/** | |||||
* Validates the given certificate chain. After the client has | |||||
* verified the authentication signature, it will invoke this method on your | |||||
* authentication service component. The implementation of this method | |||||
* should validate the given certificate chain. This validation could be | |||||
* based on PKI validation, or could be based on simply trusting the | |||||
* incoming public key. The actual implementation is very dependent on your | |||||
* type of application. This method should only be used for certificate | |||||
* validation. | |||||
* | |||||
* <p> | |||||
* Check out <a href="http://code.google.com/p/jtrust/">jTrust</a> for an | |||||
* implementation of a PKI validation framework. | |||||
* </p> | |||||
* | |||||
* @param certificateChain | |||||
* the X509 authentication certificate chain of the citizen. | |||||
* @throws SecurityException | |||||
* in case the certificate chain is invalid/not accepted. | |||||
*/ | |||||
void validateCertificateChain(List<X509Certificate> certificateChain) throws SecurityException; | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.spi; | |||||
import java.io.Serializable; | |||||
/** | |||||
* Digest Information data transfer class. | |||||
*/ | |||||
public class DigestInfo implements Serializable { | |||||
private static final long serialVersionUID = 1L; | |||||
/** | |||||
* Main constructor. | |||||
* | |||||
* @param digestValue | |||||
* @param digestAlgo | |||||
* @param description | |||||
*/ | |||||
public DigestInfo(byte[] digestValue, String digestAlgo, String description) { | |||||
this.digestValue = digestValue; | |||||
this.digestAlgo = digestAlgo; | |||||
this.description = description; | |||||
} | |||||
public final byte[] digestValue; | |||||
public final String description; | |||||
public final String digestAlgo; | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.spi; | |||||
/** | |||||
* Insecure Client Environment Exception. | |||||
*/ | |||||
public class InsecureClientEnvironmentException extends Exception { | |||||
private static final long serialVersionUID = 1L; | |||||
private final boolean warnOnly; | |||||
/** | |||||
* Default constructor. | |||||
*/ | |||||
public InsecureClientEnvironmentException() { | |||||
this(false); | |||||
} | |||||
/** | |||||
* Main constructor. | |||||
* | |||||
* @param warnOnly | |||||
* only makes that the citizen is warned about a possible | |||||
* insecure enviroment. | |||||
*/ | |||||
public InsecureClientEnvironmentException(boolean warnOnly) { | |||||
this.warnOnly = warnOnly; | |||||
} | |||||
/** | |||||
* If set the eID Applet will only give a warning on case the server-side | |||||
* marks the client environment as being insecure. Else the eID Applet will | |||||
* abort the requested eID operation. | |||||
* | |||||
* @return | |||||
*/ | |||||
public boolean isWarnOnly() { | |||||
return this.warnOnly; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.spi; | |||||
import java.util.List; | |||||
/** | |||||
* Interface for security environment service components. Can be used by the eID | |||||
* Applet Service to check the client environment security requirements. | |||||
*/ | |||||
public interface SecureClientEnvironmentService { | |||||
/** | |||||
* Checks whether the client environment is secure enough for this web | |||||
* application. | |||||
* | |||||
* @param javaVersion | |||||
* the version of the Java JRE on the client machine. | |||||
* @param javaVendor | |||||
* the vendor of the Java JRE on the client machine. | |||||
* @param osName | |||||
* the name of the operating system on the client machine. | |||||
* @param osArch | |||||
* the architecture of the client machine. | |||||
* @param osVersion | |||||
* the operating system version of the client machine. | |||||
* @param userAgent | |||||
* the user agent, i.e. browser, used on the client machine. | |||||
* @param navigatorAppName | |||||
* the optional navigator application name (browser) | |||||
* @param navigatorAppVersion | |||||
* the optional navigator application version (browser version) | |||||
* @param navigatorUserAgent | |||||
* the optional optional navigator user agent name. | |||||
* @param remoteAddress | |||||
* the address of the client machine. | |||||
* @param sslKeySize | |||||
* the key size of the SSL session used between server and | |||||
* client. | |||||
* @param sslCipherSuite | |||||
* the cipher suite of the SSL session used between server and | |||||
* client. | |||||
* @param readerList | |||||
* the list of smart card readers present on the client machine. | |||||
* @throws InsecureClientEnvironmentException | |||||
* if the client env is found not to be secure enough. | |||||
*/ | |||||
void checkSecureClientEnvironment(String javaVersion, String javaVendor, String osName, String osArch, String osVersion, String userAgent, | |||||
String navigatorAppName, String navigatorAppVersion, String navigatorUserAgent, String remoteAddress, int sslKeySize, | |||||
String sslCipherSuite, List<String> readerList) throws InsecureClientEnvironmentException; | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.spi; | |||||
import java.security.NoSuchAlgorithmException; | |||||
import java.security.cert.X509Certificate; | |||||
import java.util.List; | |||||
/** | |||||
* Interface for signature service component. | |||||
*/ | |||||
public interface SignatureService { | |||||
/** | |||||
* Gives back the digest algorithm to be used for construction of the digest | |||||
* infos of the preSign method. Return a digest algorithm here if you want | |||||
* to let the client sign some locally stored files. Return | |||||
* <code>null</code> if no pre-sign digest infos are required. | |||||
* | |||||
* @return | |||||
* @see #preSign(List, List) | |||||
*/ | |||||
String getFilesDigestAlgorithm(); | |||||
/** | |||||
* Pre-sign callback method. Depending on the configuration some parameters | |||||
* are passed. The returned value will be signed by the eID Applet. | |||||
* | |||||
* <p> | |||||
* TODO: service must be able to throw some exception on failure. | |||||
* </p> | |||||
* | |||||
* @param digestInfos | |||||
* the optional list of digest infos. | |||||
* @param signingCertificateChain | |||||
* the optional list of certificates. | |||||
* @return the digest to be signed. | |||||
* @throws NoSuchAlgorithmException | |||||
*/ | |||||
DigestInfo preSign(List<DigestInfo> digestInfos, List<X509Certificate> signingCertificateChain) throws NoSuchAlgorithmException; | |||||
/** | |||||
* Post-sign callback method. Received the signature value. Depending on the | |||||
* configuration the signing certificate chain is also obtained. | |||||
* | |||||
* <p> | |||||
* TODO: service must be able to throw some exception on failure. | |||||
* </p> | |||||
* | |||||
* @param signatureValue | |||||
* @param signingCertificateChain | |||||
* the optional chain of signing certificates. | |||||
*/ | |||||
void postSign(byte[] signatureValue, List<X509Certificate> signingCertificateChain); | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
/** | |||||
* This package contains the service provider interfaces. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.spi; | |||||
/* ==================================================================== | |||||
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.ooxml.signature.service.signer; | |||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.TestOOXMLSignatureAspect; | |||||
import junit.framework.Test; | |||||
import junit.framework.TestSuite; | |||||
public final class AllOOXMLSignatureTests { | |||||
public static Test suite() { | |||||
TestSuite result = new TestSuite(AllOOXMLSignatureTests.class.getName()); | |||||
result.addTestSuite(TestAbstractOOXMLSignatureService.class); | |||||
result.addTestSuite(TestAbstractXmlSignatureService.class); | |||||
result.addTestSuite(TestOOXMLSignatureAspect.class); | |||||
result.addTestSuite(TestOOXMLSignatureVerifier.class); | |||||
return result; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.io.ByteArrayInputStream; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.io.StringWriter; | |||||
import java.math.BigInteger; | |||||
import java.security.InvalidKeyException; | |||||
import java.security.KeyPair; | |||||
import java.security.KeyPairGenerator; | |||||
import java.security.NoSuchAlgorithmException; | |||||
import java.security.PrivateKey; | |||||
import java.security.PublicKey; | |||||
import java.security.SecureRandom; | |||||
import java.security.SignatureException; | |||||
import java.security.cert.CertificateException; | |||||
import java.security.cert.CertificateFactory; | |||||
import java.security.cert.X509Certificate; | |||||
import java.security.spec.RSAKeyGenParameterSpec; | |||||
import java.util.Calendar; | |||||
import java.util.Date; | |||||
import javax.xml.parsers.DocumentBuilder; | |||||
import javax.xml.parsers.DocumentBuilderFactory; | |||||
import javax.xml.parsers.ParserConfigurationException; | |||||
import javax.xml.transform.OutputKeys; | |||||
import javax.xml.transform.Result; | |||||
import javax.xml.transform.Source; | |||||
import javax.xml.transform.Transformer; | |||||
import javax.xml.transform.TransformerException; | |||||
import javax.xml.transform.TransformerFactory; | |||||
import javax.xml.transform.dom.DOMSource; | |||||
import javax.xml.transform.stream.StreamResult; | |||||
import org.apache.poi.util.HexRead; | |||||
import org.bouncycastle.asn1.ASN1InputStream; | |||||
import org.bouncycastle.asn1.ASN1Sequence; | |||||
import org.bouncycastle.asn1.DERIA5String; | |||||
import org.bouncycastle.asn1.DERSequence; | |||||
import org.bouncycastle.asn1.x509.AuthorityInformationAccess; | |||||
import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; | |||||
import org.bouncycastle.asn1.x509.BasicConstraints; | |||||
import org.bouncycastle.asn1.x509.DistributionPoint; | |||||
import org.bouncycastle.asn1.x509.DistributionPointName; | |||||
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.X509Extensions; | |||||
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers; | |||||
import org.bouncycastle.jce.X509Principal; | |||||
import org.bouncycastle.x509.X509V3CertificateGenerator; | |||||
import org.w3c.dom.Document; | |||||
import org.w3c.dom.Node; | |||||
import org.xml.sax.InputSource; | |||||
import org.xml.sax.SAXException; | |||||
final class PkiTestUtils { | |||||
public static final byte[] SHA1_DIGEST_INFO_PREFIX = | |||||
HexRead.readFromString( "30 1f 30 07 06 05 2b 0e 03 02 1a 04 14"); | |||||
private PkiTestUtils() { | |||||
// no instances of this class | |||||
} | |||||
static KeyPair generateKeyPair() throws Exception { | |||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); | |||||
SecureRandom random = new SecureRandom(); | |||||
keyPairGenerator.initialize(new RSAKeyGenParameterSpec(1024, RSAKeyGenParameterSpec.F4), random); | |||||
KeyPair keyPair = keyPairGenerator.generateKeyPair(); | |||||
return keyPair; | |||||
} | |||||
private static SubjectKeyIdentifier createSubjectKeyId(PublicKey publicKey) throws IOException { | |||||
ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded()); | |||||
SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bais).readObject()); | |||||
return new SubjectKeyIdentifier(info); | |||||
} | |||||
private static AuthorityKeyIdentifier createAuthorityKeyId(PublicKey publicKey) throws IOException { | |||||
ByteArrayInputStream bais = new ByteArrayInputStream(publicKey.getEncoded()); | |||||
SubjectPublicKeyInfo info = new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bais).readObject()); | |||||
return new AuthorityKeyIdentifier(info); | |||||
} | |||||
public static X509Certificate generateCertificate(PublicKey subjectPublicKey, String subjectDn, | |||||
X509Certificate issuerCertificate, PrivateKey issuerPrivateKey, boolean caFlag, int pathLength, String crlUri, | |||||
String ocspUri, KeyUsage keyUsage) throws IOException, InvalidKeyException, IllegalStateException, | |||||
NoSuchAlgorithmException, SignatureException, CertificateException { | |||||
Date notBefore = makeDate(2010, 1, 1); | |||||
Date notAfter = makeDate(2011, 1, 1); | |||||
String signatureAlgorithm = "SHA1withRSA"; | |||||
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator(); | |||||
certificateGenerator.reset(); | |||||
certificateGenerator.setPublicKey(subjectPublicKey); | |||||
certificateGenerator.setSignatureAlgorithm(signatureAlgorithm); | |||||
certificateGenerator.setNotBefore(notBefore); | |||||
certificateGenerator.setNotAfter(notAfter); | |||||
X509Principal issuerDN; | |||||
if (null != issuerCertificate) { | |||||
issuerDN = new X509Principal(issuerCertificate.getSubjectX500Principal().toString()); | |||||
} else { | |||||
issuerDN = new X509Principal(subjectDn); | |||||
} | |||||
certificateGenerator.setIssuerDN(issuerDN); | |||||
certificateGenerator.setSubjectDN(new X509Principal(subjectDn)); | |||||
certificateGenerator.setSerialNumber(new BigInteger(128, new SecureRandom())); | |||||
certificateGenerator.addExtension(X509Extensions.SubjectKeyIdentifier, false, createSubjectKeyId(subjectPublicKey)); | |||||
PublicKey issuerPublicKey; | |||||
issuerPublicKey = subjectPublicKey; | |||||
certificateGenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, createAuthorityKeyId(issuerPublicKey)); | |||||
if (caFlag) { | |||||
if (-1 == pathLength) { | |||||
certificateGenerator.addExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(true)); | |||||
} else { | |||||
certificateGenerator.addExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(pathLength)); | |||||
} | |||||
} | |||||
if (null != crlUri) { | |||||
GeneralName gn = new GeneralName(GeneralName.uniformResourceIdentifier, new DERIA5String(crlUri)); | |||||
GeneralNames gns = new GeneralNames(new DERSequence(gn)); | |||||
DistributionPointName dpn = new DistributionPointName(0, gns); | |||||
DistributionPoint distp = new DistributionPoint(dpn, null, null); | |||||
certificateGenerator.addExtension(X509Extensions.CRLDistributionPoints, false, new DERSequence(distp)); | |||||
} | |||||
if (null != ocspUri) { | |||||
GeneralName ocspName = new GeneralName(GeneralName.uniformResourceIdentifier, ocspUri); | |||||
AuthorityInformationAccess authorityInformationAccess = new AuthorityInformationAccess(X509ObjectIdentifiers.ocspAccessMethod, ocspName); | |||||
certificateGenerator.addExtension(X509Extensions.AuthorityInfoAccess.getId(), false, authorityInformationAccess); | |||||
} | |||||
if (null != keyUsage) { | |||||
certificateGenerator.addExtension(X509Extensions.KeyUsage, true, keyUsage); | |||||
} | |||||
X509Certificate certificate; | |||||
certificate = certificateGenerator.generate(issuerPrivateKey); | |||||
/* | |||||
* Next certificate factory trick is needed to make sure that the | |||||
* certificate delivered to the caller is provided by the default | |||||
* security provider instead of BouncyCastle. If we don't do this trick | |||||
* we might run into trouble when trying to use the CertPath validator. | |||||
*/ | |||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); | |||||
certificate = (X509Certificate) certificateFactory.generateCertificate(new ByteArrayInputStream(certificate.getEncoded())); | |||||
return certificate; | |||||
} | |||||
private static Date makeDate(int year, int month, int day) { | |||||
Calendar c = Calendar.getInstance(); | |||||
c.set(year, month, day, 0, 0, 0); | |||||
c.set(Calendar.MILLISECOND, 0); | |||||
return c.getTime(); | |||||
} | |||||
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(); | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.io.ByteArrayInputStream; | |||||
import java.io.ByteArrayOutputStream; | |||||
import java.io.InputStream; | |||||
import java.io.OutputStream; | |||||
import java.io.Serializable; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import org.apache.poi.ooxml.signature.service.signer.TemporaryDataStorage; | |||||
class TemporaryTestDataStorage implements TemporaryDataStorage { | |||||
private ByteArrayOutputStream outputStream; | |||||
private Map<String, Serializable> attributes; | |||||
public TemporaryTestDataStorage() { | |||||
this.outputStream = new ByteArrayOutputStream(); | |||||
this.attributes = new HashMap<String, Serializable>(); | |||||
} | |||||
public InputStream getTempInputStream() { | |||||
byte[] data = this.outputStream.toByteArray(); | |||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(data); | |||||
return inputStream; | |||||
} | |||||
public OutputStream getTempOutputStream() { | |||||
return this.outputStream; | |||||
} | |||||
public Serializable getAttribute(String attributeName) { | |||||
return this.attributes.get(attributeName); | |||||
} | |||||
public void setAttribute(String attributeName, Serializable attributeValue) { | |||||
this.attributes.put(attributeName, attributeValue); | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.io.ByteArrayOutputStream; | |||||
import java.io.File; | |||||
import java.io.OutputStream; | |||||
import java.net.URL; | |||||
import java.security.KeyPair; | |||||
import java.security.cert.X509Certificate; | |||||
import java.util.Collections; | |||||
import java.util.List; | |||||
import javax.crypto.Cipher; | |||||
import junit.framework.TestCase; | |||||
import org.apache.commons.io.FileUtils; | |||||
import org.apache.commons.io.FilenameUtils; | |||||
import org.apache.commons.io.IOUtils; | |||||
import org.apache.commons.lang.ArrayUtils; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.AbstractOOXMLSignatureService; | |||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLProvider; | |||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLSignatureVerifier; | |||||
import org.apache.poi.ooxml.signature.service.spi.DigestInfo; | |||||
import org.bouncycastle.asn1.x509.KeyUsage; | |||||
public class TestAbstractOOXMLSignatureService extends TestCase { | |||||
private static final Log LOG = LogFactory.getLog(TestAbstractOOXMLSignatureService.class); | |||||
static { | |||||
OOXMLProvider.install(); | |||||
} | |||||
private static class OOXMLTestSignatureService extends AbstractOOXMLSignatureService { | |||||
private final URL _ooxmlUrl; | |||||
private final TemporaryTestDataStorage _temporaryDataStorage; | |||||
private final ByteArrayOutputStream _signedOOXMLOutputStream; | |||||
public OOXMLTestSignatureService(URL ooxmlUrl) { | |||||
_temporaryDataStorage = new TemporaryTestDataStorage(); | |||||
_signedOOXMLOutputStream = new ByteArrayOutputStream(); | |||||
_ooxmlUrl = ooxmlUrl; | |||||
} | |||||
@Override | |||||
protected URL getOfficeOpenXMLDocumentURL() { | |||||
return _ooxmlUrl; | |||||
} | |||||
@Override | |||||
protected OutputStream getSignedOfficeOpenXMLDocumentOutputStream() { | |||||
return _signedOOXMLOutputStream; | |||||
} | |||||
public byte[] getSignedOfficeOpenXMLDocumentData() { | |||||
return _signedOOXMLOutputStream.toByteArray(); | |||||
} | |||||
@Override | |||||
protected TemporaryDataStorage getTemporaryDataStorage() { | |||||
return _temporaryDataStorage; | |||||
} | |||||
} | |||||
public void testPreSign() throws Exception { | |||||
// setup | |||||
URL ooxmlUrl = TestAbstractOOXMLSignatureService.class.getResource("/hello-world-unsigned.docx"); | |||||
assertNotNull(ooxmlUrl); | |||||
OOXMLTestSignatureService signatureService = new OOXMLTestSignatureService(ooxmlUrl); | |||||
// operate | |||||
DigestInfo digestInfo = signatureService.preSign(null, null); | |||||
// verify | |||||
assertNotNull(digestInfo); | |||||
LOG.debug("digest algo: " + digestInfo.digestAlgo); | |||||
LOG.debug("digest description: " + digestInfo.description); | |||||
assertEquals("Office OpenXML Document", digestInfo.description); | |||||
assertNotNull(digestInfo.digestAlgo); | |||||
assertNotNull(digestInfo.digestValue); | |||||
TemporaryDataStorage temporaryDataStorage = signatureService.getTemporaryDataStorage(); | |||||
String preSignResult = IOUtils.toString(temporaryDataStorage.getTempInputStream()); | |||||
LOG.debug("pre-sign result: " + preSignResult); | |||||
File tmpFile = File.createTempFile("ooxml-pre-sign-", ".xml"); | |||||
FileUtils.writeStringToFile(tmpFile, preSignResult); | |||||
LOG.debug("tmp pre-sign file: " + tmpFile.getAbsolutePath()); | |||||
} | |||||
public void testPostSign() throws Exception { | |||||
sign("/hello-world-unsigned.docx"); | |||||
} | |||||
public void testSignOffice2010() throws Exception { | |||||
sign("/hello-world-office-2010-technical-preview-unsigned.docx"); | |||||
} | |||||
public void testSignTwice() throws Exception { | |||||
sign("/hello-world-signed.docx", 2); | |||||
} | |||||
public void testSignTwiceHere() throws Exception { | |||||
File tmpFile = sign("/hello-world-unsigned.docx", 1); | |||||
sign(tmpFile.toURI().toURL(), "CN=Test2", 2); | |||||
} | |||||
public void testSignPowerpoint() throws Exception { | |||||
sign("/hello-world-unsigned.pptx"); | |||||
} | |||||
public void testSignSpreadsheet() throws Exception { | |||||
sign("/hello-world-unsigned.xlsx"); | |||||
} | |||||
private void sign(String documentResourceName) throws Exception { | |||||
sign(documentResourceName, 1); | |||||
} | |||||
private File sign(String documentResourceName, int signerCount) throws Exception { | |||||
URL ooxmlUrl = TestAbstractOOXMLSignatureService.class.getResource(documentResourceName); | |||||
return sign(ooxmlUrl, signerCount); | |||||
} | |||||
private File sign(URL ooxmlUrl, int signerCount) throws Exception { | |||||
return sign(ooxmlUrl, "CN=Test", signerCount); | |||||
} | |||||
private File sign(URL ooxmlUrl, String signerDn, int signerCount) throws Exception { | |||||
// setup | |||||
assertNotNull(ooxmlUrl); | |||||
OOXMLTestSignatureService signatureService = new OOXMLTestSignatureService(ooxmlUrl); | |||||
// operate | |||||
DigestInfo digestInfo = signatureService.preSign(null, null); | |||||
// verify | |||||
assertNotNull(digestInfo); | |||||
LOG.debug("digest algo: " + digestInfo.digestAlgo); | |||||
LOG.debug("digest description: " + digestInfo.description); | |||||
assertEquals("Office OpenXML Document", digestInfo.description); | |||||
assertNotNull(digestInfo.digestAlgo); | |||||
assertNotNull(digestInfo.digestValue); | |||||
TemporaryDataStorage temporaryDataStorage = signatureService.getTemporaryDataStorage(); | |||||
String preSignResult = IOUtils.toString(temporaryDataStorage.getTempInputStream()); | |||||
LOG.debug("pre-sign result: " + preSignResult); | |||||
File tmpFile = File.createTempFile("ooxml-pre-sign-", ".xml"); | |||||
FileUtils.writeStringToFile(tmpFile, preSignResult); | |||||
LOG.debug("tmp pre-sign file: " + tmpFile.getAbsolutePath()); | |||||
// setup: key material, signature value | |||||
KeyPair keyPair = PkiTestUtils.generateKeyPair(); | |||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); | |||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); | |||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue); | |||||
byte[] signatureValue = cipher.doFinal(digestInfoValue); | |||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), signerDn, null, keyPair.getPrivate(), true, 0, | |||||
null, null, new KeyUsage(KeyUsage.nonRepudiation)); | |||||
// operate: postSign | |||||
signatureService.postSign(signatureValue, Collections.singletonList(certificate)); | |||||
// verify: signature | |||||
byte[] signedOOXMLData = signatureService.getSignedOfficeOpenXMLDocumentData(); | |||||
assertNotNull(signedOOXMLData); | |||||
LOG.debug("signed OOXML size: " + signedOOXMLData.length); | |||||
String extension = FilenameUtils.getExtension(ooxmlUrl.getFile()); | |||||
tmpFile = File.createTempFile("ooxml-signed-", "." + extension); | |||||
FileUtils.writeByteArrayToFile(tmpFile, signedOOXMLData); | |||||
LOG.debug("signed OOXML file: " + tmpFile.getAbsolutePath()); | |||||
List<X509Certificate> signers = OOXMLSignatureVerifier.getSigners(tmpFile.toURI().toURL()); | |||||
assertEquals(signerCount, signers.size()); | |||||
// assertEquals(certificate, signers.get(0)); | |||||
LOG.debug("signed OOXML file: " + tmpFile.getAbsolutePath()); | |||||
return tmpFile; | |||||
} | |||||
} |
/* ==================================================================== | |||||
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. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.io.ByteArrayInputStream; | |||||
import java.io.ByteArrayOutputStream; | |||||
import java.io.InputStream; | |||||
import java.io.OutputStream; | |||||
import java.security.KeyPair; | |||||
import java.security.MessageDigest; | |||||
import java.security.cert.X509Certificate; | |||||
import java.util.Collections; | |||||
import java.util.HashMap; | |||||
import java.util.LinkedList; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import javax.crypto.Cipher; | |||||
import javax.xml.crypto.Data; | |||||
import javax.xml.crypto.KeySelector; | |||||
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.dom.DOMCryptoContext; | |||||
import javax.xml.crypto.dsig.CanonicalizationMethod; | |||||
import javax.xml.crypto.dsig.DigestMethod; | |||||
import javax.xml.crypto.dsig.Reference; | |||||
import javax.xml.crypto.dsig.SignatureMethod; | |||||
import javax.xml.crypto.dsig.SignedInfo; | |||||
import javax.xml.crypto.dsig.XMLSignContext; | |||||
import javax.xml.crypto.dsig.XMLSignature; | |||||
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.spec.C14NMethodParameterSpec; | |||||
import javax.xml.parsers.DocumentBuilder; | |||||
import javax.xml.parsers.DocumentBuilderFactory; | |||||
import junit.framework.TestCase; | |||||
import org.apache.commons.lang.ArrayUtils; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import org.apache.poi.ooxml.signature.service.spi.DigestInfo; | |||||
import org.apache.xml.security.utils.Constants; | |||||
import org.apache.xpath.XPathAPI; | |||||
import org.bouncycastle.asn1.x509.KeyUsage; | |||||
import org.jcp.xml.dsig.internal.dom.DOMReference; | |||||
import org.jcp.xml.dsig.internal.dom.DOMXMLSignature; | |||||
import org.w3c.dom.Document; | |||||
import org.w3c.dom.Element; | |||||
import org.w3c.dom.Node; | |||||
import org.w3c.dom.NodeList; | |||||
public final class TestAbstractXmlSignatureService extends TestCase { | |||||
private static final Log LOG = LogFactory.getLog(TestAbstractXmlSignatureService.class); | |||||
private static class XmlSignatureTestService extends AbstractXmlSignatureService { | |||||
private Document envelopingDocument; | |||||
private List<String> referenceUris; | |||||
private TemporaryTestDataStorage temporaryDataStorage; | |||||
private String signatureDescription; | |||||
private ByteArrayOutputStream signedDocumentOutputStream; | |||||
private URIDereferencer uriDereferencer; | |||||
public XmlSignatureTestService() { | |||||
super(); | |||||
this.referenceUris = new LinkedList<String>(); | |||||
this.temporaryDataStorage = new TemporaryTestDataStorage(); | |||||
this.signedDocumentOutputStream = new ByteArrayOutputStream(); | |||||
} | |||||
public byte[] getSignedDocumentData() { | |||||
return this.signedDocumentOutputStream.toByteArray(); | |||||
} | |||||
public void setEnvelopingDocument(Document envelopingDocument) { | |||||
this.envelopingDocument = envelopingDocument; | |||||
} | |||||
@Override | |||||
protected Document getEnvelopingDocument() { | |||||
return this.envelopingDocument; | |||||
} | |||||
@Override | |||||
protected String getSignatureDescription() { | |||||
return this.signatureDescription; | |||||
} | |||||
public void setSignatureDescription(String signatureDescription) { | |||||
this.signatureDescription = signatureDescription; | |||||
} | |||||
@Override | |||||
protected List<String> getReferenceUris() { | |||||
return this.referenceUris; | |||||
} | |||||
public void addReferenceUri(String referenceUri) { | |||||
this.referenceUris.add(referenceUri); | |||||
} | |||||
@Override | |||||
protected OutputStream getSignedDocumentOutputStream() { | |||||
return this.signedDocumentOutputStream; | |||||
} | |||||
@Override | |||||
protected TemporaryDataStorage getTemporaryDataStorage() { | |||||
return this.temporaryDataStorage; | |||||
} | |||||
public String getFilesDigestAlgorithm() { | |||||
return null; | |||||
} | |||||
@Override | |||||
protected URIDereferencer getURIDereferencer() { | |||||
return this.uriDereferencer; | |||||
} | |||||
public void setUriDereferencer(URIDereferencer uriDereferencer) { | |||||
this.uriDereferencer = uriDereferencer; | |||||
} | |||||
} | |||||
private XMLSignatureFactory getXMLSignatureFactory() { | |||||
return XMLSignatureFactory.getInstance("DOM", new org.jcp.xml.dsig.internal.dom.XMLDSigRI()); | |||||
} | |||||
public void testSignEnvelopingDocument() throws Exception { | |||||
// setup | |||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||||
documentBuilderFactory.setNamespaceAware(true); | |||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||||
Document document = documentBuilder.newDocument(); | |||||
Element rootElement = document.createElementNS("urn:test", "tns:root"); | |||||
rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test"); | |||||
document.appendChild(rootElement); | |||||
Element dataElement = document.createElementNS("urn:test", "tns:data"); | |||||
dataElement.setAttributeNS(null, "Id", "id-1234"); | |||||
dataElement.setTextContent("data to be signed"); | |||||
rootElement.appendChild(dataElement); | |||||
XmlSignatureTestService testedInstance = new XmlSignatureTestService(); | |||||
testedInstance.setEnvelopingDocument(document); | |||||
testedInstance.addReferenceUri("#id-1234"); | |||||
testedInstance.setSignatureDescription("test-signature-description"); | |||||
// operate | |||||
DigestInfo digestInfo = testedInstance.preSign(null, null); | |||||
// verify | |||||
assertNotNull(digestInfo); | |||||
LOG.debug("digest info description: " + digestInfo.description); | |||||
assertEquals("test-signature-description", digestInfo.description); | |||||
assertNotNull(digestInfo.digestValue); | |||||
LOG.debug("digest algo: " + digestInfo.digestAlgo); | |||||
assertEquals("SHA-1", digestInfo.digestAlgo); | |||||
TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage(); | |||||
assertNotNull(temporaryDataStorage); | |||||
InputStream tempInputStream = temporaryDataStorage.getTempInputStream(); | |||||
assertNotNull(tempInputStream); | |||||
Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream); | |||||
LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument)); | |||||
Element nsElement = tmpDocument.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS); | |||||
Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement); | |||||
assertNotNull(digestValueNode); | |||||
String digestValueTextContent = digestValueNode.getTextContent(); | |||||
LOG.debug("digest value text content: " + digestValueTextContent); | |||||
assertTrue(digestValueTextContent.length() > 0); | |||||
/* | |||||
* Sign the received XML signature digest value. | |||||
*/ | |||||
KeyPair keyPair = PkiTestUtils.generateKeyPair(); | |||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); | |||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); | |||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue); | |||||
byte[] signatureValue = cipher.doFinal(digestInfoValue); | |||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", null, keyPair.getPrivate(), true, | |||||
0, null, null, new KeyUsage(KeyUsage.nonRepudiation)); | |||||
/* | |||||
* Operate: postSign | |||||
*/ | |||||
testedInstance.postSign(signatureValue, Collections.singletonList(certificate)); | |||||
byte[] signedDocumentData = testedInstance.getSignedDocumentData(); | |||||
assertNotNull(signedDocumentData); | |||||
Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData)); | |||||
LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument)); | |||||
NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); | |||||
assertEquals(1, signatureNodeList.getLength()); | |||||
Node signatureNode = signatureNodeList.item(0); | |||||
DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode); | |||||
XMLSignatureFactory xmlSignatureFactory = getXMLSignatureFactory(); | |||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext); | |||||
boolean validity = xmlSignature.validate(domValidateContext); | |||||
assertTrue(validity); | |||||
} | |||||
public static class UriTestDereferencer implements URIDereferencer { | |||||
private final Map<String, byte[]> resources; | |||||
public UriTestDereferencer() { | |||||
this.resources = new HashMap<String, byte[]>(); | |||||
} | |||||
public void addResource(String uri, byte[] data) { | |||||
this.resources.put(uri, data); | |||||
} | |||||
public Data dereference(URIReference uriReference, XMLCryptoContext xmlCryptoContext) throws URIReferenceException { | |||||
String uri = uriReference.getURI(); | |||||
byte[] data = this.resources.get(uri); | |||||
if (null == data) { | |||||
return null; | |||||
} | |||||
return new OctetStreamData(new ByteArrayInputStream(data)); | |||||
} | |||||
} | |||||
public void testSignExternalUri() throws Exception { | |||||
// setup | |||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||||
documentBuilderFactory.setNamespaceAware(true); | |||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||||
Document document = documentBuilder.newDocument(); | |||||
XmlSignatureTestService testedInstance = new XmlSignatureTestService(); | |||||
testedInstance.setEnvelopingDocument(document); | |||||
testedInstance.addReferenceUri("external-uri"); | |||||
testedInstance.setSignatureDescription("test-signature-description"); | |||||
UriTestDereferencer uriDereferencer = new UriTestDereferencer(); | |||||
uriDereferencer.addResource("external-uri", "hello world".getBytes()); | |||||
testedInstance.setUriDereferencer(uriDereferencer); | |||||
// operate | |||||
DigestInfo digestInfo = testedInstance.preSign(null, null); | |||||
// verify | |||||
assertNotNull(digestInfo); | |||||
LOG.debug("digest info description: " + digestInfo.description); | |||||
assertEquals("test-signature-description", digestInfo.description); | |||||
assertNotNull(digestInfo.digestValue); | |||||
LOG.debug("digest algo: " + digestInfo.digestAlgo); | |||||
assertEquals("SHA-1", digestInfo.digestAlgo); | |||||
TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage(); | |||||
assertNotNull(temporaryDataStorage); | |||||
InputStream tempInputStream = temporaryDataStorage.getTempInputStream(); | |||||
assertNotNull(tempInputStream); | |||||
Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream); | |||||
LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument)); | |||||
Element nsElement = tmpDocument.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS); | |||||
Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement); | |||||
assertNotNull(digestValueNode); | |||||
String digestValueTextContent = digestValueNode.getTextContent(); | |||||
LOG.debug("digest value text content: " + digestValueTextContent); | |||||
assertTrue(digestValueTextContent.length() > 0); | |||||
/* | |||||
* Sign the received XML signature digest value. | |||||
*/ | |||||
KeyPair keyPair = PkiTestUtils.generateKeyPair(); | |||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); | |||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); | |||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue); | |||||
byte[] signatureValue = cipher.doFinal(digestInfoValue); | |||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", null, keyPair.getPrivate(), true, | |||||
0, null, null, new KeyUsage(KeyUsage.nonRepudiation)); | |||||
/* | |||||
* Operate: postSign | |||||
*/ | |||||
testedInstance.postSign(signatureValue, Collections.singletonList(certificate)); | |||||
byte[] signedDocumentData = testedInstance.getSignedDocumentData(); | |||||
assertNotNull(signedDocumentData); | |||||
Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData)); | |||||
LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument)); | |||||
NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); | |||||
assertEquals(1, signatureNodeList.getLength()); | |||||
Node signatureNode = signatureNodeList.item(0); | |||||
DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode); | |||||
domValidateContext.setURIDereferencer(uriDereferencer); | |||||
XMLSignatureFactory xmlSignatureFactory = getXMLSignatureFactory(); | |||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext); | |||||
boolean validity = xmlSignature.validate(domValidateContext); | |||||
assertTrue(validity); | |||||
} | |||||
public void testSignEnvelopingDocumentWithExternalDigestInfo() throws Exception { | |||||
// setup | |||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||||
documentBuilderFactory.setNamespaceAware(true); | |||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||||
Document document = documentBuilder.newDocument(); | |||||
Element rootElement = document.createElementNS("urn:test", "tns:root"); | |||||
rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test"); | |||||
document.appendChild(rootElement); | |||||
XmlSignatureTestService testedInstance = new XmlSignatureTestService(); | |||||
testedInstance.setEnvelopingDocument(document); | |||||
testedInstance.setSignatureDescription("test-signature-description"); | |||||
byte[] refData = "hello world".getBytes(); | |||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); | |||||
messageDigest.update(refData); | |||||
byte[] digestValue = messageDigest.digest(); | |||||
DigestInfo refDigestInfo = new DigestInfo(digestValue, "SHA-1", "urn:test:ref"); | |||||
// operate | |||||
DigestInfo digestInfo = testedInstance.preSign(Collections.singletonList(refDigestInfo), null); | |||||
// verify | |||||
assertNotNull(digestInfo); | |||||
LOG.debug("digest info description: " + digestInfo.description); | |||||
assertEquals("test-signature-description", digestInfo.description); | |||||
assertNotNull(digestInfo.digestValue); | |||||
LOG.debug("digest algo: " + digestInfo.digestAlgo); | |||||
assertEquals("SHA-1", digestInfo.digestAlgo); | |||||
TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage(); | |||||
assertNotNull(temporaryDataStorage); | |||||
InputStream tempInputStream = temporaryDataStorage.getTempInputStream(); | |||||
assertNotNull(tempInputStream); | |||||
Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream); | |||||
LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument)); | |||||
Element nsElement = tmpDocument.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS); | |||||
Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement); | |||||
assertNotNull(digestValueNode); | |||||
String digestValueTextContent = digestValueNode.getTextContent(); | |||||
LOG.debug("digest value text content: " + digestValueTextContent); | |||||
assertTrue(digestValueTextContent.length() > 0); | |||||
/* | |||||
* Sign the received XML signature digest value. | |||||
*/ | |||||
KeyPair keyPair = PkiTestUtils.generateKeyPair(); | |||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); | |||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); | |||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue); | |||||
byte[] signatureValue = cipher.doFinal(digestInfoValue); | |||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", null, keyPair.getPrivate(), true, | |||||
0, null, null, new KeyUsage(KeyUsage.nonRepudiation)); | |||||
/* | |||||
* Operate: postSign | |||||
*/ | |||||
testedInstance.postSign(signatureValue, Collections.singletonList(certificate)); | |||||
byte[] signedDocumentData = testedInstance.getSignedDocumentData(); | |||||
assertNotNull(signedDocumentData); | |||||
Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData)); | |||||
LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument)); | |||||
NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); | |||||
assertEquals(1, signatureNodeList.getLength()); | |||||
Node signatureNode = signatureNodeList.item(0); | |||||
DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode); | |||||
URIDereferencer dereferencer = new URITest2Dereferencer(); | |||||
domValidateContext.setURIDereferencer(dereferencer); | |||||
XMLSignatureFactory xmlSignatureFactory = getXMLSignatureFactory(); | |||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext); | |||||
boolean validity = xmlSignature.validate(domValidateContext); | |||||
assertTrue(validity); | |||||
} | |||||
public void testSignExternalDigestInfo() throws Exception { | |||||
// setup | |||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||||
documentBuilderFactory.setNamespaceAware(true); | |||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||||
Document document = documentBuilder.newDocument(); | |||||
XmlSignatureTestService testedInstance = new XmlSignatureTestService(); | |||||
testedInstance.setEnvelopingDocument(document); | |||||
testedInstance.setSignatureDescription("test-signature-description"); | |||||
byte[] refData = "hello world".getBytes(); | |||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1"); | |||||
messageDigest.update(refData); | |||||
byte[] digestValue = messageDigest.digest(); | |||||
DigestInfo refDigestInfo = new DigestInfo(digestValue, "SHA-1", "urn:test:ref"); | |||||
// operate | |||||
DigestInfo digestInfo = testedInstance.preSign(Collections.singletonList(refDigestInfo), null); | |||||
// verify | |||||
assertNotNull(digestInfo); | |||||
LOG.debug("digest info description: " + digestInfo.description); | |||||
assertEquals("test-signature-description", digestInfo.description); | |||||
assertNotNull(digestInfo.digestValue); | |||||
LOG.debug("digest algo: " + digestInfo.digestAlgo); | |||||
assertEquals("SHA-1", digestInfo.digestAlgo); | |||||
TemporaryTestDataStorage temporaryDataStorage = (TemporaryTestDataStorage) testedInstance.getTemporaryDataStorage(); | |||||
assertNotNull(temporaryDataStorage); | |||||
InputStream tempInputStream = temporaryDataStorage.getTempInputStream(); | |||||
assertNotNull(tempInputStream); | |||||
Document tmpDocument = PkiTestUtils.loadDocument(tempInputStream); | |||||
LOG.debug("tmp document: " + PkiTestUtils.toString(tmpDocument)); | |||||
Element nsElement = tmpDocument.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS); | |||||
Node digestValueNode = XPathAPI.selectSingleNode(tmpDocument, "//ds:DigestValue", nsElement); | |||||
assertNotNull(digestValueNode); | |||||
String digestValueTextContent = digestValueNode.getTextContent(); | |||||
LOG.debug("digest value text content: " + digestValueTextContent); | |||||
assertTrue(digestValueTextContent.length() > 0); | |||||
/* | |||||
* Sign the received XML signature digest value. | |||||
*/ | |||||
KeyPair keyPair = PkiTestUtils.generateKeyPair(); | |||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); | |||||
cipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); | |||||
byte[] digestInfoValue = ArrayUtils.addAll(PkiTestUtils.SHA1_DIGEST_INFO_PREFIX, digestInfo.digestValue); | |||||
byte[] signatureValue = cipher.doFinal(digestInfoValue); | |||||
X509Certificate certificate = PkiTestUtils.generateCertificate(keyPair.getPublic(), "CN=Test", null, keyPair.getPrivate(), true, | |||||
0, null, null, new KeyUsage(KeyUsage.nonRepudiation)); | |||||
/* | |||||
* Operate: postSign | |||||
*/ | |||||
testedInstance.postSign(signatureValue, Collections.singletonList(certificate)); | |||||
byte[] signedDocumentData = testedInstance.getSignedDocumentData(); | |||||
assertNotNull(signedDocumentData); | |||||
Document signedDocument = PkiTestUtils.loadDocument(new ByteArrayInputStream(signedDocumentData)); | |||||
LOG.debug("signed document: " + PkiTestUtils.toString(signedDocument)); | |||||
NodeList signatureNodeList = signedDocument.getElementsByTagNameNS(XMLSignature.XMLNS, "Signature"); | |||||
assertEquals(1, signatureNodeList.getLength()); | |||||
Node signatureNode = signatureNodeList.item(0); | |||||
DOMValidateContext domValidateContext = new DOMValidateContext(KeySelector.singletonKeySelector(keyPair.getPublic()), signatureNode); | |||||
URIDereferencer dereferencer = new URITest2Dereferencer(); | |||||
domValidateContext.setURIDereferencer(dereferencer); | |||||
XMLSignatureFactory xmlSignatureFactory = getXMLSignatureFactory(); | |||||
XMLSignature xmlSignature = xmlSignatureFactory.unmarshalXMLSignature(domValidateContext); | |||||
boolean validity = xmlSignature.validate(domValidateContext); | |||||
assertTrue(validity); | |||||
} | |||||
private static class URITest2Dereferencer implements URIDereferencer { | |||||
private static final Log LOG = LogFactory.getLog(URITest2Dereferencer.class); | |||||
public Data dereference(URIReference uriReference, XMLCryptoContext context) throws URIReferenceException { | |||||
LOG.debug("dereference: " + uriReference.getURI()); | |||||
return new OctetStreamData(new ByteArrayInputStream("hello world".getBytes())); | |||||
} | |||||
} | |||||
public void testJsr105Signature() throws Exception { | |||||
KeyPair keyPair = PkiTestUtils.generateKeyPair(); | |||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||||
documentBuilderFactory.setNamespaceAware(true); | |||||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||||
Document document = documentBuilder.newDocument(); | |||||
Element rootElement = document.createElementNS("urn:test", "tns:root"); | |||||
rootElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:tns", "urn:test"); | |||||
document.appendChild(rootElement); | |||||
Element dataElement = document.createElementNS("urn:test", "tns:data"); | |||||
dataElement.setAttributeNS(null, "Id", "id-1234"); | |||||
dataElement.setTextContent("data to be signed"); | |||||
rootElement.appendChild(dataElement); | |||||
XMLSignatureFactory signatureFactory = XMLSignatureFactory.getInstance("DOM", new org.jcp.xml.dsig.internal.dom.XMLDSigRI()); | |||||
XMLSignContext signContext = new DOMSignContext(keyPair.getPrivate(), document.getDocumentElement()); | |||||
signContext.putNamespacePrefix(javax.xml.crypto.dsig.XMLSignature.XMLNS, "ds"); | |||||
DigestMethod digestMethod = signatureFactory.newDigestMethod(DigestMethod.SHA1, null); | |||||
Reference reference = signatureFactory.newReference("#id-1234", digestMethod); | |||||
DOMReference domReference = (DOMReference) reference; | |||||
assertNull(domReference.getCalculatedDigestValue()); | |||||
assertNull(domReference.getDigestValue()); | |||||
SignatureMethod signatureMethod = signatureFactory.newSignatureMethod(SignatureMethod.RSA_SHA1, null); | |||||
CanonicalizationMethod canonicalizationMethod = signatureFactory.newCanonicalizationMethod(CanonicalizationMethod.EXCLUSIVE_WITH_COMMENTS, | |||||
(C14NMethodParameterSpec) null); | |||||
SignedInfo signedInfo = signatureFactory.newSignedInfo(canonicalizationMethod, signatureMethod, Collections.singletonList(reference)); | |||||
javax.xml.crypto.dsig.XMLSignature xmlSignature = signatureFactory.newXMLSignature(signedInfo, null); | |||||
DOMXMLSignature domXmlSignature = (DOMXMLSignature) xmlSignature; | |||||
domXmlSignature.marshal(document.getDocumentElement(), "ds", (DOMCryptoContext) signContext); | |||||
domReference.digest(signContext); | |||||
// xmlSignature.sign(signContext); | |||||
// LOG.debug("signed document: " + toString(document)); | |||||
Element nsElement = document.createElement("ns"); | |||||
nsElement.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS); | |||||
Node digestValueNode = XPathAPI.selectSingleNode(document, "//ds:DigestValue", nsElement); | |||||
assertNotNull(digestValueNode); | |||||
String digestValueTextContent = digestValueNode.getTextContent(); | |||||
LOG.debug("digest value text content: " + digestValueTextContent); | |||||
assertTrue(digestValueTextContent.length() > 0); | |||||
} | |||||
} |
/* ==================================================================== | |||||
Licensed to the Apache Software Foundation (ASF) under one or more | |||||
contributor license agreements. See the NOTICE file distributed with | |||||
this work for additional information regarding copyright ownership. | |||||
The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
(the "License"); you may not use this file except in compliance with | |||||
the License. You may obtain a copy of the License at | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. | |||||
==================================================================== */ | |||||
/* | |||||
* Based on the eID Applet Project code. | |||||
* Original Copyright (C) 2008-2009 FedICT. | |||||
*/ | |||||
package org.apache.poi.ooxml.signature.service.signer; | |||||
import java.io.InputStream; | |||||
import java.net.URL; | |||||
import java.security.cert.X509Certificate; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import java.util.logging.Level; | |||||
import junit.framework.TestCase; | |||||
import org.apache.commons.logging.Log; | |||||
import org.apache.commons.logging.LogFactory; | |||||
import org.apache.poi.POIXMLDocument; | |||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLProvider; | |||||
import org.apache.poi.ooxml.signature.service.signer.ooxml.OOXMLSignatureVerifier; | |||||
import org.apache.poi.openxml4j.opc.OPCPackage; | |||||
import org.apache.poi.openxml4j.opc.PackagePart; | |||||
import org.apache.poi.openxml4j.opc.signature.PackageDigitalSignatureManager; | |||||
public class TestOOXMLSignatureVerifier extends TestCase { | |||||
private static final Log LOG = LogFactory.getLog(TestOOXMLSignatureVerifier.class); | |||||
static { | |||||
OOXMLProvider.install(); | |||||
} | |||||
public void testIsOOXMLDocument() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.docx"); | |||||
// operate | |||||
boolean result = OOXMLSignatureVerifier.isOOXML(url); | |||||
// verify | |||||
assertTrue(result); | |||||
} | |||||
public void testPOI() throws Exception { | |||||
// setup | |||||
InputStream inputStream = TestOOXMLSignatureVerifier.class.getResourceAsStream("/hello-world-unsigned.docx"); | |||||
// operate | |||||
boolean result = POIXMLDocument.hasOOXMLHeader(inputStream); | |||||
// verify | |||||
assertTrue(result); | |||||
} | |||||
public void testOPC() throws Exception { | |||||
// setup | |||||
InputStream inputStream = TestOOXMLSignatureVerifier.class.getResourceAsStream("/hello-world-signed.docx"); | |||||
// operate | |||||
OPCPackage opcPackage = OPCPackage.open(inputStream); | |||||
ArrayList<PackagePart> parts = opcPackage.getParts(); | |||||
for (PackagePart part : parts) { | |||||
LOG.debug("part name: " + part.getPartName().getName()); | |||||
LOG.debug("part content type: " + part.getContentType()); | |||||
} | |||||
ArrayList<PackagePart> signatureParts = opcPackage.getPartsByContentType("application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml"); | |||||
assertFalse(signatureParts.isEmpty()); | |||||
PackagePart signaturePart = signatureParts.get(0); | |||||
LOG.debug("signature part class type: " + signaturePart.getClass().getName()); | |||||
PackageDigitalSignatureManager packageDigitalSignatureManager = new PackageDigitalSignatureManager(); | |||||
// yeah... POI implementation still missing | |||||
} | |||||
public void testGetSignerUnsigned() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.docx"); | |||||
// operate | |||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url); | |||||
// verify | |||||
assertNotNull(result); | |||||
assertTrue(result.isEmpty()); | |||||
} | |||||
public void testGetSignerOffice2010Unsigned() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-office-2010-technical-preview-unsigned.docx"); | |||||
// operate | |||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url); | |||||
// verify | |||||
assertNotNull(result); | |||||
assertTrue(result.isEmpty()); | |||||
} | |||||
public void testGetSignerUnsignedPowerpoint() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.pptx"); | |||||
// operate | |||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url); | |||||
// verify | |||||
assertNotNull(result); | |||||
assertTrue(result.isEmpty()); | |||||
} | |||||
public void testGetSignerUnsignedExcel() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-unsigned.xlsx"); | |||||
// operate | |||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url); | |||||
// verify | |||||
assertNotNull(result); | |||||
assertTrue(result.isEmpty()); | |||||
} | |||||
public void testGetSigner() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.docx"); | |||||
// operate | |||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url); | |||||
// verify | |||||
assertNotNull(result); | |||||
assertEquals(1, result.size()); | |||||
X509Certificate signer = result.get(0); | |||||
LOG.debug("signer: " + signer.getSubjectX500Principal()); | |||||
} | |||||
public void testOffice2010TechnicalPreview() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-office-2010-technical-preview.docx"); | |||||
// operate | |||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url); | |||||
// verify | |||||
assertNotNull(result); | |||||
assertEquals(1, result.size()); | |||||
X509Certificate signer = result.get(0); | |||||
LOG.debug("signer: " + signer.getSubjectX500Principal()); | |||||
} | |||||
public void testGetSignerPowerpoint() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.pptx"); | |||||
// operate | |||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url); | |||||
// verify | |||||
assertNotNull(result); | |||||
assertEquals(1, result.size()); | |||||
X509Certificate signer = result.get(0); | |||||
LOG.debug("signer: " + signer.getSubjectX500Principal()); | |||||
} | |||||
public void testGetSignerExcel() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.xlsx"); | |||||
// operate | |||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url); | |||||
// verify | |||||
assertNotNull(result); | |||||
assertEquals(1, result.size()); | |||||
X509Certificate signer = result.get(0); | |||||
LOG.debug("signer: " + signer.getSubjectX500Principal()); | |||||
} | |||||
public void testGetSigners() throws Exception { | |||||
// setup | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed-twice.docx"); | |||||
// operate | |||||
List<X509Certificate> result = OOXMLSignatureVerifier.getSigners(url); | |||||
// verify | |||||
assertNotNull(result); | |||||
assertEquals(2, result.size()); | |||||
X509Certificate signer1 = result.get(0); | |||||
X509Certificate signer2 = result.get(1); | |||||
LOG.debug("signer 1: " + signer1.getSubjectX500Principal()); | |||||
LOG.debug("signer 2: " + signer2.getSubjectX500Principal()); | |||||
} | |||||
public void testVerifySignature() throws Exception { | |||||
java.util.logging.Logger logger = java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom"); | |||||
logger.log(Level.FINE, "test"); | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/hello-world-signed.docx"); | |||||
boolean validity = OOXMLSignatureVerifier.verifySignature(url); | |||||
assertTrue(validity); | |||||
} | |||||
public void testTamperedFile() throws Exception { | |||||
java.util.logging.Logger logger = java.util.logging.Logger.getLogger("org.jcp.xml.dsig.internal.dom"); | |||||
logger.log(Level.FINE, "test"); | |||||
URL url = TestOOXMLSignatureVerifier.class.getResource("/invalidsig.docx"); | |||||
boolean validity = OOXMLSignatureVerifier.verifySignature(url); | |||||
assertFalse(validity); | |||||
} | |||||
} |
/* ==================================================================== | |||||
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.ooxml.signature.service.signer.ooxml; | |||||
import java.util.Calendar; | |||||
import java.util.TimeZone; | |||||
import junit.framework.TestCase; | |||||
public final class TestOOXMLSignatureAspect extends TestCase { | |||||
private static final TimeZone TIME_ZONE_UTC = TimeZone.getTimeZone("UTC"); | |||||
public void testFormatTimestampAsISO8601() { | |||||
assertEquals("2010-06-05T04:03:02Z", OOXMLSignatureAspect.formatTimestampAsISO8601(makeTimestamp(2010, 6, 5, 4, 3, 2))); | |||||
} | |||||
private static long makeTimestamp(int year, int month, int day, int hour, int minute, int second) { | |||||
Calendar c = Calendar.getInstance(); | |||||
c.setTimeZone(TIME_ZONE_UTC); | |||||
c.set(year, month-1, day, hour, minute, second); | |||||
c.set(Calendar.MILLISECOND, 0); | |||||
return c.getTimeInMillis(); | |||||
} | |||||
} |