mirror of https://github.com/apache/poi.git
reverted Digital Signature stuff (r824836, r824963 r825294) in attempt to get gump build working
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@825637 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
bef0cdf35c
commit
a6331f9ec9
135
build.xml
135
build.xml
|
@ -1,5 +1,5 @@
|
||||||
<?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
|
||||||
|
@ -30,7 +30,7 @@ under the License.
|
||||||
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
|
||||||
|
@ -44,7 +44,7 @@ under the License.
|
||||||
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:
|
||||||
|
@ -123,7 +123,7 @@ under the License.
|
||||||
<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"/>
|
||||||
|
@ -132,19 +132,6 @@ under the License.
|
||||||
<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" -->
|
||||||
|
@ -155,7 +142,7 @@ under the License.
|
||||||
|
|
||||||
<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"/>
|
||||||
|
@ -173,12 +160,12 @@ under the License.
|
||||||
<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}">
|
<fileset dir="${main.lib}">
|
||||||
<include name="*.jar"/>
|
<include name="*.jar"/>
|
||||||
</fileset>
|
</fileset>
|
||||||
<pathelement location="${main.resource1.dir}"/>
|
<pathelement location="${main.resource1.dir}"/>
|
||||||
</path>
|
</path>
|
||||||
|
|
||||||
|
@ -196,9 +183,6 @@ under the License.
|
||||||
<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">
|
||||||
|
@ -222,9 +206,8 @@ under the License.
|
||||||
<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>
|
||||||
|
|
||||||
|
|
||||||
|
@ -288,7 +271,7 @@ under the License.
|
||||||
|
|
||||||
<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}"/>
|
||||||
|
@ -368,12 +351,6 @@ under the License.
|
||||||
<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>
|
||||||
|
@ -396,30 +373,6 @@ under the License.
|
||||||
<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">
|
||||||
|
@ -475,7 +428,7 @@ under the License.
|
||||||
</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"/>
|
||||||
|
|
||||||
|
@ -598,7 +551,7 @@ under the License.
|
||||||
<!-- 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>
|
||||||
|
@ -631,9 +584,9 @@ under the License.
|
||||||
<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.language" value="en"/>
|
||||||
<sysproperty key="user.country" value="US"/>
|
<sysproperty key="user.country" value="US"/>
|
||||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/>
|
<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"/>
|
||||||
|
@ -662,9 +615,9 @@ under the License.
|
||||||
<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.language" value="en"/>
|
||||||
<sysproperty key="user.country" value="US"/>
|
<sysproperty key="user.country" value="US"/>
|
||||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/>
|
<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}">
|
||||||
|
@ -674,7 +627,7 @@ under the License.
|
||||||
</fileset>
|
</fileset>
|
||||||
</batchtest>
|
</batchtest>
|
||||||
</junit>
|
</junit>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -688,9 +641,9 @@ under the License.
|
||||||
<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.language" value="en"/>
|
||||||
<sysproperty key="user.country" value="US"/>
|
<sysproperty key="user.country" value="US"/>
|
||||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/>
|
<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"/>
|
||||||
|
@ -724,9 +677,9 @@ under the License.
|
||||||
<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.language" value="en"/>
|
||||||
<sysproperty key="user.country" value="US"/>
|
<sysproperty key="user.country" value="US"/>
|
||||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/>
|
<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"/>
|
||||||
|
@ -755,9 +708,9 @@ under the License.
|
||||||
<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.language" value="en"/>
|
||||||
<sysproperty key="user.country" value="US"/>
|
<sysproperty key="user.country" value="US"/>
|
||||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/>
|
<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"/>
|
||||||
|
@ -765,7 +718,7 @@ under the License.
|
||||||
<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}"/>
|
||||||
|
@ -782,8 +735,8 @@ under the License.
|
||||||
<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.language" value="en"/>
|
||||||
<sysproperty key="user.country" value="US"/>
|
<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"/>
|
||||||
|
@ -812,9 +765,9 @@ under the License.
|
||||||
<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.language" value="en"/>
|
||||||
<sysproperty key="user.country" value="US"/>
|
<sysproperty key="user.country" value="US"/>
|
||||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/>
|
<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"/>
|
||||||
|
@ -836,9 +789,9 @@ under the License.
|
||||||
<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.language" value="en"/>
|
||||||
<sysproperty key="user.country" value="US"/>
|
<sysproperty key="user.country" value="US"/>
|
||||||
<sysproperty key="POI.testdata.path" file="${poi.test.dir}"/>
|
<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"/>
|
||||||
|
@ -1087,7 +1040,7 @@ FORREST_HOME environment variable!</echo>
|
||||||
<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}" />
|
||||||
|
@ -1100,7 +1053,7 @@ FORREST_HOME environment variable!</echo>
|
||||||
<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}" />
|
||||||
|
@ -1113,7 +1066,7 @@ FORREST_HOME environment variable!</echo>
|
||||||
<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}" />
|
||||||
|
@ -1126,7 +1079,7 @@ FORREST_HOME environment variable!</echo>
|
||||||
<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">
|
||||||
|
@ -1141,7 +1094,7 @@ FORREST_HOME environment variable!</echo>
|
||||||
<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>
|
||||||
|
|
||||||
|
@ -1244,7 +1197,7 @@ FORREST_HOME environment variable!</echo>
|
||||||
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>
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,3 @@ This product contains the Piccolo XML Parser for Java
|
||||||
|
|
||||||
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.
|
|
|
@ -1,610 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,99 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,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.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");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,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.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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,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.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;
|
|
||||||
}
|
|
|
@ -1,65 +0,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.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);
|
|
||||||
}
|
|
|
@ -1,343 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,44 +0,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.
|
|
||||||
==================================================================== */
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,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.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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,371 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,206 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,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.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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,274 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,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.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This package contains implementation classes for the Office Open XML Signature Service.
|
|
||||||
*/
|
|
||||||
package org.apache.poi.ooxml.signature.service.signer.ooxml;
|
|
||||||
|
|
|
@ -1,28 +0,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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This package contains implementation classes for the Signature Service SPI.
|
|
||||||
*/
|
|
||||||
package org.apache.poi.ooxml.signature.service.signer;
|
|
||||||
|
|
|
@ -1,56 +0,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.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;
|
|
||||||
}
|
|
|
@ -1,54 +0,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.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;
|
|
||||||
}
|
|
|
@ -1,64 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,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.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;
|
|
||||||
}
|
|
|
@ -1,77 +0,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.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);
|
|
||||||
}
|
|
|
@ -1,28 +0,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.
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* This package contains the service provider interfaces.
|
|
||||||
*/
|
|
||||||
package org.apache.poi.ooxml.signature.service.spi;
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,36 +0,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.
|
|
||||||
==================================================================== */
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,212 +0,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.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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,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.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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,209 +0,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.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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,554 +0,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.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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,238 +0,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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,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.
|
|
||||||
==================================================================== */
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue